C# 在持久化聚合之前发布域事件是否安全?
在许多不同的项目中,我看到了两种引发域事件的不同方法C# 在持久化聚合之前发布域事件是否安全?,c#,dns,domain-driven-design,event-sourcing,C#,Dns,Domain Driven Design,Event Sourcing,在许多不同的项目中,我看到了两种引发域事件的不同方法 直接从聚合引发域事件。例如,假设您有Customer aggregate,其中有一个方法: public virtual void ChangeEmail(string email) { if(this.Email != email) { this.Email = email; DomainEvents.Raise<CustomerChangedEmail>(new Customer
public virtual void ChangeEmail(string email)
{
if(this.Email != email)
{
this.Email = email;
DomainEvents.Raise<CustomerChangedEmail>(new CustomerChangedEmail(email));
}
}
我不喜欢这种方法的什么地方?我不想为同一事件创建两种不同的类型,即对于CustomerChangedMail行为,我应该有CustomerChangedMailUncommitted类型和CustomerChangedMailDomainEvent。只要一种就好了。请分享您在这方面的经验 我见过两种不同的引发域事件的方法 历史上,有两种不同的方法。Evans在描述域驱动设计的战术模式时没有包括域事件 在一种方法中,域事件充当事务中的协调机制。许多描述这种模式的帖子得出结论: 请注意,上述代码将与常规域工作在同一事务中的同一线程上运行,因此应避免执行任何阻止活动,如使用SMTP或web服务 ,通常的替代品,实际上是一种非常不同的动物,只要事件被写入记录簿,而不仅仅是用于协调写入模型中的活动 当前实现的第二个问题是,每个事件都应该是不可变的。所以问题是如何初始化它的“occurrendon”属性?仅限内部聚合!这是合乎逻辑的,对吧!它迫使我将ISystemClock(系统时间抽象)传递给聚合上的每个方法 当然-看 如果你不认为时间是一个输入值,想想看,直到你做了——这是一个重要的概念 在实践中,实际上有两个重要的时间概念要考虑。如果时间是域模型的一部分,那么它就是一个输入 如果时间只是您试图保存的元数据,那么聚合不一定需要知道它——您可以将元数据附加到其他地方的事件。例如,一个答案是使用工厂实例创建事件,工厂本身负责附加元数据(包括时间) 如何实现这一目标?一个代码示例对我很有帮助 最直接的例子是将工厂作为参数传递给方法
public virtual void ChangeEmail(string email, EventFactory factory)
{
if(this.Email != email)
{
this.Email = email;
UncommitedEvents.Add(factory.createCustomerChangedEmail(email));
}
}
应用层中的流看起来像
请看格雷格·杨的演讲。主要流程是订阅者从记录簿中提取事件。在这种设计中,推送模型是一种延迟优化。我不是您介绍的两种技术中任何一种的支持者:) 现在我喜欢从域返回事件或响应对象:
public CustomerChangedEmail ChangeEmail(string email)
{
if(this.Email.Equals(email))
{
throw new DomainException("Cannot change e-mail since it is the same.");
}
return On(new CustomerChangedEmail { EMail = email});
}
public CustomerChangedEmail On(CustomerChangedEmail customerChangedEmail)
{
// guard against a null instance
this.EMail = customerChangedEmail.EMail;
return customerChangedEmail;
}
这样,我就不需要跟踪未提交的事件,也不需要依赖全局基础结构类,例如DomainEvents
。应用层控制事务和持久性的方式与不使用ES时相同
至于协调发布/保存:通常另一个间接层会有所帮助。我必须提到,我认为ES事件不同于系统事件。系统事件是指有界上下文之间的事件。消息传递基础结构将依赖于系统事件,因为这些事件通常比域事件传递更多的信息
通常,在协调诸如发送电子邮件之类的事情时,会使用流程管理器或其他实体来传递状态。您可以通过一些DateEMailChangedSent
在客户
上执行此操作,如果为空,则需要发送
这些步骤是:
- 开始交易
- 获取事件流
- 拨打电话更改客户的电子邮件,甚至添加到事件流中
- 需要记录电子邮件发送(DateEMailChangedSent返回null)
- 保存事件流(1)
- 发送
消息(2)sendmailchangedCommand
- 提交事务(3)
DateEMailChangedSent
在开始之前有一个值,我们可能会遇到以下异常:
(1) 如果我们无法保存事件流,那么这里没有问题,因为异常将回滚事务,处理将再次发生。(2) 如果由于某些消息传递失败而无法发送消息,那么就没有问题了,因为回滚会将所有内容设置回开始之前的状态。 (3) 好吧,我们已经发送了消息,所以提交时的异常可能看起来像是一个错误
public virtual void ChangeEmail(string email, EventFactory factory)
{
if(this.Email != email)
{
this.Email = email;
UncommitedEvents.Add(factory.createCustomerChangedEmail(email));
}
}
public CustomerChangedEmail ChangeEmail(string email)
{
if(this.Email.Equals(email))
{
throw new DomainException("Cannot change e-mail since it is the same.");
}
return On(new CustomerChangedEmail { EMail = email});
}
public CustomerChangedEmail On(CustomerChangedEmail customerChangedEmail)
{
// guard against a null instance
this.EMail = customerChangedEmail.EMail;
return customerChangedEmail;
}