1

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

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

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

4

2 回答 2

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任何地方注入的组件。

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

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需要. 所以在创建对象图之后,工作单元本身还不会被创建。只有当其中一个方法被调用时,才会创建这样的实例。装饰器将保持不变。DelayedUnitOfWorkProxyLazy<IUnitOfWork>DelayedUnitOfWorkProxy

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

在这种情况下,您实际上要做的是将命令处理程序的执行隔离在它们自己的范围内。就好像它们在不同的应用程序域中运行一样。您希望它们独立于它们执行的 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);

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

于 2013-09-14T17:22:38.860 回答
1

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

这是代码中的示例:

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到命令处理程序的注册,那些用IDontUseUnitOfWork接口标记的处理程序除外:

container.RegisterDecorator(
    typeof(ICommandHandler<>), 
    typeof(UnitOfWorkCommandDecorator<>),
    x => !typeof(IDontUseUnitOfWork).IsAssignableFrom(x.ImplementationType));

这个谓词功能非常有用,非常值得掌握。

于 2013-09-16T10:30:02.013 回答