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

时间:2013-09-14 16:39:20

标签: c# dependency-injection cqrs simple-injector

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

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

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

2 个答案:

答案 0 :(得分: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);

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

通过此注册,当容器使用取决于IUnitOfWork的组件解析对象图时,它将解析Lazy<IUnitOfWork>并获取其值。我们会为每个请求缓存Lazy<IUnitOfWork>,因此我们可以使用另一个依赖Lazy<IUnitOfWork>的组件并检查其IsValueCreated属性,以查看IUnitOfWork是否被注入任何位置。< / p>

现在你的装饰师看起来像这样:

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域中运行。您希望它们独立于执行它们的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);

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

答案 1 :(得分:1)

有一种简单的方法可以实现您的要求。 RegisterDecorator扩展方法的重载版本接受Predicate,与marker interface结合使用,可用于有选择地应用装饰器。

以下是代码中的示例:

public interface ICommandHandler<T> where T : class { }
public interface IDontUseUnitOfWork { }

public class MyCommand { }

public class MyCommandHandler : 
    ICommandHandler<MyCommand>, IDontUseUnitOfWork { }

public sealed class UnitOfWorkCommandDecorator<T> :
    ICommandHandler<T> where T : class
{
    public UnitOfWorkCommandDecorator(ICommandHandler<T> decorated) { }
}

注册以将UnitOfWorkCommandDecorator应用于命令处理程序除了<{1}}接口标记的那些:

IDontUseUnitOfWork

这个谓词功能非常有用,非常值得一试。