Skip to content

[Multi-provider] Gaps identified relative to js-sdk reference implementation #1882

@jonathannorris

Description

@jonathannorris

Context

We conducted a cross-SDK comparison of all MultiProvider implementations using the js-sdk as the reference. The Java MultiProvider was recently moved into the core SDK in #1765, and while functional for basic use cases, we identified several gaps relative to the reference implementation. Some of these were already noted during the original PR review.

Gaps

1. Child provider event aggregation and status tracking (High)

The MultiProvider extends EventProvider but does not listen to or forward events from child providers. If a child provider emits PROVIDER_ERROR, PROVIDER_STALE, or PROVIDER_CONFIGURATION_CHANGED at runtime, those events are not surfaced. This was called out in the #1765 review by @guidobrei:

"we effectively lose the Event features when using MultiProvider"

Expected behavior:

  • Listen to each child provider's events
  • Maintain a per-provider status map
  • Compute an aggregate status using "worst-wins" precedence: FATAL > NOT_READY > ERROR > STALE > READY
  • Emit the corresponding event when the aggregate status changes
  • Always forward PROVIDER_CONFIGURATION_CHANGED events (pass-through)

Reference: js-sdk status-tracker.ts, dotnet-sdk HandleProviderEventAsync / DetermineAggregateStatus

2. Per-provider hook execution during evaluation (High)

The strategy calls provider evaluation methods directly (e.g. provider.getBooleanEvaluation(...)), bypassing the SDK's hook pipeline. If a child provider defines hooks via getProviderHooks(), those hooks are not executed.

Expected behavior:

  • Before evaluating a child provider, run its before hooks with an isolated copy of the hook context
  • On success: run after hooks
  • On error: run error hooks
  • Always: run finally hooks
  • Hook context must be isolated per-provider to prevent cross-provider mutation

Reference: js-sdk hook-executor.ts, go-sdk isolation.go, dotnet-sdk ProviderExtensions.EvaluateAsync

3. Tracking event forwarding (High)

track() is not overridden. The default no-op implementation means tracking events are not forwarded to child providers.

Expected behavior:

  • Iterate over child providers and forward track() calls
  • The strategy should control which providers receive tracking (e.g. skip NOT_READY / FATAL providers)
  • Errors from individual track() calls should be caught and logged, not propagated

Reference: js-sdk multi-provider.ts track(), dotnet-sdk MultiProvider.cs Track()

4. ComparisonStrategy (Medium)

Only FirstMatchStrategy and FirstSuccessfulStrategy exist. There is no ComparisonStrategy for evaluating all providers and comparing results (useful for migration validation and consistency checks).

Expected behavior:

  • Evaluate all providers (ideally in parallel)
  • If all providers agree on the value, return it
  • If providers disagree, call an optional onMismatch callback and return the designated fallback provider's result
  • If any provider errors, collect and report all errors
  • Constructor accepts a fallbackProvider and optional onMismatch callback

Reference: js-sdk comparison-strategy.ts, go-sdk comparison_strategy.go, dotnet-sdk ComparisonStrategy.cs

5. Duplicate provider name handling (Medium)

When two child providers share the same metadata.name, the later provider silently overwrites the earlier one in the internal LinkedHashMap. The first provider is effectively lost. Related to #1792.

Expected behavior:

  • If an explicit name conflicts with an existing name, throw (misconfiguration)
  • If multiple providers share the same metadata-derived name, auto-deduplicate with a numeric suffix (name-1, name-2, etc.)
  • All providers should be preserved

Reference: js-sdk registerProviders(), dotnet-sdk RegisterProviders()

Spec Reference

https://openfeature.dev/specification/appendix-a/#multi-provider

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions