我们开发使用
DDD
,CQRS+Eventsourcing
的应用程序。
我们有一个
UserAggregate
。该聚合使用 UserMailIndex
。
UserMailIndex
- 是一个投影,它只是唯一电子邮件的列表。每次当userCreated
事件推送到事件存储时,投影机都会读取该事件,并将邮件添加到UserMailIndex
。
当
UserAggregate
收到命令CreateUser{Mail = ..., Name = ... etc.}
时,聚合会在CreateUser.Mail
中查找UserMailIndex
以确保邮件之前未被使用过。如果没有使用,UserAggregate
会产生事件UserCreated
。
public class UserAggregate
{
private UserMailIndex _mailIndex;
UserAggregate(UserMailIndex mailIndex)
{
_mailIndex = mailIndex;
}
...
public void CreateUser(string userMail)
{
if(_mailIndex.Contains(userMail))
return;
Apply(UserCreatedEvent(userMail));
}
}
public class UserMailIndex
{
public void When(UserCreatedEvent evnt)
{
index.Add(evnt.Mail);
}
}
如何确保
UserMailIndex
是最新的?
如果投影仪没有时间更新UserMailIndex
投影怎么办?
如果我理解正确的话,这样的架构是基于最终一致性的。
有哪些方法可以确保投影是最新的? 或者哇来决定差距是否“足够小”?
此问题扩展到任何缓存数据场景,包括投影、物化视图和所有索引场景。当我们试图防止数据重复并且您有一个作为事实来源的存储时,唯一的Critical途径是插入/创建数据的实际过程,这也应该是引发create的唯一途径 活动。
第一个问题是您的模式描述的事件源实际上并不“知道”它所描述的事件是否确实发生。您故意向模型添加额外的不确定性,从而使事情变得复杂。如果您首先引发
UserCreated
事件,并且实际的创建过程失败,那么引发该事件实际上已经破坏了 最终一致性 的链,除非其他进程尝试并成功创建用户。
因此从这里开始,实际的
CreateUser
过程(而不是消息)需要确保邮箱是唯一的。这可能是一个 SQL 或其他可以确保原子性和唯一性的后端,也可能是一个外部系统,如果存在重复,则返回失败结果。
UserCreated
事件的过程。为了支持最终一致性,我们不仅允许,而且我们预计会发生这些失败,因为我们知道我们的预测或索引将始终在某种程度上过时。缓存始终是前一个时间点状态的副本,它永远不是“现在”的快照
因此,我们应该预期
CreateUser
命令将为同一用户多次调用,但它应该只引发 UserCreated
事件一次,即第一次实际成功创建用户时。
如何确保
是最新的?如果投影仪没有时间更新UserMailIndex
投影怎么办?UserMailIndex
你不要尝试,我们那不是真的,系统效率取决于投影是否保持最新。但重要的是要将你的想法从“可能会失败”转变为“预计有时会失败”。缓存的目的是减少到底层存储的往返,但不是完全取代或阻止它们。
因此,当
CreateUser
进程失败时,由于用户已经存在,为了支持最终一致性,您可能会选择忽略此错误,或者更改逻辑以使用新消息详细信息更新用户,但重要的是您不会提出UserCreated
事件。
或者如何判断间隙是否“足够小”?
如何决定是你的IP并取决于你的应用程序域,但是
的简单模型适用于任何尺寸的间隙。这可能意味着到商店的一些冗余往返行程非常小,但这是最终一致性的一般权衡,没有可接受的间隙大小,我们必须始终更新以确保一致性,除非解决方案的其余部分允许您假设可以容忍一定的间隙大小。