如何解决域对象中带有 Autofac 的 IMediatr DDD
我已将域模型对象设置为独立于任何服务和基础架构逻辑。 我也在使用领域事件来应对领域模型中的一些变化。
现在我的问题是如何从域模型对象本身引发这些事件。 目前,我正在为此使用 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() 方法在多线程环境中可能存在竞争问题。 IE。当我引入 MassTransit 并创建消息消费者时,每个消息消费者都会通过该方法将当前 LifetimeScope 设置为 DomainEvents,而问题是,每个消费者都会用新的生命周期范围覆盖。
为什么我使用 DomainEvents 静态类?因为我不想用基础设施的东西污染我的领域模型对象。 我想过使 DomainEvents 非静态(定义一个接口),但后来我需要将它们注入到每个域模型对象中,我仍在考虑这一点,但也许有更好的方法。
我想知道是否有更好的方法来处理这个问题? 也许 DomainEvents 类有一些变化?或者可能删除 DomainEvents 静态类,最终使用接口或 DomainService 来执行此操作。 问题是我不喜欢静态类,但我也不喜欢将非特定于域的依赖项推送到域模型对象中。
请帮忙。
更新
为了更好地阐明流程以及我使用 DomainEvents 的目的... 我有一个长时间运行的过程,可能需要几分钟到几小时/几天才能完成。 所以这个过程是这样的:
- 我收到来自 MassTransit 的消息,即 ProcessStartMessage(processId)
- 从数据库中获取 (processId) 的 ProcessData。
- 构建内存域模型ProcessTracker(单例)并将我从DB加载的所有数据放入其中。 (内存缓存)
- 我收到了来自 Masstransit 的另一条消息,即。 ProcessStatusChanged(processId,data)。
- 将此消息数据转发到内存中的单例 ProcessTracker 进行处理。
- ProcessTracker 处理数据。
为了 ProcessTracker 能够处理这些数据,它实例化了许多领域模型对象,每个对象负责处理数据的某些部分。 (注意没有更多的 db 调用和来自 db 的实体水化,这一切都发生在内存中,域模型也没有映射到任何实体,它没有连接到任何 db 对象)。 在某些时候,我需要记录链中的域模型对象已完成的操作、它的工作是否完成或开始、已达到某个里程碑等。这是通过引发 DomainEvents 来完成的。我还需要将这些事件通知 GUI,因此它们也用于发送 Masstransit 消息。
即(伪代码):
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));
...
}
}
关于数据库更新,那些不是事务性的,对进程不重要,域模型对象状态只保存在内存中,如果进程崩溃,进程必须从头开始(没有恢复)。
结束进程:
- 我从大众运输系统收到 ProcessEnd
- 消息数据被转发到ProcessTracker
- ProcessTracker 处理数据并将其作为过程的结果
- 处理结果保存到db
- 向流程中的其他方发送一条消息,通知他们流程已完成。
解决方法
首先问问自己,当您从域模型中引发事件时,您打算做什么?
通常它是这样工作的:
- 获取命令
- 从存储库加载域对象
- 执行行为
- (这里可能)发起一个活动
- 保持新的域对象状态
那么,您的额外域事件处理程序适合放在哪里?您是否要执行其他一些数据库调用,发送电子邮件?请记住,这一切都现在发生,当您甚至还没有持久化域对象的更改状态时。如果你的坚持失败了怎么办?它会在 执行完所有域处理程序之后发生。
当您处理单个命令时,您不应执行多个事务。 Aggregate
模式清楚地告诉您聚合是事务边界。您应该在完成事务之后引发域事件,或者在同一技术事务中,但它应该只保留聚合状态和事件.域事件反应可能会触发其他域对象的事务,并且应该在处理当前命令的范围之外完成。
这个问题根本不是技术问题,而是设计问题。
如果您使用 MassTransit,则只有在消息使用者中处理命令时,才能使其(相对)可靠。然后,您可以使用 in-memory outbox,除非消费者成功,否则它不会发送事件。仍然不能保证在代理失败的情况下会发布事件。
除非您使用 Event Sourcing,否则您有两个 100% 可靠的选择:
- 使用事务发件箱模式(NServiceBus 有一个,而且非常复杂)。它对您使用的数据库类型有限制。
- 将事件存储到与域对象相同的数据库中,在不同的表中,在同一个事务中。使用
DELETE INTO
轮询表并从那里向代理发送事件。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。