c# - 用工作单元装饰特定的命令处理程序

标签 c# dependency-injection cqrs simple-injector

我正在尝试将我的应用程序从服务模式重写为命令和查询模式(在移至CQRS之前)。目前,我被困在this blog上。

它显示了他将工作单元提交从基本命令移到了PostCommitCommandHandlerDecorator的位置,然后使用简单注入器将它们绑定在一起。作者还指出,并非所有命令都需要使用工作单元,在我的情况下,这是正确的,因为并非每个命令都与数据库对话,而是某些命令发送电子邮件等。

我如何以这样的方式设计我的命令和绑定,即只有那些需要包装在工作单元提交中的命令才能被IoC容器绑定?

最佳答案

我如何以这样一种方式构造我的命令和绑定,即只有那些需要包装在工作单元落实中的命令才能被IoC容器绑定?


首先,不是所有处理程序都使用工作单元真的重要吗?创建工作单元而不使用它时是否有问题?因为当没有性能问题时,不需要使您的代码更复杂。

但是,让我们假设它确实很重要。在这种情况下,技巧是查询容器是否将工作单元注入到某个地方。您可以使用Lazy<T>使其正常工作。看一下以下注册:

Func<IUnitOfWork> uowFactory = 
    () => new MyUnitOfWork(connectionString);

// Register the factory as Lazy<IUnitOfWork>
container.Register<Lazy<IUnitOfWork>>(
    () => new Lazy<IUnitOfWork>(uowFactory), 
    Lifestyle.Scoped);

// Create a registration that redirects to Lazy<IUnitOfWork>
container.Register<IUnitOfWork>(
    () => container.GetInstance<Lazy<IUnitOfWork>>().Value, 
    Lifestyle.Scoped);


对于本文的其余部分,我假定您正在构建Web应用程序,但是想法是相同的。

通过此注册,当容器使用依赖于IUnitOfWork的组件解析对象图时,在幕后它将解析Lazy<IUnitOfWork>并获取其值。我们为每个请求缓存Lazy<IUnitOfWork>,因此这使我们可以拥有另一个依赖于Lazy<IUnitOfWork>的组件,并检查其IsValueCreated属性以查看是否将IUnitOfWork注入到任何地方。

现在,您的装饰器可能如下所示:

public class TransactionCommandHandlerDecorator<TCommand>
    : ICommandHandler<TCommand>
{
    private readonly ICommandHandler<TCommand> decorated;
    private readonly Lazy<IUnitOfWork> lazyUnitOfWork;

    public TransactionCommandHandlerDecorator(
        ICommandHandler<TCommand> decorated,
        Lazy<IUnitOfWork> lazyUnitOfWork)
    {
        this.decorated = decorated;
        this.lazyUnitOfWork = lazyUnitOfWork;
    }

    public void Handle(TCommand command)
    {
        this.decorated.Handle(command);

        if (this.lazyUnitOfWork.IsValueCreated)
        {
            this.lazyUnitOfWork.Value.SubmitChanges();
        }
    }
}


不过请注意,您仍然不知道工作单元是否已实际使用,但是我认为可以肯定的是,当注入工作单元时,将使用工作单元。您不想注入未使用的依赖项。

如果这样不能解决问题,并且您想检查它是否已创建,则必须注入一个代理工作单元,以便您进行检查。例如:

public class DelayedUnitOfWorkProxy : IUnitOfWork
{
    private Lazy<IUnitOfWork> uow;

    public DelayedUnitOfWorkProxy(Lazy<IUnitOfWork> uow)
    {
        this.uow = uow;
    }

    void IUnitOfwork.SubmitChanges()
    {
        this.uow.Value.SubmitChanges();
    }

    // TODO: Implement All other IUnitOfWork methods
}


您的配置现在将如下所示:

Func<IUnitOfWork> uowFactory = 
    () => new MyUnitOfWork(connectionString);

// Register the factory as Lazy<IUnitOfWork>
container.Register<Lazy<IUnitOfWork>>(
    () => new Lazy<IUnitOfWork>(uowFactory), 
    Lifestyle.Scoped);

// Register the proxy that delays the creation of the UoW
container.Register<IUnitOfWork, DelayedUnitOfWorkProxy>(
    Lifestyle.Scoped);


当命令或任何其他依赖项需要IUnitOfWork时,他们将获得DelayedUnitOfWorkProxy,并注入Lazy<IUnitOfWork>。因此,在创建对象图之后,将不会再创建工作单元本身。仅当调用DelayedUnitOfWorkProxy方法之一时,才会创建此类实例。装饰器将保持不变。

但是,即使这可能还不够好。您的MVC控制器(假设您正在构建ASP.NET MVC应用程序)可能取决于使用工作单元的查询,而命令处理程序则没有。在那种情况下,您可能仍然不想提交工作单元,因为命令处理程序(或其依赖项之一)仍然不使用工作单元。

在这种情况下,您实际上要尝试的是在自己的范围内隔离命令处理程序的执行。就像它们在其他App Domain中运行一样。您希望它们独立于执行它们的Web请求。

在这种情况下,您需要混合生活方式。使用Simple Injector,您可以保持所有代码和配置不变,但可以切换到以下混合生活方式:

container.Options.DefaultScopedLifestyle = Lifestyle.CreateHybrid(
    () => container.GetCurrentLifetimeScope() != null,
    new LifetimeScopeLifestyle(),
    new WebRequestLifestyle());

Func<IUnitOfWork> uowFactory = 
    () => new MyUnitOfWork(connectionString);

// Register the factory as Lazy<IUnitOfWork>
container.Register<Lazy<IUnitOfWork>>(
    () => new Lazy<IUnitOfWork>(uowFactory), 
    Lifestyle.Scoped);

// Register a proxy that depends on Lazy<IUnitOfWork>    
container.Register<IUnitOfWork, DelayedUnitOfWorkProxy>(
    Lifestyle.Scoped);


混合生活方式是两种(或多种)生活方式的组合,并且包含一个谓词委托,容器将调用该谓词以检查应采用哪种生活方式。

仅使用这种配置就不会发生任何事情,因为LifetimeScopeLifestyle要求您显式启动和停止新作用域。如果没有范围,container.GetCurrentLifetimeScope()方法将始终返回null,这意味着混合生活方式将始终选择WebRequestLifestyle。

您需要的是在解析新的命令处理程序之前启动新的生存期作用域。与往常一样,这可以通过定义装饰器来完成:

private sealed class LifetimeScopeCommandHandlerDecorator<T>
    : ICommandHandler<T>
{
    private readonly Container container;
    private readonly Func<ICommandHandler<T>> decorateeFactory;

    public LifetimeScopeCommandHandlerDecorator(Container container,
        Func<ICommandHandler<T>> decorateeFactory)
    {
        this.decorateeFactory = decorateeFactory;
        this.container = container;
    }

    public void Handle(T command)
    {
        using (this.container.BeginLifetimeScope())
        {
            var decoratee = this.decorateeFactory.Invoke();
            decoratee.Handle(command);
        }
    }
}


您应该将此装饰器注册为最后一个装饰器(最外面的装饰器)。装饰器不是依赖ICommandHandler<T>,而是依赖Func<ICommandHandler<T>>。这样可以确保仅在调用Func<T>委托时解析修饰的命令处理程序。这将延迟创建,并允许首先创建生存期范围。

由于此装饰器依赖于两个单例(容器和Func<T>均为单例),因此装饰器本身也可以注册为单例。这是您的配置如下所示:

// Batch register all command handlers
container.Register(
    typeof(ICommandHandler<>), 
    typeof(ICommandHandler<>).Assembly);

// Register one or more decorators
container.RegisterDecorator(
    typeof(ICommandHandler<>), 
    typeof(TransactionCommandHandlerDecorator<>));

// The the lifetime scope decorator last (as singleton).
container.RegisterDecorator(
    typeof(ICommandHandler<>), 
    typeof(LifetimeScopeCommandHandlerDecorator<>),
    Lifestyle.Singleton);


这将有效地将命令使用的工作单元与在其余请求中在命令处理程序的上下文外部创建的任何工作单元隔离开。

关于c# - 用工作单元装饰特定的命令处理程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18803979/

相关文章:

c++ - 依赖倒置和普遍依赖

c# - 通用异步查询类

c# - 可以使用 PayFlow Pro 创建非经常性配置文件吗

c# - Unity 内部编译器错误与自定义 dll

python - 为什么要使用组合?

java - 从子上下文引用在父上下文中创建的 Spring Singletons

java - 是否有 Java 端口或 NEventStore 库的等效项?

domain-driven-design - CQRS 和同步操作(如用户注册)

c# - 在 MVC 3 的多个位置搜索 .cshtml?

c# - 日历项目更新时发生 Exchange 错误 :- At least one recipient isn't valid