考虑一个支持作者通过多个渠道发布文章的系统,识别业务领域中的实体可以得到:文章、作者、渠道三个明显的实体类型。假设文章支持文字、图片、视频以及几种形式的混合,那么还需要一个文章内容实体,一篇文章中可能由一段文字+若干张图片+一个视频+一段文字构成,那么文字、图片和视频可以分别作为独立的文章内容,这样便于作者分别编辑。因此就有了如下的实体关系:
现在进一步考虑这个系统应该支持的功能。除了正常的文章、作者维护以外,系统可能还需要提供文章和作者的下架操作,因为可能某些内容或者作者出现了不适宜展示的内容。
在对文章进行下架时,我们需要考虑是否删除文章内容,答案似乎显而易见:既然删除了文章肯定要删除文章内容。那么下架作者呢,是否应该删除作者名下的文章呢?
有经验的开发者可能立即识别到,可以不做物理删除,通过增加逻辑删除状态就可以实现下架的功能。但是问题依然存在,即使采用状态字段,我们是否需要在下架的同时处理关联的内容,对于作者来说,如果作者下架了我们是否还能让读者浏览他已经发布的内容?这似乎依赖于需求。而对于文章来说,如果文章下架了,文章的内容一定要修改为逻辑删除状态吗?既然已经在各种入口浏览不到下架的文章了,其内容是否删除似乎并不重要。
在我们的业务领域中,充满着各种实体间关系,类似上面的例子,我们在设计系统时,必须考虑这些关系以及在不同情况下面如何处理这些关系。
上面的例子相对比较简单,在复杂一些的系统中,各个实体之间可能都有联系,而且通常这些关系并不好维护,在不同的状态下如何维护关系通常取决于业务规则,那么有没有一种相对系统的方法来指导我们处理这些复杂的关系呢。
在领域驱动设计中,解决这种问题的有力武器便是聚合。
我们面临的复杂业务对象关系之所以呈现显得复杂,通常是由于没有定义出他们之间的边界。这些由业务规则约束着的关系只是表面,我们真正需要处理的实际上是业务规则,由那些不变的业务规则约束着的一组实体和值对象就构成一个聚合。
一个聚合有明显的边界,在这个边界内部的对象之间有比较紧密的联系,而所有业务规则所关注的核心实体便是这个聚合的根。
所以,对于一个聚合来说,最关键的是确定其 边界 和 根。
例如,对于上面的例子,我们可以划分出这样的聚合:
上面划分出了三个聚合,其中作者和渠道都只有一个实体构成,在实际的系统中这种仅由一个实体构成的聚合通常占绝大多数。文章由文章实体和文章内容构成。
那么这么划分的原则是什么呢?
在上面的划分中,作者和渠道被划分独立的聚合,这是比较常见的也是符合系统要求的划分方法。将聚合设计得尽可能小有以下几方面的考虑:
虽然在聚合之间划分了清晰的边界,但是聚合之间通常不可能完全独立,他们之间还会存在联系。
在前面的聚合划分示意图中,文章中标注了属性 author_id 和 channel_id,这和作者以及渠道本身的唯一标识相关联,通过这种唯一标识引用聚合,以相对轻量的方式实现了聚合之间关联关系的维护,在通过关联关系进行查找时也可以轻松定位关联的其他聚合实体。
在聚合的边界之外,没必要再通过强一致性事务维护聚合之间的联系。因为强一致性通常意味着较低的性能,对于那些不紧密的联系,在业务允许的前提下,最好通过引入消息组件等形式来实现最终一致性,既提供关联关系的维护能力,又不会带来系统性能的显著降低。
领取专属 10元无门槛券
私享最新 技术干货