您好我有一个常见问题,我认为Autofac或任何IoC容器都无法解决这个问题。这可能是一个设计问题,我需要一些新的输入。
我拥有EF 6的经典MVC网络解决方案。它以真正的DDD风格实施,具有反腐败层,三个有限的上下文,横切关注的问题推动了基础设施项目。很高兴看到所有作品都以良好的方式落入其中。我们还将CUD操作的命令添加到域中。
现在问题就在于此。客户需要一个跟踪每个实体属性的更改日志,并且在完成更新时,我们需要在更新之前和之后保存到更改日志值。我们在ILoggerService
中实现了成功,它包含了我们用来检测更改的Microsoft测试实用程序。但我,我的角色是软件架构师,决定使用ChangeTrackerRepository
来装饰我们的通用存储库,这些存储库依赖于ILoggerService
。这很好用。装饰器跟踪Add(…)
中的方法Modify(…)
和IRepository<TEntity>
。
问题是我们的存储库具有自定义存储库,这些存储库具有如下自定义查询:
public class CounterPartRepository : Repository<CounterPart>, ICounterPartRepository
{
public CounterPartRepository(ManagementDbContext unitOfWork)
: base(unitOfWork)
{}
public CounterPart GetAggregate(Guid id)
{
return GetSet().CompleteAggregate().SingleOrDefault(s => s.Id == id);
}
public void DeleteCounterPartAddress(CounterPartAddress address)
{
RemoveChild(address);
}
public void DeleteCounterPartContact(CounterPartContact contact)
{
RemoveChild(contact);
}
}
我们有简单的存储库,它只关闭通用存储库并将适当的EF Bounded上下文注入其中(工作单元模式):
public class AccrualPeriodTypeRepository : Repository<AccrualPeriodType>, IAccrualPeriodTypeRepository
{
public AccrualPeriodTypeRepository(ManagementDbContext unitOfWork)
: base(unitOfWork)
{
}
}
问题在于,当通过通用装饰器使用AutoFac装饰AccrualPeriodTypeRepository时,我们可以轻松地将该repo注入到CommandHandler actor中
public AddAccrualPeriodCommandHandler(IRepository<AccrualPeriod> accrualRepository)
这很好用。
但我们如何装饰CounterPartRepository ???
我已经经历了几个解决方案,他们最终都陷入了死胡同。
1)手动装饰每个自定义存储库生成许多自定义装饰器,它将几乎无法维护。
2)使用扩展的自定义查询装饰关闭的Repository Repository。这闻起来很糟糕。应该是该存储库的一部分吗?
3)如果我们考虑2 ...也许跳过我们的服务并且仅依靠IRepository来操作我们的聚合根和IQueryHandler(参见文章https://cuttingedge.it/blogs/steven/pivot/entry.php?id=92)
我认为,当您拥有自定义的封闭存储库并且简单的存储库也已关闭但同时从同一存储库继承时,我需要对常见问题进行一些新的输入,当涉及到装饰存储库时
答案 0 :(得分:2)
您是否考虑过装饰命令处理程序而不是装饰存储库? Repos的级别太低,知道应该记录什么以及如何记录并不是他们的责任。
以下内容如何:
1)你有一个命令处理程序:
public class DeleteCounterPartAddressHandler : IHandle<DeleteCounterPartAddressCommand>
{
//this might be set by a DI container, or passed to a constructor
public ICounterPartRepository Repository { get; set; }
public void Handle(DeleteCounterPartAddressCommand command)
{
var counterpart = repository.GetPropertyById(command.CounterPartId);
// in DDD you always want to read and aggregate
// and save an aggregate as a whole
property.DeleteAdress(command.AddressId);
repository.Save(counterpart)
}
}
2)现在你可以简单地使用Chain of Responsibility模式用日志记录,交易等来“装饰”你的处理程序:
public class LoggingHandler<T> : IHandler<T> {
private readonly IHandler<T> _innerHandler;
public LoggingHandler(IHandler<T> innerHandler) {
_innerHandler = innerHandler;
}
public void Handle(T command)
{
//Obviously you do it properly, but you get the idea
_log.Info("Before");
_innerHandler.Handle(command);
_log.Info("After");
}
}
现在您只有一段代码负责日志记录,您可以使用任何命令处理程序撰写它,因此如果您想要记录特定命令,那么您只需将其“包装”在记录处理程序,它仍然是您的IHandle<T>
,因此系统的其余部分不会受到影响。
而且你也可以解决其他问题(线程,排队,交易,多路复用,路由等),而不会在这里和那里乱搞这些东西。
通过这种方式将问题分开。
对我来说也好得多,因为你登录的是真实的操作(业务)级别,而不是低级别的存储库。
希望它有所帮助。
PS 在DDD中,你真的希望你的存储库只暴露聚合级别的方法,因为聚合假设要处理它们的不变量(没有别的,没有服务,没有存储库),并且因为聚合代表交易边界。
真的,由Repository决定如何从持久存储中获取Aggregate以及如何将其保留回来,在它之外它应该看起来像是在向某人询问某个对象并且它为您提供了一个可以调用行为的对象。 / p>
通常,您只能从存储库中获取聚合,调用其行为然后将其保存回来。这实际上意味着您的存储库主要具有GetById和Save方法,而不是像“UpdateThatPartOfAnAggregate”这样的内部组件。