Skip to content

Conversation

@MattIPv4
Copy link
Member

This PR

Allows for folks who do not wish to interact with the global singleton instance of the OpenFeatureAPI in the web-sdk to instead use a package-local singleton instance of the OpenFeatureAPI. This should be useful for micro-frontend environments where there is a risk for different versions of the SDK being loaded by micro-frontends and colliding in window due to the global singleton mechanism.

Related Issues

https://cloud-native.slack.com/archives/C06E4DE6S07/p1764115826255009

Notes

N/A

Follow-up Tasks

N/A

How to test

tbd.

Signed-off-by: MattIPv4 <me@mattcowley.co.uk>
@MattIPv4 MattIPv4 force-pushed the feat/non-global-singleton branch 2 times, most recently from 48eff17 to 618f9ea Compare November 28, 2025 19:41
Signed-off-by: MattIPv4 <me@mattcowley.co.uk>
@MattIPv4 MattIPv4 force-pushed the feat/non-global-singleton branch from 618f9ea to 98c3bc0 Compare November 28, 2025 19:43
@lukas-reining
Copy link
Member

Hey @MattIPv4, thanks for the proposal.
This looks like a good approach to tackle the problem.

I see the following things to consider here:

1. Higher Level SDKs and integrations
Several mechanisms are relying on the singleton. E.g the React and Angular SDK are using the singleton today.
A micro frontend architecture using the higher level React and Angular SDKs (ignoring all other possible problems with the SDKs and micro frontends) would not be benefitting from this today. And it could become hard to communicate the intended use and possible problems with these SDKs using micro frontends.
In both SDKs, I actually think this could be solved by updating them to use the new package local singleton as users of these SDKs typically do not have to interact with the singleton anyways.

2. Provider lifecycle
Allowing more than one instance of the API, we should be careful about the provider lifecycles.
We should never have a provider instance get registered to more than one instance of the OpenFeature API.
If we had, the static context and the initialization and shutdown make the provider state inconsistent, always resulting in a last write wins situation.
A solution to handle this could be to add a method to the provider class to lock the provider. When one SDK would run setProvider, it could check the lock and either call setLocked or fail if the provider has already been locked by another instance of the API. As a provider may be bound to the same SDK "multiple times", we could save the API object identity or generate a ID per instance which would be checked in the lock.

3. API clarity
I am not 100% sure about having the package local singleton.
Having the package local singleton as a getter on the global singleton could make understanding the consequences of using either pretty complicated.
My feeling is, that is would be better to allow for the singleton as it is today and a way to create a new "non-singleton" API. This would give the responsibility of making the API available throughout the code to the app author in the case they can not use the window singleton. They could either create a singleton theirselves or use something like dependency injection if they want. To me this would make the two ways of using the API more clear to app authors.

I think allow more than one API makes sense. And I think the way you drafted it makes sense. To me concern 2 and 3 should be considered though. What you you think @MattIPv4?

@MattIPv4
Copy link
Member Author

MattIPv4 commented Dec 2, 2025

Hey!

1. Higher Level SDKs and integrations Several mechanisms are relying on the singleton. E.g the React and Angular SDK are using the singleton today. A micro frontend architecture using the higher level React and Angular SDKs (ignoring all other possible problems with the SDKs and micro frontends) would not be benefitting from this today. And it could become hard to communicate the intended use and possible problems with these SDKs using micro frontends. In both SDKs, I actually think this could be solved by updating them to use the new package local singleton as users of these SDKs typically do not have to interact with the singleton anyways.

To avoid this being a breaking change, I don't want to switch the higher-level SDKs over to using the isolated instance by default. I can see there being a world in which users are wanting the global singleton behaviour there, even in a micro-frontend -- you might want the provider set by the core of the frontend to be accessible by micro-frontends using the higher-level SDKs.

I think my suggestion would be to add an isolated flag to the higher-level SDK providers (I'm only familiar with the React one, so do correct me if that doesn't make sense for them all), so that folks can opt into the isolated behavior if they want, similar to how you'd opt into it if you were just using the base web SDK.

2. Provider lifecycle Allowing more than one instance of the API, we should be careful about the provider lifecycles. We should never have a provider instance get registered to more than one instance of the OpenFeature API. If we had, the static context and the initialization and shutdown make the provider state inconsistent, always resulting in a last write wins situation. A solution to handle this could be to add a method to the provider class to lock the provider. When one SDK would run setProvider, it could check the lock and either call setLocked or fail if the provider has already been locked by another instance of the API. As a provider may be bound to the same SDK "multiple times", we could save the API object identity or generate a ID per instance which would be checked in the lock.

Absolutely agree with this, adding a locking mechanism so that a provider can't be reused makes sense. Though, unless I've missed something, this is already a problem today, as you can add a provider to multiple domains? Do we want to continue allowing this (in which case we'll want to track a ref to the API instance), or prevent that as part of the new locking mechanism (in which case we can just track a lock bool)?

3. API clarity I am not 100% sure about having the package local singleton. Having the package local singleton as a getter on the global singleton could make understanding the consequences of using either pretty complicated. My feeling is, that is would be better to allow for the singleton as it is today and a way to create a new "non-singleton" API. This would give the responsibility of making the API available throughout the code to the app author in the case they can not use the window singleton. They could either create a singleton theirselves or use something like dependency injection if they want. To me this would make the two ways of using the API more clear to app authors.

I'm not sure I follow why you wouldn't want a package-local singleton? If you import this in multiple files, you'd want the same instance to interact with? Or are you suggesting you'd prefer folks to manage this fully themselves and export/import a single instance around their app on their own? I don't really see any benefit to the latter; it seems like a worse DX for no benefit (once you've got package-level isolation to solve the SDK versioning issues, domains give you any additional isolation you'd need, I think)?

@lukas-reining
Copy link
Member

To avoid this being a breaking change, I don't want to switch the higher-level SDKs over to using the isolated instance by default. I can see there being a world in which users are wanting the global singleton behaviour there, even in a micro-frontend -- you might want the provider set by the core of the frontend to be accessible by micro-frontends using the higher-level SDKs.

I think my suggestion would be to add an isolated flag to the higher-level SDK providers (I'm only familiar with the React one, so do correct me if that doesn't make sense for them all), so that folks can opt into the isolated behavior if they want, similar to how you'd opt into it if you were just using the base web SDK.

That's a valid point. I think this would work

@lukas-reining
Copy link
Member

  1. Provider lifecycle Allowing more than one instance of the API, we should be careful about the provider lifecycles. We should never have a provider instance get registered to more than one instance of the OpenFeature API. If we had, the static context and the initialization and shutdown make the provider state inconsistent, always resulting in a last write wins situation. A solution to handle this could be to add a method to the provider class to lock the provider. When one SDK would run setProvider, it could check the lock and either call setLocked or fail if the provider has already been locked by another instance of the API. As a provider may be bound to the same SDK "multiple times", we could save the API object identity or generate a ID per instance which would be checked in the lock.

Absolutely agree with this, adding a locking mechanism so that a provider can't be reused makes sense. Though, unless I've missed something, this is already a problem today, as you can add a provider to multiple domains? Do we want to continue allowing this (in which case we'll want to track a ref to the API instance), or prevent that as part of the new locking mechanism (in which case we can just track a lock bool)?

this is already a problem today, as you can add a provider to multiple domains

Binding to multiple domains is not a problem. The SDK tracks if a provider is still used in another domain or the default domain. Only if a provider is not registered for any domain anymore, it is shutdown.
And for setting the context it is not a problem as the static context is the same for all domains as it is only set on the SDK.

There really must be two API instances for this to be a problem.

Do we want to continue allowing this (in which case we'll want to track a ref to the API instance), or prevent that as part of the new locking mechanism (in which case we can just track a lock bool)?

Good question, I think we can solve this in another issue. I will open one up for this explicitly.
If we provide a non-singleton way for other languages, the locking should even become part of the API.
But I would wait for the result of this discussion.

@lukas-reining
Copy link
Member

I'm not sure I follow why you wouldn't want a package-local singleton? If you import this in multiple files, you'd want the same instance to interact with? Or are you suggesting you'd prefer folks to manage this fully themselves and export/import a single instance around their app on their own? I don't really see any benefit to the latter; it seems like a worse DX for no benefit (once you've got package-level isolation to solve the SDK versioning issues, domains give you any additional isolation you'd need, I think)?

Or are you suggesting you'd prefer folks to manage this fully themselves and export/import a single instance around their app on their own?

Yes I do. But I am not 100% sure if we want it or if the package-local singleton solves enough of an issue while we may not want to handler others for now.
Issues like #1218 suggest that there are cases with only one version and instance of the SDK package where it could be necessary to have two API instances.
For that I want to be sure that we know before if we want to consider and solve these cases or not.

Thinking about it, I might be leaning towards agreeing with the package-local singleton and domains beeing sufficient,
even though does not handle the case described in #1218.

@MattIPv4
Copy link
Member Author

MattIPv4 commented Dec 4, 2025

Binding to multiple domains is not a problem. The SDK tracks if a provider is still used in another domain or the default domain. Only if a provider is not registered for any domain anymore, it is shutdown. And for setting the context it is not a problem as the static context is the same for all domains as it is only set on the SDK.

There really must be two API instances for this to be a problem.

Ah good to know, I hadn't poked around closely enough at how this works today!

Good question, I think we can solve this in another issue. I will open one up for this explicitly.
If we provide a non-singleton way for other languages, the locking should even become part of the API.
But I would wait for the result of this discussion.

That sounds good to me -- leave it as a known gap in this implementation for now (given this is very explicitly opt-in as a whole, I'd hope folks aren't going to mix and match both), and then address it separately down the road 👍

@toddbaert
Copy link
Member

I wonder if a better API might be to expose a public constructor for a client, which takes a provider; and in this version of the API, there's no singleton involved. The relationship between the provider and the client is extremely obvious, and more clients bound to the provider could be easily added by simply instantiating new clients with the same provider instance.

var myProvider = new MyProvider();
var isolatedClient = new Client(myProvider)

I think this provides a lot of flexibility, and I think it would be compatible with many of our existing SDKS, for example, in react, you could manually supply this client to the <OpenFeatureProvider>

@MattIPv4
Copy link
Member Author

MattIPv4 commented Dec 8, 2025

@toddbaert I think I agree in principle, but immediately off the top of my head, I think this'd cause issues with context changes? Folks would need to start calling onContextChange manually, if it is set, and the SDKs would somehow need to know (for the React SDK's useContextMutator, for example) if they're working with a regular client or an isolated client, so they know how to update the context correctly? My gut says it is better to keep to one consistent interface that end users and the higher-level SDKs work with, rather than having two different interfaces that get mixed together?

Though actually, I think useContextMutator wouldn't work correctly today if you pass in a client to <OpenFeatureProvider>, as it won't know the domain and so will always set the default domain context?

@toddbaert
Copy link
Member

@toddbaert I think I agree in principle, but immediately off the top of my head, I think this'd cause issues with context changes? Folks would need to start calling onContextChange manually, if it is set, and the SDKs would somehow need to know (for the React SDK's useContextMutator, for example) if they're working with a regular client or an isolated client, so they know how to update the context correctly?

Excellent point.... I think the root issue here is we only have a context mutation function at the root/singleton in the static context (aka client) paradigm so there's no practical way to properly update the context just for the client in question. I guess the follow up question is... should there be? I suspect no, since it might make things quite confusing for our existing user-cases.

My gut says it is better to keep to one consistent interface that end users and the higher-level SDKs work with, rather than having two different interfaces that get mixed together?

Are you referring to the interface that would be added if we did something like I mentioning above?

Though actually, I think useContextMutator wouldn't work correctly today if you pass in a client to <OpenFeatureProvider>, as it won't know the domain and so will always set the default domain context?

I think this is a an issue now, yes. Good point... 😬

Maybe the best way to do this is something more like you proposed. I'll give it more consideration. I was just trying to present some alternative options so that we don't pigeon-hole ourselves too early. ❤️

@MattIPv4
Copy link
Member Author

MattIPv4 commented Dec 8, 2025

I was just trying to present some alternative options so that we don't pigeon-hole ourselves too early. ❤️

Absolutely appreciate other ideas and perspectives on this for sure.

My gut says it is better to keep to one consistent interface that end users and the higher-level SDKs work with, rather than having two different interfaces that get mixed together?

Are you referring to the interface that would be added if we did something like I mentioning above?

No, sorry, referring to only exposing the OpenFeatureAPI class as the interface folks always use, whether it's the global singleton or the isolated singleton, rather than also having to worry about directly using the client interface without the API SDK around it (though, of course, the client interface is something folks use today too, just in a different way from if it were to replace the API SDK, if that makes any sense).

@lukas-reining
Copy link
Member

No, sorry, referring to only exposing the OpenFeatureAPI class as the interface folks always use, whether it's the global singleton or the isolated singleton, rather than also having to worry about directly using the client interface without the API SDK around it (though, of course, the client interface is something folks use today too, just in a different way from if it were to replace the API SDK, if that makes any sense).

For the reasons about static context and what has been discussed I am also leaning towards leaving the access only at the API level @toddbaert.

for example, in react, you could manually supply this client to the

I think this should also be okay using a passed API instance.

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.

3 participants