-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Selector provider #21
base: master
Are you sure you want to change the base?
Conversation
I'm not a fan of the concept that a service instantiation should incur in a DB call. We use services extensively, they're not properly cached, and we usually call it from wherever we need them, rather than passing them around, sounds like a recipe for unnecessary and uncontrolled DB queries. Also, if I understand correctly, the feature flag selector (and most of the usages I can think of, given that user/account is the only variable parameter I can think of in the context of instantiating a service) would work only if |
Wait. I'm not suggesting to make db queries (we could, but is not the goal). This it not meant to be used every time we evaluate a feature flag either. There are different ways of forking behavior depending on feature flags and it depends mostly on the type of service. A good reference for this is here, where it talks about Toggles at the edgeThis refers to use case services. You can decide to use one use case implementation or another depending on a feature flag, and in this case you will probably do it based on the current user. In this scenario the use case service definition can benefit from the selector service and we can avoid flow controls at the resolver/view/controller level. Toggles in the coreThis kind of toggle doesn't necessarily depend on a user. So applying the forking at the injection level is not the best approach. Here we can have a factory or builder to abstract the knowledge of the feature flag handler or simply ask for one or another depending on the feature flag value. We do have to get better are differentiating use case services, and other domain services. This particular feature helps with the former. |
@Lacrymology I agree about avoiding "a service instantiation should incur in a DB call" and it's true that we "usually call it from wherever we need them". But this implementation is still quite generic, given a
require("service", selector=user)
@requires("service", alias="repo", selector-param="user"):
def foo(user, repo=None):
pass |
I thought about putting this in There's one thing that I haven't told you, which is the plan to start using feature flags as a service, thus getting rid of Still there's the ability to instantiate services based on other conditions, like env vars or whatever is needed. But in general you want the same service to be provided the same way always, so having this as a provider accomplishes that goal. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sebastiandev yeah i see your point about "the same service could be retrieved in multiple places with and without the selector" which is not ideal. I think eager loading feature flags, old and new would make a lot of sense. Old feature flags is just 1 row, and new ones can't be more than 100 right? Probably way less, so fetching all toggles when a user authenticates and updating current-user
to have them set, would make these concerns go away.
Question: I was wondering the case where we have the option to either run a service or not, rather than selecting a service among two or more options. Would this case be covered by this? Would we need to provide a no-operation service in the |
Selector provider allows to instantiate services based on conditions like
Usually when using a selector provider you need to define
The selector will be called with the key and it should return an option. Different services should be mapped to each option.
A typical use case is instantiating services based on feature flags. The
key
will be the feature flag and the selector will return the option for that flag depending on the context (current user, request params, etc). We can then assign different services to the different values for that feature flag.The trivial case is a on/off feature flag, implemented as a
boolean selector
here, meaning we can map theon
andoff
state with different services.Boolean Selector Example
If the feature_flag
new-service-enabled
is True,service.v2
will be used, otherwiseservice.v1
will be used.Multivariate Selector Example
The feature_flag
new-use-case
is a multivariate flag. We have defined 3 possible optionsv1, v2, v3
each assigned to different services. If the selector ends up returning a new or different option the default service will be used.