为一个通用的CommandHandler使用structuremap为每个模块注册不同的UnitOfWorks

本文关键字:模块 注册 UnitOfWorks structuremap 使用 一个 CommandHandler | 更新日期: 2023-09-27 18:09:59

我在最近的项目中使用CQRS模式,并在我的DAL中首先使用EF代码,因此我定义了一些通用的CommandHandlers来执行插入/更新/删除:

public class InsertCommandHandler<TEntity> : ICommandHandler<InsertCommandParameter<TEntity>>
    where TEntity : BaseEntity, IAggregateRoot<TEntity>, new()
{
    private readonly IUnitOfWork _uow;
    public InsertCommandHandler(IUnitOfWork uow)
    {
        _uow = uow;
    }
    public void Handle(InsertCommandParameter<TEntity> parameter)
    {
        var entity = parameter.Entity;
        _uow.Repository<TEntity>().Add(entity);
    }
}
public interface ICommandParameter
{
}
public abstract class  BaseEntityCommandParameter<T> : ICommandParameter
    where T :  BaseEntity, new()
{
    public T Entity { get; set; }
    protected BaseEntityCommandParameter()
    {
        Entity = new T();
    }
}
public  class InsertCommandParameter<T> : BaseEntityCommandParameter<T> where T :  class, new()
{
}

如您所见,我将IUnitOfWork注入到InsertCommandHandler构造函数中。

public interface IUnitOfWork : IDisposable
{
    IRepository<T> Repository<T>() where T : BaseEntity, IAggregateRoot<T>,new ();
    void Commit();
}

我使用Structuremap 3作为我的IoC Container,所以我定义了以下转换来解决每个BaseEntity类型的ICommandHandler(使用部分封闭类型的自定义注册约定):

public class CRUDCommandRegistrationConvention : StructureMap.Graph.IRegistrationConvention
{
    private static readonly
    Type _openHandlerInterfaceType = typeof(ICommandHandler<>);
    private static readonly
    Type _openInsertCommandType = typeof(InsertCommandParameter<>);
    private static readonly
    Type _openInsertCommandHandlerType = typeof(InsertCommandHandler<>);
    private static readonly
    Type _openUpdateCommandType = typeof(UpdateCommandParameter<>);
    private static readonly
    Type _openUpdateCommandHandlerType = typeof(UpdateCommandHandler<>);
    private static readonly
    Type _openDeleteCommandType = typeof(DeleteCommandParameter<>);
    private static readonly
    Type _openDeleteCommandHandlerType = typeof(DeleteCommandHandler<>);

    public void Process(Type type, Registry registry)
    {
        if (!type.IsAbstract && typeof(BaseEntity).IsAssignableFrom(type))
            if (type.GetInterfaces()
                .Any(x => x.IsGenericType && x.GetGenericTypeDefinition() 
                    == typeof(IAggregateRoot<>)))
            {
                Type closedInsertCommandType = _openInsertCommandType.MakeGenericType(type);
                Type closedInsertCommandHandlerType = _openInsertCommandHandlerType.MakeGenericType(type);
                Type closedUpdateCommandType = _openUpdateCommandType.MakeGenericType(type);
                Type closedUpdateCommandHandlerType = _openUpdateCommandHandlerType.MakeGenericType(type);
                Type closedDeleteCommandType = _openDeleteCommandType.MakeGenericType(type);
                Type closedDeleteCommandHandlerType = _openDeleteCommandHandlerType.MakeGenericType(type);
                Type insertclosedHandlerInterfaceType = _openHandlerInterfaceType.MakeGenericType(closedInsertCommandType);
                Type updateclosedHandlerInterfaceType = _openHandlerInterfaceType.MakeGenericType(closedUpdateCommandType);
                Type deleteclosedHandlerInterfaceType = _openHandlerInterfaceType.MakeGenericType(closedDeleteCommandType);
                registry.For(insertclosedHandlerInterfaceType).Use(closedInsertCommandHandlerType);
                    registry.For(updateclosedHandlerInterfaceType).Use(closedUpdateCommandHandlerType);
                    registry.For(deleteclosedHandlerInterfaceType).Use(closedDeleteCommandHandlerType);
            }
    }
}

用在我的CompositionRoot:

    public static class ApplicationConfiguration
{
    public static IContainer Initialize()
    {
        ObjectFactory.Initialize(x =>
        {
            x.Scan(s =>
            {
                s.AssemblyContainingType(typeof(ICommandHandler<>));
                s.AssemblyContainingType(typeof(Order));
                s.AssemblyContainingType(typeof(FindOrderByIdQueryHandler));
                s.WithDefaultConventions();
                x.For(typeof(IUnitOfWork))
                    .Use(typeof(EfUnitOfWork<SaleDBContext>))
                    .Named("SaleDBContext")
                    .SetLifecycleTo((Lifecycles.Singleton));
                s.Convention<CRUDCommandRegistrationConvention>();
            });
        });
        return ObjectFactory.Container;
    }
    public static T Resolve<T>()
    {
        return ObjectFactory.GetInstance<T>();
    }
}

我为IUnitOfWork注册了EfUnitOfWork<SaleDBContext>,但我想在我的解决方案(Bounded context)中使用每个模块单独的DbContext。例如,我的销售模块有自己的DbContext,人力资源模块有自己的DbContext等,以上注册转换,只注册EfUnitOfWork<SaleDBContext>作为我的IUnitOfWork

我在我的解决方案中有一些模块(Visual Studio中的解决方案文件夹),每个模块有3层(3类库项目):我的模块有以下结构(每个模块有3个组件),例如:

 SaleModule: 
 ----Application 
 ----Domain (Entities , ...) //Order, Customer,...
 ----DAL (DbContext ,...) //SaleDbContext
 HRModule: 
 ----Application 
 ----Domain (Entities , ...) // Employee, OrganizationUnit, ...
 ----DAL (DbContext ,...)//HRDbContext
 InfrastructureModule: 
 ----Application (ICommandHandler,IQueryHandler,...)
 ----Domain 
 ----DAL 

InsertCommandHandler<T>放入基础设施模块

当我使用InsertCommanHandler<T>时,我希望它使用相应模块的DbContext作为IUnitOfWork。例如,我希望InsertCommandHandler<Order>使用SaleDbContext作为IUnitOfWork, InsertCommandHandler<Employee>使用HRDbContext作为IUnitOfWork

( ]

这是一个消费者代码的示例,IoC容器应该为Consumer1提供SaleDbContext,为Consumer2提供HRDbContext:

public class Consumer1
{
    ICommandHandler<InsertCommandParameter<Order>> _insertCommandHandler;
    public Consumer1(ICommandHandler<InsertCommandParameter<Order>> insertCommandHandler)
    {
       _insertCommandHandler = insertCommandHandler;
    }
    public void DoInsert()
    {
        var command = new InsertCommandParameter<Order>();
        command.Entity = new Order(){
                                       Number = 'ord-01',
                                       // other properties
                                    };
        insertCommandHandler.Handle(command); //this query handler should use SaleDbContext
    }
}
public class Consumer2
{
    ICommandHandler<InsertCommandParameter<Employee>> _insertCommandHandler;
    public Consumer2(ICommandHandler<InsertCommandParameter<Employee>> insertCommandHandler)
    {
       _insertCommandHandler = insertCommandHandler;
    }
    public void DoInsert()
    {
        var command = new InsertCommandParameter<Employee>();
        command.Entity = new Employee(){
                                       EmployeeNumber = 'Emp1',
                                       // other properties
                                    };
        insertCommandHandler.Handle(command); //this query handler should use HRDbContext
    }
}

我怎么能做到这一点在我的合成根使用StructureMap

为一个通用的CommandHandler使用structuremap为每个模块注册不同的UnitOfWorks

您可以使IUnitOfWorkIUnitOfWork<TConnection>一样通用。这允许每个Repository规定它需要哪个UnitOfWork,理想情况下使用构造函数注入,例如

public class InsertCommandHandler : ICommandHandler<Order>
{
    public InsertCommandHandler(IUnitOfWork<SalesDbContext> salesUnitOfWork)
    {
        // ...
    }
}

然而,你可能不想在每个处理程序中引用DbContext,所以你应该定义一个抽象来避免这种依赖。

从一个简单的接口开始,所有DbContext包装器类都将实现

public interface IConnection
{
    DbContext Context { get; }
}

相应更新IUnitOfWork

public interface IUnitOfWork<TConnection> where TConnection : IConnection { }

下面是一个包装器示例

public class SalesConnection : IConnection
{
    private readonly DbContext context;
    public SalesConnection()
    {
        this.context = new SalesDbContext();
    }
    public DbContext Context { get { return this.context; } }
}

下面是更新后的命令处理程序

public class InsertCommandHandler : ICommandHandler<Order>
{
    public InsertCommandHandler(IUnitOfWork<SalesConnection> salesUnitOfWork)
    {
        // ...
    }
}

对于公共处理程序,逻辑上要做的事情是每个逻辑域(即每个DbContext)有一个,例如SalesInsertCommandHandler, HRInsertCommandHandler

public class SalesInsertCommandHandler<TCommand> : ICommandHandler<TCommand>
{
    public SalesInsertCommandHandler(IUnitOfWork<SalesConnection> unitOfWork)
    {
    }
}

这遵循了关注点分离原则,当你想用不同的方面(跟踪、重试逻辑等)修饰你的关注点时,它给了你额外的灵活性

所有的命令处理程序当然可以从一个通用的(抽象的)命令处理程序继承。

public abstract class CommandHandler<TConnection, TCommand> :
     ICommandHandler<TCommand>
    where TConnection : IConnection
{
    private readonly IUnitOfWork<TConnection> unitOfWork;
    public CommandHandler(IUnitOfWork<TConnection> unitOfWork)
    {
        this.unitOfWork = unitOfWork;
    }
}
public class SalesInsertCommandHandler<TCommand> : 
    CommandHandler<SalesConnection, TCommand>
{
}