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

Sha*_*ean 1 c# dependency-injection cqrs simple-injector

我试图将我的应用程序从服务模式重写为命令和查询模式(在我转移到CQRS之前).目前我被困在这个博客上.

它显示了他将工作单元提交从base命令转移到a中的位置PostCommitCommandHandlerDecorator,然后使用Simple Injector将它们绑定起来.作者还指出,并非所有命令都需要使用工作单元,这在我的情况下是正确的,因为不是每个命令都与数据库对话,而是有些发送电子邮件等.

如何构建我的命令和绑定,使得只有那些需要包含在工作单元提交中的命令才会被IoC容器绑定?

Ste*_*ven 8

如何构建我的命令和绑定,使得只有那些需要包含在工作单元提交中的命令才会被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);
Run Code Online (Sandbox Code Playgroud)

对于本文的其余部分,我假设您正在构建一个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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但请注意,您仍然不知道工作单元是否实际使用,但我认为可以安全地假设工作单元在注入时使用.您不希望注入未使用的依赖项.

如果没有删除它,并且您想检查它是否已创建,则必须注入一个代理工作单元,以便您检查它.例如:

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
}
Run Code Online (Sandbox Code Playgroud)

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

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);
Run Code Online (Sandbox Code Playgroud)

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

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

在这种情况下,您实际上要做的是在自己的范围内隔离命令处理程序的执行.好像它们在不同的App域中运行.您希望它们独立于执行它们的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);
Run Code Online (Sandbox Code Playgroud)

混合生活方式是两种(或多种)生活方式的组合,它包含一个谓词代表,容器将调用该代表来检查应该应用哪种生活方式.

只有这种配置才会发生任何事情,因为它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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您应该将此装饰器注册为最后一个装饰器(最外面的装饰器).而不是依赖于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);
Run Code Online (Sandbox Code Playgroud)

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