Skip to content

feat: add Domains (formerly Clients) (#185)#231

Open
mstepien wants to merge 13 commits into
open-feature:mainfrom
mstepien:185-domains
Open

feat: add Domains (formerly Clients) (#185)#231
mstepien wants to merge 13 commits into
open-feature:mainfrom
mstepien:185-domains

Conversation

@mstepien
Copy link
Copy Markdown

@mstepien mstepien commented Apr 13, 2026

Description

This PR introduces the Domains (formerly Named Clients) feature to the OpenFeature Kotlin SDK, ensuring full compliance with the OpenFeature Domains specification.

Domains allow logical binding of clients with specific providers, enabling the use of multiple providers in a single application. If no specific provider or context is bound to a given domain, the system gracefully falls back to the globally configured default provider and evaluation context.

Changes Made

  • Client Metadata Updates: Added domain: String? to ClientMetadata. Concurrently deprecated name in favor of domain to align with the wider OpenFeature schema and specification.
  • Support for Domain-Bound Providers: Augmented OpenFeatureAPI with domain-parameterized methods (e.g., setProvider(domain, provider), getProvider(domain), getProviderStatus(domain)).
  • Domain State Isolation: Introduced ProviderRepository and DomainState to isolate lifecycle, observers, events, and execution flow tracking per domain in a thread-safe manner using atomic flow substitutions and Mutex.
  • Domain-Specific Evaluation Context: Permitted setting and retrieving EvaluationContext on a per-domain basis setEvaluationContext(domain, context). A domain client will first attempt to use its specific contextual state before defaulting back to the global execution context.
  • Event Flow Isolation: Ensured that hot-swapping providers and evaluating events dynamically bubbles up isolated state and OpenFeatureStatus to the individual domain correctly.
  • E2E Validation: Provided test coverage in ProviderRepositoryTest and DomainE2ETest to enforce atomic state concurrency, dynamically mapped fallback lifecycles, and strict hook/event propagation rules bounded to isolated domains.

Related Issues

Closes #185

Testing

  • End-to-end domain evaluation test assertions executed (DomainE2ETest)
  • Concurrency and flow-mapping assertions completed (ProviderRepositoryTest)
  • Evaluated hook propagation order locally.
  • Unit/Integration tests all passed successfully on JVM target.

Reviewer Focus

  • Please verify the fallback logic in OpenFeatureAPI where missing domain contexts gracefully fallback to global contexts without breaking isolation.
  • Sanity-check the coroutine scope management and flow-switching inside ProviderRepository (getStateFlow()) for potential leaks when updating providers rapidly.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces domain-specific support to the Kotlin SDK, allowing providers and evaluation contexts to be isolated per domain while maintaining a global fallback mechanism. Key architectural changes include the addition of a ProviderRepository to manage DomainState and updates to OpenFeatureAPI and OpenFeatureClient to handle domain-specific logic. The review feedback highlights several critical issues regarding state isolation: specifically, ensuring that initialContext is bound to the correct domain, using domain-specific contexts during provider initialization and event tracking, and correctly propagating global context updates when no domain override exists. Additionally, there is a recommendation to handle dispatcher changes more dynamically within the domain state listener.

Comment thread kotlin-sdk/src/commonMain/kotlin/dev/openfeature/kotlin/sdk/OpenFeatureAPI.kt Outdated
Comment thread kotlin-sdk/src/commonMain/kotlin/dev/openfeature/kotlin/sdk/OpenFeatureAPI.kt Outdated
Comment thread kotlin-sdk/src/commonMain/kotlin/dev/openfeature/kotlin/sdk/OpenFeatureClient.kt Outdated
Comment thread kotlin-sdk/src/commonMain/kotlin/dev/openfeature/kotlin/sdk/ProviderRepository.kt Outdated
@mstepien mstepien marked this pull request as ready for review April 15, 2026 13:52
typotter added a commit that referenced this pull request Apr 15, 2026
LoggingHook, Logger interface, and platform-specific LoggerFactory
implementations are merged. PII filtering (#221) and full logging
docs (#220) are still open. Domain binding (#231) is actively in
development. Update status symbols from ❌ to ⚠️ and rewrite the
Logging and Domains sections to reflect current state.
typotter pushed a commit that referenced this pull request Apr 21, 2026
Full logging stack (#233, #221, #220) is now merged — update Logging
from ⚠️ to ✅. Domain binding (#231) is actively in development —
update Domains from ❌ to ⚠️ and rewrite the section to reflect
current state with a link to the OpenFeature Domains docs.

Signed-off-by: Tyler Potter <tyler.john.potter@gmail.com>
Comment thread kotlin-sdk/src/commonMain/kotlin/dev/openfeature/kotlin/sdk/OpenFeatureAPI.kt Outdated
Comment thread kotlin-sdk/src/commonMain/kotlin/dev/openfeature/kotlin/sdk/ProviderRepository.kt Outdated
Comment thread kotlin-sdk/src/commonMain/kotlin/dev/openfeature/kotlin/sdk/ProviderRepository.kt Outdated
Comment thread kotlin-sdk/src/commonMain/kotlin/dev/openfeature/kotlin/sdk/ProviderRepository.kt Outdated
Comment thread kotlin-sdk/src/commonMain/kotlin/dev/openfeature/kotlin/sdk/OpenFeatureAPI.kt Outdated
Comment thread kotlin-sdk/src/commonMain/kotlin/dev/openfeature/kotlin/sdk/ProviderRepository.kt Outdated
Comment thread kotlin-sdk/src/commonMain/kotlin/dev/openfeature/kotlin/sdk/ProviderRepository.kt Outdated
Comment thread kotlin-sdk/src/commonMain/kotlin/dev/openfeature/kotlin/sdk/ProviderRepository.kt Outdated
Comment thread kotlin-sdk/src/commonMain/kotlin/dev/openfeature/kotlin/sdk/ProviderRepository.kt Outdated
Comment thread kotlin-sdk/src/commonMain/kotlin/dev/openfeature/kotlin/sdk/ProviderRepository.kt Outdated
Comment thread kotlin-sdk/src/commonMain/kotlin/dev/openfeature/kotlin/sdk/ProviderRepository.kt Outdated
Comment thread kotlin-sdk/src/commonMain/kotlin/dev/openfeature/kotlin/sdk/OpenFeatureAPI.kt Outdated
@typotter
Copy link
Copy Markdown
Contributor

Spec 3.2.2.4 requires the API to have a mechanism to manage (create and remove) evaluation context for an associated domain.

There is no clearEvaluationContext(domain) method. Once domain context is set, it can be replaced but not cleared to revert to global fallback. Worth considering whether setEvaluationContext(domain, emptyContext) is sufficient or whether an explicit clear is needed.

Signed-off-by: Marcin Stepien <marcin.stepien@fluxon.com>
Signed-off-by: Marcin Stepien <marcin.stepien@fluxon.com>
cache domain context merging to eliminate GC evaluation overhead
Signed-off-by: Marcin Stepien <marcin.stepien@fluxon.com>
Signed-off-by: Marcin Stepien <marcin.stepien@fluxon.com>
Signed-off-by: Marcin Stepien <marcin.stepien@fluxon.com>
…#185)

harden provider termination sequences against race conditions and event pollution (open-feature#185)
Signed-off-by: Marcin Stepien <marcin.stepien@fluxon.com>
Spec 3.2.2.4 requires the API to have a mechanism to manage (create and remove) evaluation context for an associated domain.

- Added ability to securely manage and clear evaluation contexts natively per domain.
- Introduced `contextMutex` and `ioMutex` to fix global vs domain TOCTOU Deadlocks.
- Ensured phantom background coroutines cancel properly on `shutdown()`.

Signed-off-by: Marcin Stepien <marcin.stepien@fluxon.com>
Signed-off-by: Marcin Stepien <marcin.stepien@fluxon.com>
@mstepien
Copy link
Copy Markdown
Author

Spec 3.2.2.4 requires the API to have a mechanism to manage (create and remove) evaluation context for an associated domain.

There is no clearEvaluationContext(domain) method. Once domain context is set, it can be replaced but not cleared to revert to global fallback. Worth considering whether setEvaluationContext(domain, emptyContext) is sufficient or whether an explicit clear is needed.

fixed with ca14dfa

…n-feature#185)

- Added `observeEvents()` and typed `observe<T>()` to the `Client` interface to natively route domain-bound provider events (resolves spec 5.2.1 & 5.2.7).
Signed-off-by: Marcin Stepien <marcin.stepien@fluxon.com>
- 5.3.4.1: now emits `ProviderReconciling` prior to executing the provider's `onContextSet` function.
- 5.3.4.2: now emits `ProviderContextChanged` if the provider's `onContextSet` function terminates normally.
- 5.3.4.3: catches crashes within `onContextSet` and emits `ProviderError` instead of `ProviderContextChanged`.
Signed-off-by: Marcin Stepien <marcin.stepien@fluxon.com>
open-feature#185)

Spec 1.1.2.3: The provider mutator function MUST invoke the shutdown function on the previously registered provider once it's no longer being used to resolve flag values. Provider instances which are bound to multiple domains won't be shut down until the last binding is removed.

- thread-safe attachProvider and detachProvider reference counting in ProviderRepository.
- refactored DomainState teardown to decouple the FeatureProvider.shutdown() invocation from localized domain states.
- ensured OpenFeatureAPI only invokes shutdown() when a provider's global domain reference count reaches zero.
Signed-off-by: Marcin Stepien <marcin.stepien@fluxon.com>
-  global reference counting and initialization mutexes in
  `ProviderRepository` to track shared provider lifecycles.
- deduplicated `provider.initialize()` and `provider.shutdown()` calls so
  providers bound to multiple domains are only initialized once and shut
  down only when the last domain unbinds.
- global status ledger that syncs background health
  transitions (e.g., `ProviderError` or `ProviderStale`) across all domains
  sharing the provider instance.

- Specs 1.1.2.2: "Provider instances which are already active (because they have been bound to another domain or otherwise) need not be initialized again."
- Specs 1.1.2.3: "Provider instances which are bound to multiple domains won't be shut down until the last binding is removed."
Signed-off-by: Marcin Stepien <marcin.stepien@fluxon.com>
@mstepien mstepien requested a review from typotter April 23, 2026 18:27
@mstepien
Copy link
Copy Markdown
Author

2026-04-22-23 PR Update: Multi-Domain Isolation, Lifecycle Management, and Specification Compliance

This update brings core specification's requirements regarding isolated eventing, shared provider lifecycles, and evaluation context management.

Provider Lifecycle & Deduplication

  • Thread-safe Shared Provider Tracking (Spec 1.1.2.3): Implemented global reference counting in ProviderRepository. OpenFeatureAPI now ensures a shared provider's shutdown() function is only invoked once the provider's global reference count across all domains safely reaches zero.
  • Initialization Deduplication (Spec 1.1.2.2): Introduced Mutex-backed initialization tracking to guarantee that a provider bound to multiple domains is only actively initialized exactly once.
  • Global Health Ledger (Spec 1.1.2.2): Created a centralized status tracker that securely syncs background health transitions (like ProviderError crashes or ProviderStale) across all active domains sharing a provider instance.
  • Synthetic Event Emission: Ensures that domains successfully bypassing redundant initialization actively poll the global ledger and synthetically rebroadcast the synchronized state (e.g., ProviderReady) to their downstream clients.

Domain-Isolated Eventing

  • Client Event Observability (Specs 5.2.1, 5.2.7): Added observeEvents() and observe<T>() APIs to the Client interface, natively routing domain-isolated provider events directly to end users.
  • Context Event Generation (Specs 5.3.4.1-3): Hardened the setEvaluationContext workflow to securely isolate event emissions. It now emits ProviderReconciling prior to execution, ProviderContextChanged on success, and catches unhandled exceptions to emit ProviderError if a crash occurs within the provider's onContextSet.

Evaluation Context Management

  • Domain Context Merging (Spec 3.2.3): Added context merging capabilities bound per domain, including an internal cache to eliminate garbage collection overhead during rapid, highly-concurrent flag evaluations.
  • Context Teardown (Spec 3.2.2.4): Added the ability to clear and manage evaluation contexts natively per domain, fortified with granular contextMutex locks to prevent TOCTOU race condition deadlocks.

Architecture Hardening & Tech Debt

  • State Encapsulation: Refactored architecture to hide internal state objects (DomainState) from the public API.
  • Memory & Thread Safety: Memoized the defaultDomainState flow to prevent allocations, eliminated race conditions during rapid provider swapping, and ensured all phantom background observer coroutines cancel out completely upon OpenFeatureAPI.shutdown().

Comment thread kotlin-sdk/src/commonMain/kotlin/dev/openfeature/kotlin/sdk/OpenFeatureAPI.kt Outdated
Moved the `setProvider` and `setProviderAndWait` methods that take a `domain`
parameter from the OpenFeatureAPI singleton to the Client interface.
This improves API ergonomics by allowing developers to bind providers directly
to a domain-scoped client instance, adhering to the design principle that
getClient(domain) should be the sole place the domain string is required.
The domain-parameterized routing methods on OpenFeatureAPI have been made
internal to preserve a clean public API surface area.
Signed-off-by: Marcin Stepien <marcin.stepien@fluxon.com>
@mstepien mstepien requested a review from typotter May 8, 2026 10:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Domains (formerly Named Clients)

2 participants