首页 > 解决方案 > SimpleInjector 容器验证和 Akka.net

问题描述

我目前正在使用 SimpleInjector 和 Akka.net 来解决某些参与者可能需要来自容器的依赖项。它使用Akka.DI.SimpleInjector nuget 包运行良好。

现在我的问题是,需要解决一些依赖关系的演员必须在容器中注册。在那方面一切都很好。

问题是,当您调用container.Verify();验证过程尝试创建已注册的参与者时,这通常不应该在ActorSystem. 因此,当容器现在执行此操作时,我会收到一条InvalidOperationException类似消息There is no active ActorContext...和 StackTrace,表明它试图调用 actor 构造函数。

任何人有任何想法如何解决这个问题?

编辑:是一个突出问题的示例。需要 Akka.net 和 SimpleInjector NuGet。

编辑 2:更新了 gist 示例以拥有一个完整的工作程序,其中通过 DI 创建和使用演员。验证的问题仍然存在。

标签: simple-injectorakka.net

解决方案


编辑

我在查看您提供的示例以突出您的问题时写了这篇文章。这可能会误导我的误解,我将把我原来的回复留在这里,因为它可能会帮助一些人。

TL;DR:您可以注册工厂而不是参与者,并且仍然有一种很好的方式来表达特定参与者的依赖关系,您可以使用委托

原来的

我认为您遇到的问题与对两个概念的误解有关。
让我问你两个问题:

  1. 我们如何注册必须以特定方式构造的对象?
  2. Akka.net 中的演员是什么,我们如何与之互动?

为了回答第一个问题,我们ActorSystem以 为例。ActorSystem 必须通过静态方法创建ActorSystem.Create
我们如何告诉容器如何构造 ActorSystem 的实例?
答案是使用Factory

我们必须注册一个知道如何构造 ActorSystem 的工厂。工厂的唯一职责是提供一个正确且完整构造的 ActorSystem 实例。
工厂可以是一个对象,但也可以是一个函数(至少对于 SimpleInjector)。

要完全回答第一个问题,我们还必须问自己对象的生命周期是什么。会是单例吗?每次检索依赖项时都可以构造它吗?
这完全取决于依赖类型。

如果我们继续以 ActorSystem 为例,我们知道它必须是一个单例(好吧,这并不完全正确,但强烈建议您的应用程序中只有一个 AytorSystem 并将其视为单例)。

使用 SimpleInjector,我们可以像这样实现上述目标:

var container = new Container();
container.RegisterSingleton<ActorSystem>(() => ActorSystem.Create("MyActortSystem"));

如果我们将工厂的用法应用于您最初的问题,我们最终会得到这样的结果:

var container = new Container();
container.RegisterSingleton<MyActor>(() => Props.Create<MyActor>(() => new MyActor()));

上面的代码无法编译,因为我们仍然必须(暂时手动)提供对MyActor构造函数的依赖。
但是上面的代码还有第二个更重要的问题,它违反了 Akka.net 的一个基本原则:actor 只能通过 IActorRef 访问,而不能直接访问

上面的代码用它的类型注册了actor,然后消费者可以通过. 区别是至关重要的,因为 Akka.net 使用它来实现actor 透明度并防止滥用 actor。 如果只有 IActorRef,则只能通过and与 actor 进行通信,不能调用 MyActor 类中定义的公共方法。这有助于防止人们从另一个线程或从参与者系统外部更改/访问/操作参与者的状态。 这对于 Akka.net 或基于 Actor 的实现至关重要。MyActorIActorRef

AskTell


我可能会坚持,但尊重演员只能通过 IActorRef 访问这一事实非常重要,因为这让 Akka.net 信守了关于演员模型要求的承诺。
不尊重这一点可能会导致参与者系统试图解决的问题:线程问题。

话虽如此,我们能做些什么来解决这个问题呢?
我们知道我们可以在容器中注册一个工厂来为我们提供一个必须以特定方式构造的对象的实例。
我们不能注册返回MyActor引用的工厂,因为这会违反 akka.net 的要求。为了满足这些要求,演员应该通过IActorRef.
我们可以注册一个返回 IActorRef 的工厂,但这只有在我们只注册一个参与者时才有效。我们不能注册 2 个不同的工厂,每个工厂都返回一个 IActorRef(不同的参与者)。
消费者如何要求引用一个演员而不是另一个演员?我们不能说“给我一个 IActorRef 依赖项,而只给我第二个注册工厂生产的那个”。
这个问题的答案再次是工厂。我们必须注册一个工厂,它知道如何构造我们的actor并返回它。消费者应该依赖工厂而不是MyActor

必须为每个参与者创建一个可以用作依赖项的特定工厂有点麻烦。希望有些人找到了一种简单的方法来做到这一点。我们可以使用委托作为对工厂建模的一种方式。

委托对命名的工厂函数进行建模。然后,消费者将能够使用相应的委托来表达对参与者的依赖。
在向您展示它是如何完成的之前,让我们来看看它从消费者看来是怎样的:

public class Consumer
{
    private IActorRef _myActor;
    public Consumer(MyActor.ReceiveActorProvider myActorFactory)
    {
        _myActor = myActorFactory();
        _myActor.Tell(new SomeMessage());
    }
}

消费者有一个依赖MyActor.ReceiveActorProvider,它是我们的代表,我们的工厂。因为它是一个工厂,这也意味着它不是我们的演员,我们必须向工厂询问我们的演员,这就是委托的调用所做的事情。在这样做时,我们最终得到了一个IActorRef可以安全使用的产品。

委托的声明相当简单:

public class MyActor : ReceiveActor
{
    public delegate IActorRef ReceiveActorProvider();
    ...        
}

这是注册/实施(我将引导您完成):

container.RegisterSingleton<MyActor.ReceiveActorProvider>(() =>
{
    var actorSys = container.GetInstance<ActorSystem>();
    var myActor = actorSys.ActorOf<MyActor>(Props.Create<MyActor>(() => new MyActor()));
    return () => myActor;
});

actorSys.ActorOf首先,我们从容器中检索对 ActorSystem 的引用,然后通过使用 Props 的常规方法创建MyActor 的新实例。棘手
的部分是回报。请记住,我们正在注册一个工厂,或者换句话说,“调用时返回 IActorRef 的东西”,我们不能直接返回,因为这与委托的签名不对应。相反,我们返回这是一种时髦的方式,即我们返回一个“返回 myActor 的函数”。 (你们当中更有洞察力的人可能已经看到,我们实际上是在注册一个单例的“工厂工厂”)。myActor
() => myActor

我们终于有了一种方法让消费者询问特定的演员,并给他们一个IActorRef与他们所要求的演员相对应的人。这照顾了演员系统的要求。

仍然有一个问题,它仍然不起作用,MyActor 需要一个仍未解决的依赖项......
这个问题有两个答案:

  1. 只要我们正在构建我们的组合根,我们就可以直接container在注册函数中使用我们的函数。
  2. 第二种方法是使用 Akka.DI.Core,这是一个为 Akka.net 添加对依赖注入的支持的库。该库仅添加支持,不提供对容器的支持。因此,在我们的例子中,我们还必须使用Akka.DI.SimpleInjector 库

由于这已经很长了,我只会向您展示如何做第二种方式:

var container = new Container();
var actorSystem = ActorSystem.Create("MyActorSystem");
var resolver = new SimpleInjectorDependencyResolver(container, actorSystem);

container.RegisterInstance(actorSystem);
container.Register<IMyDependency, MyDependency>();
container.RegisterSingleton<MyActor.ReceiveActorProvider>(() =>
{
    var actorSys = container.GetInstance<ActorSystem>();
    var myActor = actorSys.ActorOf(resolver.Create<MyActor>());
    return () => myActor;
});

这里我们创建一个SimpleInjectorDependencyResolver并在actor创建序列中使用。当在actorSys.ActorOf(resolver.Create<MyActor>()).
在幕后,这个对象将使用 Akka.DI.Core 来创建ActorOf方法所需的 Props。当必须解决依赖关系时,Akka.DI.Core 也会使用它(Akka.DI.Core 只是一个抽象)。

最后的话

为了简单起见,我采取了一些捷径(我将代理放在 Actor 类中,这并不理想,因为它会在尝试表达依赖关系时将消费者引导到 Actor 类,从而可能误导他们直接使用 Actor 类)。
我简单地触及了生命周期的主题,并将几乎所有东西都注册为单例。这是不正确的,您应该问自己每个依赖项的生命周期是什么,并相应地注册它们。
我不自称是专家,我在这里写的内容是基于我对 IoC、依赖注入和 Akka.net 的理解,所以,我可能是错的(在这种情况下,请告诉我,以便我可以提高我的理解)。


推荐阅读