我已经将域模型对象设置为独立于任何服务和基础结构逻辑。我还使用域事件来对域模型中的一些更改作出反应。
现在我的问题是如何从域模型对象本身引发这些事件。目前,我正在使用Udi Dahan的DomainEvents静态类(我需要在事件发生时准确处理,而不是在以后)。这些事件用于许多事情,如日志记录、更新相关服务和其他领域模型对象和数据库中的数据、将消息发布到MassTransit总线等。
DomainEvents静态类使用我在其中某个时刻注入的Autofac作用域来查找IMediatr实例并发布事件,如下所示:
public static class DomainEvents
{
private static ILifetimeScope Scope;
public async static Task RaiseAsync<TDomainEvent>(TDomainEvent @event) where TDomainEvent : IDomainEvent
{
var mediator = Scope?.Resolve<IMediatorBus>();
if (mediator != null)
{
await mediator!.Publish(@event).ConfigureAwait(false);
}
else
{
Debug.WriteLine("Mediator not set for DomainEvents!");
}
}
public static void SetScope(ILifetimeScope scope)
{
Scope = scope;
}
}
这一切在单线程环境中都可以正常工作,但DomainEvents.SetScope()方法在多线程环境中可能是一个赛车问题。当我介绍MassTransit并创建消息使用者时,每个消息使用者都会通过该方法将当前LifetimeScope设置为DomainEvents,而问题是,每个使用者都会用新的LifetimeScope覆盖生存期范围。
为什么使用DomainEvents静态类?因为我不想用基础设施的东西污染我的领域模型对象。我曾想过让DomainEvents成为非静态的(定义一个接口),但后来我需要将它们注入到每个域模型对象中,我仍在考虑这一点,但也许有更好的方法。
我想知道是否有更好的方法来处理这个问题?也许DomainEvents类发生了一些更改?或者可以删除DomainEvents静态类,最终使用接口或DomainService来完成此操作。问题是我不喜欢静态类,但我也不喜欢将非域特定的依赖项推送到域模型对象中。
请帮忙。
更新
为了更好地阐明过程以及我使用DomainEvents的原因。。。我有一个长时间运行的过程,可能需要几分钟到几小时/天才能完成。所以这个过程是这样的:
- 我收到来自MassTransit的消息,即ProcessStartMessage(processId)
- 从Db获取(processId)的ProcessData
- 构造一个内存中的域模型ProcessTracker(singleton),并将我从DB加载的所有数据放入其中。(内存缓存中)
- 我收到另一条来自Masstransit的消息,即ProcessStatusChanged(processId,data)
- 将此消息数据转发到内存中的单例ProcessTracker进行处理
- ProcessTracker处理数据
ProcessTracker为了能够处理这些数据,它实例化了许多域模型对象,每个对象负责处理部分数据。(请注意,不再有来自数据库的数据库调用和实体水合,这一切都发生在内存中,而且域模型没有映射到任何实体,也没有连接到任何数据库对象)。在某个时刻,我需要记录链中的域模型对象做了什么,它是否完成或开始工作,是否达到了某个里程碑等。这是通过引发DomainEvents来完成的。我还需要将这些事件通知GUI,以便它们也用于发送Masstransit消息。
Ie。(伪代码):
public class ProcessTracker
{
private Step _currentStep;
public void ProcessData(data)
{
_currentStep.ProcessData(data);
DomainEvents.Raise(new ProcesTrackerDataProcessed());
...
}
}
public class Step
{
public Phase _currentPhase;
public void ProcessData(data)
{
if (data.IsManual && _someOtherCondition())
{
DomainEvents.Raise(new StepDataEvent1());
...
}
if(data.CanTransition)
{
DomainEvents.Raise(new TransitionToNewPhase(this, data));
}
_currentPhase.DoSomeWork(data);
DomainEvents.Raise(new StepDataProcessed(this, data));
...
}
}
关于数据库更新,这些更新不是事务性的,对进程也不重要,域模型对象状态只保存在内存中,如果进程崩溃,进程必须从头开始(没有恢复)。
结束流程:
- 我从MassTransit接收ProcessEnd
- 消息数据被转发到ProcessTracker
- ProcessTracker处理数据并获取处理结果
- 处理结果保存到数据库
- 将向流程中的其他方发送一条消息,通知他们流程已完成
首先问问自己,当您从域模型中引发事件时,您将做什么?
通常它是这样工作的:
- 获取命令
- 从存储库加载域对象
- 执行行为
- (可能在这里)引发一个事件
- 保持新域对象状态
那么,您额外的域事件处理程序适合哪里呢?你打算执行其他一些数据库调用,发送电子邮件吗?请记住,这一切都发生在现在,当您甚至还没有持久化域对象的更改状态时。如果你的坚持失败了怎么办?在执行完所有域处理程序后,就会发生。
在处理单个命令时,不应执行多个事务。Aggregate
模式清楚地告诉您聚合就是事务边界。您应该在完成事务后引发域事件,或者在同一技术事务内引发域事件,但它应该只保持事件的聚合状态和。域事件反应可能会触发其他域对象的事务,并且应该在处理当前命令的范围之外执行。
这个问题根本不是技术问题,而是设计问题。
如果使用MassTransit,则只有在消息使用者中处理命令时,才能使其(相对)可靠。然后,您可以使用内存发件箱,除非使用者成功,否则发件箱不会发送事件。仍然不能保证在代理失败的情况下会发布该事件。
除非你去活动采购,否则你有两个100%可靠的选择:
- 使用事务发件箱模式(NServiceBus有一个,它非常复杂)。它对您使用的数据库类型有限制
- 将事件存储到与域对象相同的数据库中,存储在同一事务中的不同表中。使用
DELETE INTO
轮询该表,并从那里向代理发送事件