在上一篇文章中,介绍了A+ES的基本概念及适合解决的一些问题,我们看到分布式最终一致性的解决方案的巧妙。如果您想实践一下A+ES,先别急,今天我们看看实践过程中的常见问题,实践者可以更好的理解A+ES,也为选型提供一些参考。
1. 并发控制
当多个线程或多个实例同时访问聚合的事件流,这将导制一些潜在的并发冲突,从而使聚合处于不正确的状态。如图所示:
最简单的方法就是在线程2处理事件4时抛出一个EventStoreConcurrency Exception供最终用户处理,需要时也可以捕获这个异常进行重试。
如果重新执行聚合的成本过高,或者不方便重新执行,那么我可以使用事件冲突决议(Event conflict resolution),它可以减少并发所致的异常。这种方法的基本思想是在追加到事件流之前对事件进行比对校验。
2. 性能
如果一个事件流由成百上千个事件组成,那么我们在回放一个聚合时可能会有性能问题。解决方法有两种:
a. 版本号,为事件指定版本号,在内存中缓存加载过的事件,在聚合进行操作时,基于最后一个版本号获取该事件之后的那些事件进行播放。这种方法是以内存换取性能。
b. 聚合快照,在加载聚合实例时,我们只需要加载最近的一次快照,然后对发生在其后的事件进行重放。也可以使用资源库来访问快照。
3. 如何实现事件存储
事件可以存储在SQL,NoSQL或基于文件的BLOB存储。存储的实现都比较简单,我们看一下基于关系支持版本号的存储表结构如何设计:
如果使用基于文件的BLOB存储的,一般策略是:每个聚合实例对应一个文件,每个事件对应一条记录:
4. 读模型投射
上一篇我们提到在基于A+ES的实践中,如何实现不同维度的聚合查询是常见问题,例:最近一个月所有客户的订单总量。对于A+ES的实践中,并没有像关系型数据库那样灵活的连接查询操作。差劲的方法是先构建聚合,然后重播所有事件,让当前聚合进入正确的状态,然后匹配我们的查询条件,One by one最终得到我们想要的结果。想想就很复杂,实践中我们一般不会这样去做,这里我们推荐一种被称为读模型投射(Read Model Projection)可供查询使用的模式。
读模型投射中,使用一组简单的领域事件订阅方来生成和更新读模型,当订阅方接收到新的事件时,它们将计算一些查询结果,然后将这些结果保存了到读模型中以供后续使用。
5. 事件变更的支持
在敏捷实践中,我们提倡小步快跑,快速开发,在应对新需求的加入时,可以让现在模型演化达到支持新需求的目的,那么如果我们的新需求需要修改领域事件该如何处理呢?
如果我们只是简单的变更了字段,那么必然会对订阅方产生影响,这时选择一个有利于版本控制和事件重命名的序列器是不错的选择,对于不同版本的属性变更可以通过标签而不是名字来跟踪各个契约成员。Google 开源了一个Protocol Buffer可供选择。
总结
A+ES不同于我们原来的基于数据库的应用,它在某种程度上简化了一类问题的解决方法,同时也让另一类问题变得复杂,今天的内容希望能够帮助我们的实践者在做架构决策提提供更多的支持。