You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Athena's current IoC model currently only supports DI. Another way to go about it is the service locator pattern, which allows dynamically fetching services based on some key. A good use case for when this could be useful is if you have a service that needs access to several other services when not all of them will be used. This pattern can allow for the lazily instantiation of the only the service(s) you need, which is not possible with DI since they would need to be instantiated and injected as an array/hash or something.
This issue is intended to act as both an RFC, and a way for me to document my thoughts/ideas on how to go about implementing this feature.
Requirements
The services MUST be provided in such as way where they are lazily initialized. The locator type should expose methods to check if it has a service given a key, and the ability to return the related service based on that key. The key can either be the metaclass of the type of a service, or an arbitrary string. Ideally the locator could provide disparate types in a way that does not return a union of all the possible types. It would also be nice to support the @service_name syntax it should be able to provide multiple services based on the same type based on diff keys. Similary, it should support resolving interfaces, but can forgo bindings.
The locator type itself should be auto generated based on either a static set of services it should provide. This set of services should also be able to be "dynamically" generated via some means. Ideally some sort of macro that can yield/expose information from each service with a specific tag; allowing the user to build out the mapping based on the type of each service, or some value exposed on it (class var, annotation value, tag value, etc).
Challenges
Ultimately this feature has some challenges that will need to be figured out in order to implement it. The main issue is since the container is entirely built at compile time. This makes it so the service map(s) need to be known at compile time as well; which makes the "dynamic" mapping harder as well.
The other main challenge in order for things to be made lazy, the container itself needs to be used, which means that the locator types need to be generated/managed internally via the container. It could be exposed as a service itself, but that's probably not necessary.
Implementation
I think the easiest way to go about this is have some macro that generates some type that accepts the container as an ivar. Overloads on the metaclass of each service could be used to handle returning the proper service in a type safe way. String based keys are going to be tricker, probably will need to leverage some sort of overload like def get(key : String, type : T.class) forall T. This of course wouldn't really work if all the services are of different types. Tho will have to see how it works in practice.
Static registration would be easy enough as the macro could be called with a named tuple/hash literal. Might also be able to introduce a new annotation to allow annotating a type directly, using the macro internally, but macro would need to be used if you need the same locator in multiple services.
The "dynamic" registration side of things is where I'm not 100% sure how to handle yet. For a few reasons:
Custom/user provided data in tags isn't really supported as there isn't really a way to access them
Don't really have a good way to access all types with a given tag outside of when the container is being built
Don't have a way to tap into the creation of the container
Concessions
Ultimately crystal-lang/crystal#8835 would solve the vast majority of these challenges. As such, I think the best path forward would be to forgo some of the ideal features in favor of getting something working to support the framework's needs, and implement everything else when there is more flexibility. The primary use case for this feature is the integration of the console component into the framework. I.e. such that only the console command being invoked needs to be instantiated.
Proposed Plan
After typing this all out, I'm thinking the best solution, for now, would be to support the static mapping context, and just generate the mapping for the console component integration manually via iterating over children of the base type. This should probably be tied with maybe introducing an ACONA::AsCommand annotation such that all services will support lazily initialization.
The text was updated successfully, but these errors were encountered:
I did figure out another way to implement this (ab)using a combination of a macro included hook with a finished hook, along with mutable constants to create a sort of "pipeline" of execution that can also invoke user defined code via iterating over and including modules that can interact with the consts.
Service Locators
Athena's current IoC model currently only supports DI. Another way to go about it is the service locator pattern, which allows dynamically fetching services based on some key. A good use case for when this could be useful is if you have a service that needs access to several other services when not all of them will be used. This pattern can allow for the lazily instantiation of the only the service(s) you need, which is not possible with DI since they would need to be instantiated and injected as an array/hash or something.
This issue is intended to act as both an RFC, and a way for me to document my thoughts/ideas on how to go about implementing this feature.
Requirements
The services MUST be provided in such as way where they are lazily initialized. The locator type should expose methods to check if it has a service given a key, and the ability to return the related service based on that key. The key can either be the metaclass of the type of a service, or an arbitrary string. Ideally the locator could provide disparate types in a way that does not return a union of all the possible types. It would also be nice to support the
@service_name
syntax it should be able to provide multiple services based on the same type based on diff keys. Similary, it should support resolving interfaces, but can forgo bindings.The locator type itself should be auto generated based on either a static set of services it should provide. This set of services should also be able to be "dynamically" generated via some means. Ideally some sort of macro that can yield/expose information from each service with a specific tag; allowing the user to build out the mapping based on the type of each service, or some value exposed on it (class var, annotation value, tag value, etc).
Challenges
Ultimately this feature has some challenges that will need to be figured out in order to implement it. The main issue is since the container is entirely built at compile time. This makes it so the service map(s) need to be known at compile time as well; which makes the "dynamic" mapping harder as well.
The other main challenge in order for things to be made lazy, the container itself needs to be used, which means that the locator types need to be generated/managed internally via the container. It could be exposed as a service itself, but that's probably not necessary.
Implementation
I think the easiest way to go about this is have some macro that generates some type that accepts the container as an ivar. Overloads on the metaclass of each service could be used to handle returning the proper service in a type safe way. String based keys are going to be tricker, probably will need to leverage some sort of overload like
def get(key : String, type : T.class) forall T
. This of course wouldn't really work if all the services are of different types. Tho will have to see how it works in practice.Static registration would be easy enough as the macro could be called with a named tuple/hash literal. Might also be able to introduce a new annotation to allow annotating a type directly, using the macro internally, but macro would need to be used if you need the same locator in multiple services.
The "dynamic" registration side of things is where I'm not 100% sure how to handle yet. For a few reasons:
Concessions
Ultimately crystal-lang/crystal#8835 would solve the vast majority of these challenges. As such, I think the best path forward would be to forgo some of the ideal features in favor of getting something working to support the framework's needs, and implement everything else when there is more flexibility. The primary use case for this feature is the integration of the console component into the framework. I.e. such that only the console command being invoked needs to be instantiated.
Proposed Plan
After typing this all out, I'm thinking the best solution, for now, would be to support the static mapping context, and just generate the mapping for the console component integration manually via iterating over children of the base type. This should probably be tied with maybe introducing an
ACONA::AsCommand
annotation such that all services will support lazily initialization.The text was updated successfully, but these errors were encountered: