Skip to content

feat: implement isolated API instances#492

Open
marcozabel wants to merge 8 commits into
open-feature:mainfrom
open-feature-forking:feat/isolated-api-instances
Open

feat: implement isolated API instances#492
marcozabel wants to merge 8 commits into
open-feature:mainfrom
open-feature-forking:feat/isolated-api-instances

Conversation

@marcozabel
Copy link
Copy Markdown

This PR

This PR introduces support for creating isolated OpenFeature API instances, each with their own providers, hooks, context, and event handling - enabling multi-tenant or side-by-side usage without shared global state.

Related Issues

#486

Notes

Follow-up Tasks

How to test

@marcozabel marcozabel requested review from a team as code owners April 22, 2026 09:49
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 22, 2026

Codecov Report

❌ Patch coverage is 87.25490% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.93%. Comparing base (16f2012) to head (1e8704c).

Files with missing lines Patch % Lines
openfeature/openfeature_api.go 86.45% 7 Missing and 6 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #492      +/-   ##
==========================================
- Coverage   84.07%   83.93%   -0.15%     
==========================================
  Files          27       27              
  Lines        2079     2147      +68     
==========================================
+ Hits         1748     1802      +54     
- Misses        292      299       +7     
- Partials       39       46       +7     
Flag Coverage Δ
e2e 83.93% <87.25%> (-0.15%) ⬇️
unit 83.93% <87.25%> (-0.15%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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 implements isolated API instances in the OpenFeature Go SDK, fulfilling requirements 1.8.1, 1.8.2, and 1.8.4. Key changes include the addition of a NewAPI factory function and a global registry to manage provider-to-instance bindings, preventing a single provider from being used across multiple instances. Review feedback highlights a critical risk of runtime panics when using interfaces as map keys and suggests a more efficient locking mechanism for unbinding providers. Additionally, it is recommended to update the documentation to emphasize the importance of calling Shutdown on isolated instances to prevent memory leaks.

Comment thread openfeature/openfeature_api.go Outdated
Comment thread openfeature/openfeature.go Outdated
@marcozabel marcozabel force-pushed the feat/isolated-api-instances branch 3 times, most recently from f9a372a to 51f73a0 Compare April 22, 2026 10:46
@sahidvelji sahidvelji linked an issue Apr 25, 2026 that may be closed by this pull request
Comment thread openfeature/isolated_api_test.go Outdated
}

// Requirement 1.8.1 (independence): State set on the singleton MUST NOT affect isolated instances.
func TestIsolatedAPI_SingetonDoesNotAffectInstance(t *testing.T) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
func TestIsolatedAPI_SingetonDoesNotAffectInstance(t *testing.T) {
func TestIsolatedAPI_SingletonDoesNotAffectInstance(t *testing.T) {

Comment thread openfeature/openfeature.go Outdated
// provider resources and free the provider bindings held by the global registry.
//
// Use [IEvaluation.GetClient] or [IEvaluation.GetNamedClient] to create clients bound to this instance.
func NewAPI() IEvaluation {
Copy link
Copy Markdown
Contributor

@sahidvelji sahidvelji Apr 25, 2026

Choose a reason for hiding this comment

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

I want to avoid returning an interface here. I suggest we export [evaluationAPI] and return this concrete type instead of the interface.

Comment thread openfeature/openfeature_api.go Outdated
// referenced by this API instance. Must be called with api.mu held.
// All comparisons use pointer identity (via providerBindingKey) to avoid panics from unhashable
// FeatureProvider implementations that contain maps or slices.
func (api *evaluationAPI) maybeUnbindOldProvider(oldProvider FeatureProvider) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The word "maybe" seems odd in this method name. Perhaps unbindIfUnreferenced?

Comment thread openfeature/openfeature.go Outdated
// Callers MUST invoke [IEvaluation.Shutdown] when the instance is no longer needed to release
// provider resources and free the provider bindings held by the global registry.
//
// Use [IEvaluation.GetClient] or [IEvaluation.GetNamedClient] to create clients bound to this instance.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A couple of things: section 1.8 is experimental in the spec, so worth noting that in the doc comment (e.g. // Experimental: ... at the top). Also, the spec recommends (1.8.3) that the factory live in a distinct module/package for discoverability; in Go this would typically be a sub-package like openfeature/isolated. WDYT?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for adding the experimental note! Just bumping the second half of this; any thoughts on moving NewAPI() to a sub-package like openfeature/isolated? Per 1.8.3 the factory should be in a distinct module/package so it's intentionally less discoverable than the singleton. WDYT?


// Requirement 1.8.4: A provider instance SHOULD NOT be bound to more than one API instance at a time.
func TestRequirement_1_8_4_CrossInstanceBinding(t *testing.T) {
t.Cleanup(func() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The provider isolation tests are solid and I like that both directions are covered. Worth adding similar tests for hooks, evaluation context, and events between isolated instances to match the coverage in other SDKs.

@toddbaert toddbaert requested review from dd-oleksii and erka April 27, 2026 19:22
@erka
Copy link
Copy Markdown
Member

erka commented Apr 28, 2026

IEvaluation interface and openfeature package functions are out of sync like SetProviderWithContext is missing, NewDefaultClient is GetClient(?), etc... func GetApiInstance() IEvaluation { is deprecated.

I don't see that we implement Requirement 1.8.2: Isolated instances MUST conform to the same API contract as the singleton here. Should there be the table driven test which runs against GetApiInstance and NewAPI?

The developer's experience isn't great after all in my opinion. If someone tries a new isolated API they will have to rewrite the code. It's not just plug and play @toddbaert

@marcozabel marcozabel force-pushed the feat/isolated-api-instances branch 2 times, most recently from 9d61b22 to 4003d8b Compare April 29, 2026 10:30
Signed-off-by: marcozabel <marco.zabel@dynatrace.com>
Signed-off-by: marcozabel <marco.zabel@dynatrace.com>
Signed-off-by: marcozabel <marco.zabel@dynatrace.com>
Signed-off-by: marcozabel <marco.zabel@dynatrace.com>
Signed-off-by: marcozabel <marco.zabel@dynatrace.com>
Signed-off-by: marcozabel <marco.zabel@dynatrace.com>
Signed-off-by: marcozabel <marco.zabel@dynatrace.com>
Signed-off-by: marcozabel <marco.zabel@dynatrace.com>
@marcozabel marcozabel force-pushed the feat/isolated-api-instances branch from ac5a289 to 1e8704c Compare April 29, 2026 12:13
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.

Support isolated API instances

4 participants