Skip to content

Add optimizely module with validated Scala factory#140

Merged
EtaCassiopeia merged 2 commits into
mainfrom
feat/optimizely-module
May 13, 2026
Merged

Add optimizely module with validated Scala factory#140
EtaCassiopeia merged 2 commits into
mainfrom
feat/optimizely-module

Conversation

@EtaCassiopeia
Copy link
Copy Markdown
Owner

@EtaCassiopeia EtaCassiopeia commented May 13, 2026

Summary

Implements #133 (D1 — optimizely module scaffold) and #134 (D2 — validated factory).

Scope deviation from the original plan

The plan assumed depending on dev.openfeature.contrib.providers:optimizely, but that artifact is not published to Maven Central — only present as source in the contrib repo. Rather than block on upstream's release timeline, this PR integrates with the Optimizely Java SDK directly via com.optimizely.ab:core-api:4.2.2 + core-httpclient-impl:4.2.2. Trade-offs:

  • ✅ Ships now; production use case isn't blocked on upstream.
  • ✅ Idiomatic Scala/ZIO surface; full control over error mapping and lifecycle.
  • ⚠️ We own ~300 lines of integration code mapping Optimizely's decision API to OpenFeature's evaluation API. The forthcoming WireMock-backed failure spec ([D4] Optimizely WireMock-backed failure-mode integration suite #136) covers the lifecycle / error paths end-to-end.

When dev.openfeature.contrib.providers:optimizely is eventually published, switching is a small refactor that this PR doesn't preclude.

What lands

  • New optimizely/ sbt sub-project, aggregated into root, cross-built on Scala 2.13.16 + 3.3.4.
  • OptimizelyFeatureProviderEventProvider implementation wrapping com.optimizely.ab.Optimizely:
    • initialize() registers an Optimizely UpdateConfigNotification handler that counts down a latch on first fire so setProviderAndWait returns cleanly; subsequent fires emit PROVIDER_CONFIGURATION_CHANGED.
    • getState mirrors the Optimizely client's isValid state.
    • Decision mapping for all five OF evaluation types (Boolean → getEnabled; String / Int / Double → typed variable lookup via decision.getVariables.getValue(variableKey, classOf[T]); Object → getVariables.toMap wrapped in a Structure). The variable key defaults to "value" and can be overridden per-evaluation via the context attribute openfeature.variableKey, matching the contrib provider's convention so apps switching between integrations see consistent behaviour.
    • Provider failure modes map to OpenFeature error codes: missing targetingKeyTARGETING_KEY_MISSING, client not valid yet → PROVIDER_NOT_READY, Optimizely reasons containing "not found" → FLAG_NOT_FOUND.
    • shutdown() removes the notification listener and (by default) closes the underlying client.
  • OptimizelyProvider — Scala factory object with four shapes:
    • make(sdkKey) — CDN datafile, validates SDK key.
    • make(sdkKey, datafileUrl) — self-hosted Optimizely Agent, validates both.
    • fromOptimizelyClient(client) — escape hatch; caller manages lifecycle (we don't close the client on shutdown).
    • layer(...) ZLayer wrappers that surface FeatureFlagError.InvalidConfiguration at build time.
  • SDK key validation: non-null, non-empty after trim, no whitespace, length 6–128, matches [A-Za-z0-9_-]+, rejects obvious placeholders (YOUR_SDK_KEY, <sdk-key>, changeme, …).
  • Datafile URL validation: parseable, scheme http/https, non-empty host.
  • ContextTransformer — converts OF EvaluationContext (targetingKey + Value-wrapped attributes) to the (userId, Map[String, Object]) pair Optimizely needs.

Closes #133
Closes #134

@EtaCassiopeia EtaCassiopeia force-pushed the feat/optimizely-module branch from 84d73e2 to 20cd449 Compare May 13, 2026 16:22
@EtaCassiopeia EtaCassiopeia merged commit fcb11c6 into main May 13, 2026
1 check passed
@EtaCassiopeia EtaCassiopeia deleted the feat/optimizely-module branch May 13, 2026 16:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:optimizely optimizely module kind:chore build / CI / tooling kind:feature new feature or behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[D2] OptimizelyProvider factory: CDN default + SDK-key/URL validation [D1] Scaffold optimizely/ sbt module + contrib provider dependency

1 participant