美文网首页
DDD领域驱动设计

DDD领域驱动设计

作者: Insights | 来源:发表于2021-06-23 17:27 被阅读0次

    参考来源: 使用DDD指导业务设计的一点思考 https://insights.thoughtworks.cn/ddd-business-design/ 后端开发实践系列——领域驱动设计(DDD)编码实践 https://insights.thoughtworks.cn/backend-development-ddd

    领域驱动设计指导业务设计

    领域驱动设计是什么

    领域驱动设计是 Eric Evans 提出的一种软件设计方法和思想,主要解决业务系统的设计和建模。

    理论发展过程

    1. DDD(Domain-Driven Design,领域驱动设计),对应中文版为《领域驱动设计》。

    2. Implement Domain-Driven Design,简称IDDD,中文版《实现领域驱动设计》。 3.《领域驱动设计模式、原理与实践》问世,简称PPPDDD。

    3. IDDD的精华版DDDD(Domain-Driven Design Distilled),《领域驱动设计精粹》。

    模型和领域模型

    模型是能够表达系统业务逻辑和状态的对象。 模型,用来反映事物某部分特征的物件,无论是实物还是虚拟的。 领域,指的特定行业或者场景下的业务逻辑。 DDD 中的模型是指反应 IT 系统的业务逻辑和状态的对象,是从具体业务(领域)中提取出来的,因此又叫做领域模型。

    我们可以吧系统复杂的问题分为两类:

    • 业务复杂度

    • 技术复杂度 技术复杂度,软件设计中和技术实现相关的问题,例如处理用户输入,持久化模型,处理网络通信等。 业务复杂度,软件设计中和业务逻辑相关的问题,例如为订单添加商品,需要计算订单总价,应用折扣规则等。

    识别上下文的边界是 DDD 中最难得一部分,同时上下文边界是由业务变化动态变化的,我们把识别出边界的上下文叫做限界上下文(Bounded Context)。 使用上下文之后,带来另外一个收获。模型之间本质上没有多对多关系,如果有,说明存在一个隐含的成员关系,这个关系没有被充分的分析出来,对后期的开发会造成非常大的困扰。

    我们将那相关性极强的领域模型放到一起考虑,数据的一致性必须解决,同时生命周期也需要保持同步,我们把这个集合叫做聚合

    聚合中需要选择一个代表负责和全局通信,类似于一个部门的接口人,这样就能确保数据保持一致。我们把这个模型叫做聚合根

    相对于非聚合根的模型,我们叫做实体

    image

    我们把没有自己生命周期的模型,仅用来呈现多个字段的值的模型和对象,称作为值对象

    image

    使用领域模型指导程序设计

    指导数据库设计

    使用领域模型建立数据库的要点:

    • 留意多对多关系,并拆解成一对多关系

    • 值对象和实体往往为一对一关系

    • 使用 ORM 时,聚合根和实体可以配置为级联删除和更新

    • 禁止聚合根之间进行关联

    指导 API 设计

    RESTful API 已经变成了主流 API 设计方式,当设计好领域对象后,设计 API 的难度大大降低。

    使用聚合根作为 URI 的根路径,使用实体作为子路径。通过 ID 作为 Path 参数。 image

    指导对象设计

    领域模型解决业务复杂度的问题,领域模型只应该被用作处理业务逻辑,存储、业务表现都应该和领域模型无关。 可以把这些 Plain Object 分为三类:

    • DTO,和交互相关或者和后端、第三方服务对接

    • Entity,数据库表映射

    • Model,领域模型

    指导代码组织

    代码组织,通俗来说就是如何分包。 DDD 特别的抽离出一层叫做 application。这一层是 DDD 的精华,领域模型关心业务逻辑,但是不关心业务场景。

    application 用来隔离业务场景,显得非常重要。 image

    领域驱动设计(DDD)编码实践

    实现业务的3种常见方式

    1. 基于“Service + 贫血模型”的实现 这种方式当前被很多软件项目所采用,主要的特点是:存在一个贫血的“领域对象”,业务逻辑通过一个Service类实现,然后通过setter方法更新领域对象,最后通过DAO(多数情况下可能使用诸如Hibernate之类的ORM框架)保存到数据库中。 这种方式依然是一种面向过程的编程范式,违背了最基本的OO原则。另外的问题在于职责划分模糊不清,使本应该内聚在Order中的业务逻辑泄露到了其他地方(OrderService),导致Order成为一个只是充当数据容器的贫血模型(Anemic Model),而非真正意义上的领域模型。在项目持续演进的过程中,这些业务逻辑会分散在不同的Service类中,最终的结果是代码变得越来越难以理解进而逐渐丧失扩展能力。

    2. 基于事务脚本的实现 我们会发现领域对象(Order)存在的唯一目的其实是为了让ORM这样的工具能够一次性地持久化,在不使用ORM的情况下,领域对象甚至都没有必要存在。于是,此时的代码实现便退化成了事务脚本(Transaction Script),也就是直接将Service类中计算出的结果直接保存到数据库(或者有时都没有Service类,直接通过SQL实现业务逻辑)。 在系统足够简单的情况下完全可以采用。但是:一方面“简单”这个度其实并不容易把握;另一方面软件系统通常会在不断的演进中加入更多的功能,使得原本简单的代码逐渐变得复杂。

    3. 基于领域对象的实现 在这种方式中,核心的业务逻辑被内聚在行为饱满的领域对象(Order)中,实现Order类如下:

    public void changeProductCount(ProductId productId, int count) {
        if (this.status == PAID) {
            throw new OrderCannotBeModifiedException(this.id);
        }
        OrderItem orderItem = retrieveItem(productId);
        orderItem.updateCount(count);
    }
    

    然后在Controller或者Service中调用:

    @PostMapping("/order/{id}/products")
    public void changeProductCount(@PathVariable(name = "id") String id, @RequestBody @Valid ChangeProductCountCommand command) {
        Order order = DAO.byId(orderId(id));
        order.changeProductCount(ProductId.productId(command.getProductId()), command.getCount());
        order.updateTotalPrice();
        DAO.saveOrUpdate(order);
    }
    

    可以看到,所有业务(“检查Order状态”、“修改Product数量”以及“更新Order总价”)都被包含在了Order对象中,这些正是Order应该具有的职责。

    相关文章

      网友评论

          本文标题:DDD领域驱动设计

          本文链接:https://www.haomeiwen.com/subject/exjfektx.html