DDD Core
高内聚,低耦合是好的软件设计标准,而DDD是一种软件设计思想,他用一些更加细化的流程或者规范指导我们一步一步走到高内聚低耦合的目标。
软件是将现实生活中的行为映射到代码中,所以越是接近现实的设计,越是高效的。能否做到这一点,也正是软件工程师和程序员的区别。
各种概念
领域
和现实生活中的领域一个意思,指一块业务范围。
核心域 & 子域
核心域和子域都是领域,只是地位不同。
限界上下文
对应到现实生活中,限界上下文类似语境的意思。每个领域中有一个或多个限界上下文,但如果存在多个限界上下文,那应该思考,领域是否切分的太大。
限界上下文用来辅助领域的使用,例如不同语境下相同的东西可能用不同的词汇(例如商品和货物)或者相同的东西关注不同的属性。
Q:我的一个电商系统,有个Class叫Product,我可以在库存领域和物流领域使用同一个Class吗?因为我不想要重复的代码?
A:应该使用两个Product Class(准确的说在库存领域叫commodity,在物流领域是goods,虽然他们其实是一个东西),因为在不同领域我们对模型的侧重点不同,在物流领域我们关注Product的类型编号,不能送错货物了,或者重量大小之类的。而在库存领域,我们即关心的是他们的大小重量,也关心他们的生产日期之类的,我不想存过期的东西。两个领域既有重合在意的信息,也有不同侧重的信息,所以应该建立两个Class在不同的领域。
实体&值对象
举例:体育场的座位。当我们发放的门票上有座位号的时候,座位需要作为独立的实体,座位号是唯一的标识。而当先到先座模式下,我们只关心剩余座位数,那么座位号并不是唯一标识,这时候座位就可以作为一个值对象。
所以一个model是值对象还是实体与业务场景有关,与具体存储方法无关,他们都可以存入数据库都可以有在数据库中的唯一标示。
举例:我们网购的时候填入的地址,从业务角度并不关心他的唯一性,你可以使用它,与你同住的人都可以使用。但每个人可能有很多个不同的收货地址,例如家,学校,公司等。所以在存储时你需要单独建立一个表,每个地址有个数据库中的唯一标示,并用此唯一标示绑定与你的一对多关系。所以地址在这个业务场景下,是一个值对象,但有数据库的id。
在学DDD的初期,经常会陷入思维定势,将新的知识与已知的知识对应,以为有id的就是实体,没有id的就是值对象,这样的想法并不全面,就像不是有了北洋舰队,清朝就变成了现代国家一样。在学习DDD的时候一定要摆脱旧的数据建模的思想,不能一上来就想数据库建表之类的事情。而是一切以业务为中心进行领域建模。
高内聚,就是将model的行为封装到实体本身中,所以在高内聚的代码中,应该很少需要get和set方法,这又让我们多了一个不使用Lombok的理由。
聚合&聚合根
聚合包含聚合根、实体和值对象。
聚合根是一种实体,从真实世界对业务对象进行识别和概念建模的时候,一个对象可能有多个层次,也可能有多个子实体,但是这些子实体都不可能孤立存在,它们必须依附于一个聚合根存在,它们和根节点具有同样的生命周期。
举例:在电商系统中,客户是一个实体也是聚合根,客户的地址,订单是依附于客户的值对象和子实体。
架构
DDD 并不硬性要求架构设计,可以使用分层架构、六边形架构、SOA架构、REST风格、CRQS架构、事件驱动架构、数据网织和基于网格的分布式计算等架构方式/风格。这些架构的文章很多,在这里不赘述了,省的复制粘贴工作。
核心理解
无论是分层架构还是六边形架构,在DDD中我们要保证的就是将领域中的业务逻辑和我们的基础设施分开。
所以领域内的model类中,不应该有类似@Table等这类JPA的注解,也不应该有@Valid这样网络接口层用来校验合法性的注解,这样就讲我们的领域模型和基础设施强耦合了,如果我们有一天更换数据库类型,我们就需要修改领域模型,这是不合理的。
Q:这样我们会不会有很多相似的类,例如Product在领域逻辑中,在数据持久层中,在网络接口层都需要一个类文件,而他们的属性基本相同。
A:我只能说这些重复是有利的。