DDD(领域驱动设计)--战术设计
前言
战术设计
战略设计为我们提供一种高层视野来审视我们的软件系统,主要包括领域/子域、通用语言、限界上下文和架构风格等概念,
而战术设计则将战略设计进行具体化和细节化,它主要关注的是技术层面的实施,也是对程序员来得最实在的地方。
战术设计的目的是保证战略的实现。在DDD中,代码就是设计本身,你不再需要那些繁文缛节的并且永远也无法得到实时更新的设计文档。
警惕贫血对象,要创建行为饱满的领域对象并不难,我们需要转变一下思维,将领域对象当做是服务的提供方,而不是数据容器,多思考一个领域对象能够提供哪些行为,而不是数据。
实体
一个实体模型就是一个独立的事物,采用充血模型,具有业务属性和业务行为。每个实体都拥有一个唯一的标识符,可以将它的个性化和所有其他类型相同或者不同的实体区分开。许多时候,实体是可变的,它的状态会随着时间发生变化。
对一个实体进行多次修改,修改后的数据和原来可能会不大相同,但它们依然是同一个实体,因为唯一标识没变。
值对象
一个值对象,是对一个不变的概念整体所建立的模型,没有一个唯一的标识符。在这个模型中,值就真的只有一个值。和实体不一样,它没有唯一标识符,而是由值类型封装的属性对比来决定相等性。此外,一个值对象不是实物,而是常常用来描述、量化或者测量一个实体。
一个典型的值对象是地址值对象,比如下单中,有下单人、商品信息、优惠信息、还有收货地址信息,如果地址信息用省、市、区等属性表示,会有些零碎,拿出来构成一个“地址”属性,会更合适一些,这个多个值的集合就是值对象了。
聚合
在DDD中,实体和值对象是很基础的领域对象,实体跟值对象都只是个体化的对象,它们的行为表现出来的是个体能力。聚合用来确保这些领域对象在实现共同的业务逻辑时,能保证数据的一致性。
聚合是由业务和逻辑紧密关联的实体和值对象组合而成的。聚合是数据修改和持久化的基本单元。
聚合设计的四条基本规则
- 在聚合边界内保护业务规则不变性
- 聚合要设计得小巧
- 只能通过标识符引用其他聚合
- 使用最终一致性更新其他聚合
聚合根
每个聚合都有一个根实体,叫做聚合根,外界只能通过聚合根跟聚合通信。聚合根的主要目的是为了避免由于复杂数据模型缺少统一的业务规则控制,而导致聚合、实体之间数据不一致的问题。
如果把聚合比作一个团队,那么聚合根就是团队的leader,其他团队的需求都需要跟该团队的leader商量后才能开始动工。
领域服务
当某个操作不适合放在聚合和值对象上时,最好的方式便是使用领域服务了。
可以使用领域服务的地方,过度使用领域服务将导致贫血领域模型。
- 执行一个显著的业务操作过程
- 对领域对象进行转换
- 已多个领域对象作为输入进行计算,结果产生一个值对象
领域服务不需要定义接口,直接定义实现即可
- 如果接口有不同的实现,那么需要考虑领域中是否存在特定的功能行为
- 如果我们采用了依赖注入或者工厂,即便接口和实现类是合并在一起的,我们依然能达到这样的目的。依赖倒置容器(例如 Spring)将完成服务实例的注入工作,由于客户端并不负责服务的实例化,它并不知道接口和实现类是分开的还是合并在一起的。
领域服务与实体方法的区别
- 实体方法完成单一实体自身的业务逻辑,相对简单的原子业务逻辑
- 领域服务则是多个实体组合出的相对服务的业务逻辑
仓储
仓储用于保存和获取聚合对象,应该将仓储看作一个对象的集合,而不是数据库的CRUD,更不是一张表一个仓储。
需要做到让用户无感知的,以为就在内存中使用一个集合一样。
仓储的出现就是为了让大家聚焦领域模型,不要聚焦表结构。
领域事件
领域事件是一条记录,记录着在限界上下文中发生的对业务产生重要影响的事情,经常用于保证两个聚合之间的一致性。
对于领域事件的命名,必须体现出模型的通用语言,这些名词是连接模型之间的桥梁,对发生的事情进行充分的沟通至关重要。
领域事件类型名称应该是对过去发生事情的陈述,即动词的过去式,如OrderCreated。
参考资料
- 《领域驱动设计——软件核心复杂性应对之道》
- 《实现领域驱动设计》
- 《领域驱动设计精粹》
__EOF__
欢迎转载,但请注明出处!
欢迎大家一起交流学习!如果有什么疑问,大家可以在评论区一起交流!
如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是我的最大动力!