-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Redesign the Dependency Injection API #1675
Comments
@Horusiath the |
So I like idea number 1 because it makes DI more of an infrastructure concern instead of having it be in your face all the time.The concept of selective injections is great. Its something you will want to use any time your passing an IActorRef and DI injected services in an actors constructor. About the scoping of dependencies. In my mind thats easy. You either have it scoped to the lifecycle of the actor its being injected in (which means its disposed/recreated on actor restart/creation). Or its singleton scoped. |
Also, we need to think about how this fits with remote deployment. |
@rogeralsing My guess would be that dependencies should be resolved on the target side, since For this reason I think, that dependency resolution should not work on |
I agree with @Horusiath the |
While we're at it, a container like SimpleInjector has a few different ways to do things. One of the things it does is verify all dependencies, so it knows at startup, that it can inject everything. A fail fast philosophy. This means that you cannot register Actors to verify their dependencies, as it fails when trying to create a new one. It would be nice for this to work one way or another, perhaps by a setting in the adaptor. |
We're badly in need of selective injections: With Autofac, for example, it is very easy to mix both. I think we need that functionality in A very simple case (without adding more "abstractions") would be providing additional parameters to the method, allowing this:
param1 and param2 would be matched by type. For matching by name we would need an abstraction like NamedParameter. I understand that we can do this now by explicitly resolving services from DI and using |
Ok, so the more general design could look like this: instead of having actor producers and extension providers, we could create a single dependency resolver set as a field of
Given this single abstraction, we could actually use it for variety of options:
I've also though about how to allow users to partially define constructor params, such as Of course this method is a heavier that standard I'll try to present some PR for that. The biggest problem I see here is current actor creation pipeline, which is way more complex that is should be. |
@Horusiath any progress on scoped DI? Do you know what is the beast way to implement similar behaviour in current Akka implementation? |
@arkadiuszwojcik the PR for this is essentially prepared (see #3057) - it already worked at the time, when it was send. I'm hoping to merge it for the v1.4 release. The last missing piece was the way of registering types in container - since Akka.NET was purely dependent on HOCON for the configuration, and no suitable method for user code was prepared. This will be partially solved by @Aaronontheweb and his PR to split up HOCON from Akka - from there we'll be able to abstract the way of how and when configuration happens. |
If anybody's interested, I've just done a very compact Proof-of-Concept of what you probably call "partial injection":
(and I've got this to work with Autofac) The one major limitation is that parameter types have to be different, but in type-heavy environment such as Akka this is not an issue anyway. While this is not a very "generic" way to do things, it might be implemented as a non-breaking change in (I can move this to separate issue if that's a preferable way) |
Selective injections are definitely the biggest sticking point for us at the moment. It really limits us when we have to choose between every constructor parameter coming from DI, or from the actor's parent. Having something like |
I would like to share my experience with Akka.NET and dependency injection. I have an ASP.NET Core application with Microsoft DI container and I don't use any of Akka.NET DI. Instead I created a simple immutable classes which encapsulate dependencies for each actor type(including child actors) and resolve/pass them explicitly. For example: public class RequestHandlerDependencies
{
public IApplicationMetrics Metrics { get; }
public ICanTell AuthorizationHandler { get; }
public ConnectionHandlerDependencies ConnectionHandlerDependencies { get; }
public MessageHandlerDependencies MessageHandlerDependencies { get; }
public RequestHandlerDependencies(
IApplicationMetrics metrics,
ConnectionHandlerDependencies connectionHandlerDependencies,
MessageHandlerDependencies messageHandlerDependencies,
ICanTell authorizationHandler)
{
Metrics = metrics;
AuthorizationHandler = authorizationHandler;
ConnectionHandlerDependencies = connectionHandlerDependencies;
MessageHandlerDependencies = messageHandlerDependencies;
}
} Then I register them on .AddSingletonActorProvider()
.AddMetrics(MetricsConfiguration.Configuration)
.AddSingleton<ConnectionHandlerDependencies>()
.AddSingleton<MessageHandlerDependencies>()
.AddSingleton(provider =>
{
var actorProvider = provider.GetRequiredService<SingletonActorProvider>();
var authorizationHandler = actorProvider.SingletonOf(
AuthorizationHandler.Props(/* get dependencies from provider, configuration etc. */),
"authorization");
return new RequestHandlerDependencies(
provider.GetRequiredService<IApplicationMetrics>(),
authorizationHandler,
provider.GetRequiredService<ConnectionHandlerDependencies>(),
provider.GetRequiredService<MessageHandlerDependencies>()
);
}) Usage: app.MapWhen(ctx => ctx.WebSockets.IsWebSocketRequest, ws =>
{
ws.Run(async context =>
{
var actorSystem = context.RequestServices.GetRequiredService<ActorSystem>();
var requestHandler = actorSystem.ActorOf(
RequestHandler.Props(
context.RequestServices.GetRequiredService<RequestHandlerDependencies>(),
context.Request),
$"request-{context.Request.HttpContext.TraceIdentifier}");
});
}); |
However I haven't yet figured out a way to reuse Akka actors(and configuration) between actor systems. Let's say I want to reuse In non-Akka world I would do it using dependency injection by creating public static IServiceCollection AddAuthorizationHandler(
this IServiceCollection services,
/* configuration objects/delegates */)
{
services
// configure dependencies
.AddSingleton(provider =>
{
// resolve dependencies
// create and apply configuration
return new AuthorizationHandler(/* dependencies, configuration */);
});
} For Akka actors it's hard to do this, because public interface IActorProvider<TActor>
{
IActorRef GetActor();
} Then it should be possible to implement this for the required DI scopes(singleton, transient etc.) and register them in DI, allowing consumers to resolve and use them to create actor instances. There is no way to provide extra parameters during actor resolving here, but I don't find it particularly useful anyway, because from my experience that's rarely needed. |
closed via #4590 |
Right now our DI API is not sufficient to fluently operate with actors. The main issues here are:
Props.DI().Create<InjectedActor>()
is probably not the nicest way to abstract the fact, that we're working with some kind of DI container. This should be transparent.Some ideas here are:
Ad.1) Abstract DI container inside dedicated
ActorProducer
s, which will be aware of their role without explicit notifying usingProps.DI()
.Ad.2) Allow to make a selective injections - I've actually have an idea here to create a feature inside
Akka.DI.Core
(which is shared by all DI plugins) i.e.:Context.Parent
is example of pinning existing value to a constructor. Here anInject
method could work two ways:null
/default(T)
. DI-aware actor producer would recognize this as a point, where container should resolve a dependency.Before somebody mention it: I think, that part we shouldn't speak about scoping of actors themselves - akka has it's own lifecycle rules and none of DI libraries would fit into that.
The text was updated successfully, but these errors were encountered: