理解领域驱动设计 (3)

值得注意的是,给定的分层方式仅仅是逻辑上的分层,而对于实际的物理分层,却又有所不同,但遵守一个前提为好,即限界上下文的边界高于分层的边界。诸如如下两种开发中常见的代码组织方式,都可见到。一种是基于技术分层,而另一种更偏向基于业务分层。

方式一

- application - productcontext - ordercontext - ... - domain - productcontext - ordercontext - ... - infrastructure - productcontext - ordercontext - ...

方式二

- productcontext - application - domain - infrastructure - ordercontext - application - domain - infrastructure

具体采用哪种方式,并没有强制要求,无论代码组织结构是否表达了层的概念,都需要充分理解分层的意义,并使得整个代码结构在架构上要吻合分层架构的理念。

战术设计

相比于战略设计的怎么规划,战术设计更侧重于怎么执行,详细的设计和编码。

图片

聚合

在认识聚合前,我们得对类再次回顾,类是作为我们开发中的最小单元,一切以类构建,而在上下文的视角中,聚合成了最小概念,包装了一组高度相关的对象,上下文内以聚合为最小单元,以此来保证聚合边界。又将分而治之的思想融入到了限界上下文的内部。

聚合本身是由一个或多个实体及值对象组成,其中一个实体作为聚合根。管理着内部关联的实体与值对象,对外代表着聚合,外部来访者仅可通过聚合根进行访问。

图片

对于聚合图的画法,或许因人而异,我更加倾向于用矩形代表实体,椭圆代表值对象,用 UML 类图中的组合-聚合箭头来表示其双方间的关系。

需要注意的是,此处的聚合不要与 UML 类图中的聚合等同起来,两者含义并不相同。

实体

对于实体来讲,这个概念对于我们并不陌生,拥有者唯一的身份标识符,内含属性作为该实体的静态特征,作为聚合所拥有的领域知识,拥有着与自身相关的领域行为。

值对象

对于值对象,我倾向于将它理解为,基础类型之延伸,既能封装基础类型,又能约束内部属性间关系,还能拥有着自身的领域行为,而与实体的区别是,没有唯一身份标识,尽管带来了持久化的一些问题,但还是存在解决方案。以 DateTime 理解值对象最好不过了,DateTime 内部的自身约束保证了,每一次变动的 DateTime 都是最新的,当我们想在 2 月 28 日加 1,这便要依靠 DateTime 中的行为去约束内部的属性。

聚合划分

经统一语言与业务分析阶段,借助一系列如事件风暴、用例分析法、名次动词法、四色建模法等活动后,获得了一系列相关联的对象。或可形成一张庞大的对象关联图。

图片

如不考虑聚合的划分,我们依照以往的思路便是创建一大堆表,运用三范式或是依靠程序去保证数据的一致性不运用主外键。然后疯狂撸码,CRUD 好不快活。

而随着业务的逐渐扩张,这当初的想法已有点吃力了,如同树苗逐渐成长,枝叶也逐渐增多。借助枝干我们可以分清叶子的归属,而对象网中呢,变得错综复杂了,也就隐约有了大泥球的征兆。

借助划分聚合的一些方法,将其规整化。将原有复杂的对象图拆分成可控制的小型对象图。

保持单一导航方向,解除双向依赖,保持依赖简单。

保持聚合设计的小巧

聚合内的业务规则一致性

通过聚合标识符引用其他聚合

聚合与协作聚合间因业务场景、进程边界等因素影响,可依照场景使用强一致性或是最终一致性。

如上的对象图依照关系的强弱,关系的主与次进行了聚合划分,或许得出的部分聚合存在不合理处,可再调整其边界。

图片

聚合协作

聚合与协作聚合之间依照聚合根实体的唯一标识符进行关联,而不是通过依靠协作聚合的引用实例来完成。保持这个原则有助于保持聚合之间的边界并避免加载不必要的对象。如我们常习惯上将关联的集合对象写入到类中,然后在仓储使用时,通过 EF 加载导航属性,以此方便直接加载关联聚合数据。

//一个聚合内建议用 public class Order : AggregateRoot { public virtual ICollection<OrderItem> OrdrItems { get; set; } //... } _orderRepository.Include(e=>e.OrderItems).FirstOrDefault();

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpzfwj.html