领域驱动设计释厄录

基础

学习DDD看的书

  1. 《实现领域驱动设计》
    基础只是,概念
  2. 《领域驱动设计:软件核心复杂性应对之道 》
    使用,流程,串联
    千万不要先看 《领域驱动设计:软件核心复杂性应对之道 》,不然你会哭的,因为你看的和天书基本没啥区别,要先看 《实现领域驱动设计》,然后在去看 埃文斯的那本书。

领域模型

领域模型是某个业务领域的软件模型,通常通过对象模型来实现。

贫血模型和充血模型

  • 在贫血模型中,对象中只有属性值和公有的get set 方法,几乎没有业务逻辑,完全就是一个数据集。
  • 在充血模型中,数据与对应的业务逻辑被封装在一个类中,这种模型满足面向对象的封装特性,是典型的面向对象编程。
  • 充血模型和贫血模型是领域对象和非领域对象的重要区别。

失忆症

  • 当业务逻辑出现多次变化的适合,直接散落在项目中的各个业务逻辑很可能忘记其本来的意图,而业务的迭代,可能会导致项目越来越复杂,这样就会出现,除作者以外,甚至作者,看这段代码的适合会蒙圈,这种一般称为贫血模型导致的失忆症。

是充血模型的好处

  • 复用程度高,表达能力强,适合复杂的业务逻辑处理
  • 业务逻辑的变动,很可能会导致领域模型的变动,但是,这个在传统的开发中也是不可避免的,但是充血模型将逻辑高内聚,反而不容易漏改逻辑

举例 -- 这个可以不看

一个简单的主子订单模型,加上付款操作
在传统模式下,可见我们的对象只是一个数据集合,除了传递数据,并没有做任何操作,这个只是一个简单的付款,如果之后,业务场景复杂后,很可能出现,这个一个业务逻辑,哪里一个业务逻辑

public class Order {
    private Long orderId;
    private String orderNo;
    private Long amount;
    private Integer status;
    ---get set 方法 --- 
}
public class OrderItem {
    private Long id;
    private Long orderId;
    private Long orderNo;
    private String itemName;
    private Long payAmount;
  --- get set 方法--- 
}

付款方法
    public void pay(OrderReq orderReq){
        Order order =  dao1.query(orderReq);
        List  orderItems = dao2.query(orderReq);
        ---业务逻辑
        order.setStatus(PAY);
        for (OrderItem orderItem : orderItems) {
           ----业务逻辑
            orderItem.setPayAmount(orderReq.getPayAmount());
        }
        transaction.execute(){
            dao1.save(order);
            dao2.save(orderItems);
        }
    }
业务更新后,很可能 
    public void pay(OrderReq orderReq){
        Order order =  dao1.query(orderReq);
        List  orderItems = dao2.query(orderReq);
        ---业务逻辑
         order.setStatus(PAY);
        if(会员){
          order.setStatus(PAY1);
        }
        if(优惠){
         order.setStatus(PAY2);
        }
        for (OrderItem orderItem : orderItems) {
           ----业务逻辑
        if(优惠){
         orderItem.setStatus(PAY2);
        }
        if(会员){
          orderItem.setStatus(PAY1);
        }
            orderItem.setPayAmount(orderReq.getPayAmount());
        }
        transaction.execute(){
            dao1.save(order);
            dao2.save(orderItems);
        }
    }

领域是什么

在实现领域驱动一书中这样解释

从广义上讲,领域(Domain)即是一个组织所做的事情以及其中所包含的一切。商业机构通常会确定一个市场,然后在这个市场中销售产品和服务。每个组织都有它自己的业务范围和做事方式。这个业务范围以及在其中所进行的活动便是领域。当你为某个组织开发软件时,你面对的便是这个组织的领域。这个领域对于你来说应该是明晰的,因为你在这个领域中工作。

在我看来,领域其实就是我们的业务模块,或者说是我们的实际业务就是领域。

子域

对一个大的领域的问题进行根据业务职能的划分,例如在一个电商项目中,商品,订单,支付,履约,结算,库存,物流,发票,用户就是不同的子域。而在子域的划分中,也会产生不同的类型,核心子域通用子域支撑子域等等。

限界上下文

  • 特定模型的限界应用。限界上下文使团队所有成员能够明确地知道什么必须保持一致,什么必须独立开发。
  • 限界上下文为业务流程在一个划定的界限,由一个或者多个子域组成。
  • 在基于某一个角度,将多个子域划分进一个限界上下文
  • 限界上下文即是一个特定的解决方案,在一个特定的限界上下文只使用一套通用语言,并且保证它的清晰性和简洁性。

问题空间和解决问题空间

  • 问题空间:是我们业务所遇到的挑战,问题空间是领域的一部分,对问题空间的开发将产生一个新的核心域,问题空间是核心域和其他子域的组合。
  • 解决问题空间:如何实现软件以解决这些业务挑战,包括一个或多个限界上下文,即一组特定的软件模型来解决问题。

上下文映射图

  • 表示限界上下文和他们呢的集成关系,下面就是一个上下文映射图,


    U表示上游(Upstream),D表示下游(Downstream)

领域驱动架构

系统分层

DDD所使用的传统分层架构
  • 一般我们使用聚合或者工厂对复杂的聚合进行创建,而不是直接在应用层创建
  • 领域模型用于发布领域事件时,应用层可以将订阅方注册到任意数量的事件上,这样的好处是可以对事件进行存储和转发。同时,领域模型只需要关注自己的核心逻辑;领域事件发布器也可以保持轻量化,而不用依赖于消息机制的基础设施,可以使用Spring的event事件。
image.png

命令和查询职责分离——CQRS

  1. 如果一个方法修改了对象的状态,该方法便是一个命令(Command),它不应该返回数据。在Java和C#中,这样的方法应该声明为void。
  2. 如果一个方法返回了数据,该方法便是一个查询(Query),此时它不应该通过直接的或间接的手段修改对象的状态。在Java和C#中,这样的方法应该以其返回的数据类型进行声明。

防腐层

  • 当你的领域和其他领域存在数据交互时,你需要一个防腐层作为两个领域之间的纽带。这会给你带来很多“数据转换”的代码,但是对于业务多变的系统来讲,它能保护你的领域。
  • 在我们的微服务架构中,一般来说指当前服务与其他服务进行交互的适合,并不直接调用,而由一个中间层,来进行判断,处理,缓冲。

实体

  • 一种对象,它不是由属性来定义的,而是通过一连串的连续事件和标识定义的。
  • 实体应该具有唯一标示,如订单号,身份证号,用户id
  • 唯一标示可以有用户产生(如手机号)/系统产生(如uuid)/持久化机制生成(如mysql主键)
  • 实体的其他信息可以进行修改,但是无论如何修改,唯一标示是不变的,其还是一个相同的实体。

值对象

  • 一种描述了某种特征或属性但没有概念标识的对象。
  • 度量或描述,它度量或者描述了领域中的一件东西。
  • 不变性,可以作为不变量,一个值对象创建了就不能改变了
  • 概念整体,它将不同的相关的属性组合成一个概念整体。一个值对象可以只处理单个属性,也可以处理一组相关联的属性。在这组相关联的属性中,每一个属性都是整体属性所不可或缺的组成部分,这和简单地将一组属性组装在对象中是不同的。如果一组属性联合起来并不能表达一个整体上的概念,那么这种联合并无多大用处。
    即他可以是单个属性,也可以是多个属性,但必须是有关联性的,而不是一堆属性的聚合。
  • 可替换性,当度量和描述改变时,可以用另一个值对象予以替换。
  • 值对象相等性,它可以和其他值对象进行相等性比较
  • 无副作用行为,它不会对协作对象造成副作用

最小化集成

在所有的DDD项目中,通常存在多个限界上下文,这意味着我们需要找到合适的方法对这些上下文进行集成。当模型概念从上游上下文流入下游上下文中时,尽量使用值对象来表示这些概念。这样的好处是可以达到最小化集成,即可以最小化下游模型中用于管理职责的属性数目。使用不变的值对象使得我们做更少的职责假设。

这里我理解的最小化集成,就是我们的限界上下文直接的参数应该是值对象

领域服务

  • 领域中的服务表示一个无状态的操作,它用于实现特定于某个领域的任务
  • 慎重使用领域服务,能在聚合和实体中完成的操作,不要放在领域服务中,过度使用领域服务将会造成贫血领域模型。
  • 领域服务通常和领域内相关的聚合放在同一模块(目录)下。

什么是领域服务

  1. 执行一个显著的业务操作过程。
    2.对领域对象进行转换。
    3.以多个领域对象作为输入进行计算,结果产生一个值对象。

领域事件 Domain Event

  • 领域事件用于发布和捕捉发生在领域内的一些事情,领域事件通常是由某个聚合发出,在整个领域范围内订阅。某某方法执行完成、某某数据修改成功、某某接口调用结束等等都可以理解为领域内事件,这些事件结束后我们往往需要做一些进一步的操作,比如:订单领域完成下单操作后,物流领域需要开始物流。这是在两个领域之间具备先后关系的事件,问题在于完成下单之后怎么通知物流领域呢?直接调用吗?领域事件认为,在有需要时,当订单创建完成,应当对外发布一个携带了必要对象信息的订单创建完成事件(OrderCreatedEvent)消息,由订阅方订阅到该事件消息,进而完成后续业务。
  • 领域事件是对领域内发生的活动进行的建模
  • 我觉的领域事件就使用事务消息就比较好,别的书里说的感觉用处不大

模块

  • 可以理解为 package分包
  • 模块表示了一个命名的容器,用于存放领域中内聚在一起的类。
  • 将类放在不同模块中的目的在于达到松耦合性。
  • 由于DDD中的模块并不是一个通用的存储区域,因此对其进行适当的命名是重要的。
  • 事实上,模块名是通用语言的重要组成部分。
  • 模块应该包含一组具有高内聚性的概念集合,这样做的好处是可以在不同的模块之间实现松耦合

模块的命名规范

  • 通常模块名就是包名,一般来说是公司层面统一的
  • 先考虑模块,再是限界上下文,因为模块的目的在于组织那些内聚在一起的领域对象

聚合

  • 将实体和值对象在一致性边界之内组成聚合(Aggregate)
  • 聚合就是一组相关对象的集合,我们把聚合作为数据修改的单元。外部对象只能引用聚合中的一个成员,我们把它称为根。在聚合的边界之内应用一组一致的规则。

聚合的原则

  1. 在一致性边界之内建模真正的不变条件:
    不变条件是指某个业务规则,这个规则应该总是保持一致的。这里的一致通常是指事务一致性,因为聚合作为我们操作和读取数据的基本单元,这就要求我们在提交事务时,该聚合边界内的所有对象都应当保持一致。 即在一个事务中只修改一个聚合实例,需要保证事务一致性
  2. 设计小聚合
    因为聚合需要保证在一个事务中只能修改一个聚合,所以聚合需要更加紧凑,逻辑更加一致,保证在单个聚合的事务一致性,多个聚合直接,需要实现最终一致性。
  3. 通过唯一标识引用其他聚合
    即聚合直接引用的适合使用唯一标示引用,而不是直接引用聚合对象。在java中,通俗标示,就是我们因为另一个聚合的唯一id,而不是直接引用对象,这样我们的聚合会更加轻量级,减少内存,对与java来说,对gc也有好处。
  4. 在边界之外使用最终一致性
    即多个聚合直接需要保持最终一致性,一般使用事件驱动来完成最终一致性

工厂 Factory

  • 将创建复杂对象和聚合的职责分配给一个单独的对象,该对象本身并不承担领域模型中的职责,但是依然是领域设计的一部分。工厂应该提供一个创建对象的接口,该接口封装了所有创建对象的复杂操作过程,同时,它并不需要客户去引用那个实际被创建的对象。对于聚合来说,我们应该一次性地创建整个聚合,并且确保它的不变条件得到满足
  • 隐藏创建细节,方便调用

资源库 Repository

  • 我理解的在我们的开发过程中,资源库就是我们的dao层,与数据存储进行交互的。一般资源库分为面向集合的资源库面向持久化的资源库
  1. 面向集合的资源库

面向集合的资源库是指资源库就像一个集合,在向一个Set集合中添加聚合时,如果重复添加是不会成功的,从集合中获取到的实例也可以直接进行修改而不需要写会操作,因为从集合中获取到的对象是引用类型,这意味着任何修改都会直接生效。类似Hibernate,但是用的不多

  1. 面向持久化的资源库

面向持久化的资源库就是我们熟悉的数据库操作,但是我们需要把实现细节进行封装,资源库应该尽可能地模拟一个集合的操作方式,将细节封装,例如防止聚合添加重复的校验不再由客户端做,调用方不关心细节,可以完全作为一个集合的方式使用。

还有

  • 对事务的管理绝对不应该放在领域模型和领域层中,而应该放在应用层

资源库和dao的区别

  • DAO主要从数据库表的角度来看待问题,并且提供CRUD操作,DAO相关的模式通常只是对数据库表的一层封装。
  • 而资源库采用面向集合的方式,而不是面向数据访问的方式。
  • 所以 资源库 可以看作 DAO,但是DAO不一定是资源库

基础设施

基础设施的职责是为应用程序的其他部分提供技术支持。

集成限界上下文

  • 常见的手段包括开放领域服务接口、开放HTTP服务以及消息发布-订阅机制。

领域对象的生命周期

image.png
  1. 使用FACTORY(工厂)来创建和重建复杂对象和AGGREGATE(聚合),从而封装它们的内部结构。
  2. 在生命周期的中间和末尾使用REPOSITORY(存储库)来提供查找和检索持久化对象并封装庞大基础设施的手段。

https://zhuanlan.zhihu.com/p/350033901
https://tech.meituan.com/2017/12/22/ddd-in-practice.html

你可能感兴趣的:(领域驱动设计释厄录)