为什么我越来越喜好用DDD — DDD架构篇(1)

打印 上一主题 下一主题

主题 873|帖子 873|积分 2629

Hello DDD


  • DDD 是一种软件设计方法,DDD 是引导我们做软件工程设计的一种本领。它提供了用切割工程模型的各类本领,如;范畴、边界上下文、实体、值对象、聚合、工厂、仓储等。通过 DDD 的引导思想,我们可以在前期投入更多的时间,更加合理的规划出可持续迭代的工程设计。
  • 在DDD中有一套共识的工程两阶段设计本领,包罗:战略设计、战术设计

    • 战略设计,DDD架构主要应对复杂的业务体系需求,通过抽象、分治的过程,合理的拆分为独立的多个微服务,从而分而治之。与之评价拆分的是否合理,则是在需求开辟上线时候,是否每次都大量操作多个微服务开辟和上线。这样的战略设计是一种失败的微服务单体设计。所以少数几个中等规模的单体应用,四周环绕着一个服务生态体系,这更故意义。
    • 战术设计:在这个范畴下,主要以讨论如何基于面向对象思维,运用范畴模型来表达业务概念。常在不做范畴模型设计的架构,也就是通常映射到 MVC 三层架构下,Service + 数据模型的开辟模式,会让 Service 扁平的、大量的,平铺出非常复杂的业务逻辑代码。再加上举动对象与功能逻辑的分离,贫血模型的开辟方式,让举动对象的不断交叉利用,也是让体系不断增加复杂度,并到难以维护的根因。所以这一阶段要设计每一个可以表达范畴概念的模型,并运用实体、聚合、范畴服务来承载。

DDD的概念

什么是充血模型?
范畴内都包罗什么?
实体、聚合、值对象,有什么区别?
这些概念,也是战术设计过程中非常紧张的知识项,搞清楚它们才能做 DDD 设计。
充血模型

充血模型,指将对象的属性信息与举动逻辑聚合到一个类中,常用的本领如在对象内提供属于当前对象的信息校验、拼装缓存Key、不含服务接口调用的逻辑处理等。


  • 这样的方式可以在利用一个对象时,就顺便拿到这个对象的提供的一系列方法信息,全部利用对象的逻辑方法,都不需要本身再次处理同类逻辑。
  • 但不要只是把充血模型,仅限于一个类的设计和一个类内的方法设计。充血还可以是整个包结构,一个包下包罗了用于实现此包 Service 服务所需的各类零部件(模型、仓储、工厂),也可以被看做充血模型。
  • 同时我们还会再一个同类的类下,提供对应的内部类,如用户实名,包罗了,通讯类、实名卡、银行卡、四要素等。它们都被写进到一个用户类下的内部子类,这样在代码编写中也会清晰的看到子类的所属信息,更容易明白代码逻辑,也便于维护迭代。
范畴模型

范畴模型,指特定业务范畴内,业务规则、策略以及业务流程的抽象和封装。在设计本领上,通过风暴模型拆分范畴模块,形成边界上下文。最大的区别在于把原有的浩繁 Service + 数据模型的方式,拆分为独立的有边界的范畴模块。每个范畴内创建自身所属的;范畴对象(实体、聚合、值对象)、仓储服务(DAO 操作)、工厂、端口适配器Port(调用外部接口的本领)等。
那么,现在这里有几个概念;范畴服务、范畴对象、仓储界说、事件消息、端口适配器。我们先来看他们是怎么从贫血模型演变过来的,在细分解说每个概念。


  • 在原本的 Service + 贫血的数据模型开辟引导下,Service 串联调用每一个功能模块。这些根本设施(对象、方法、接口)是被相互掉调用的。这也是由于贫血模型并没有面向对象的设计,全部的需求开辟只有详细设计。
  • 换到充血模型下,现在我们以一个范畴功能为聚合,拆分一个范畴内所需的 Service 为范畴服务,VO、Req、Res 重新设计为范畴对象,DAO、Redis 等长期化操作为仓储等。举例;一套账户服务中的,授信认证、开户、提额降额等,每一个都是一个独立的范畴,在每个独立的范畴内,创建自身范畴所需的各项信息。
  • 范畴模型另有一个特点,它自身只关注业务功能实现,不与外部任何接口和服务直连。如;不会直接调用 DAO 操作库,也不会调用缓存操作 Redis,更不会直接引入 RPC 连接其他微服务。而是通过仓库和端口适配器,界说调用外部数据的含有出入参对象的接口标准,让根本设施层做具体的调用实现。通过这样的方式让范畴只关心业务实现,同时做好防腐。
实体、聚合、值对象

原本在贫血模型下的开辟,常常是不会特殊在意一个方法的出入参对象的,也经常是很多个服务共用一个VO对象作为入参,只要这个对象能把我需要的属性信息带进来就可以了。
但在 DDD 的范畴模型设计下,范畴对象的设计是非常面向对象的。而且在整个风暴事件的四色建模过程也是在以范畴对象为驱动进行的。
实体、聚合、值对象,三者位于每个范畴下的范畴对象内,服务于范畴内的范畴服务。三个对象界说具体如下;

实体
是依托于长期化层数据以范畴服务功能目的为引导设计的范畴对象。长期化PO对象是原子类对象,不具有业务语义,而实体对象是具有业务语义且有唯一标识的对象,跟随于范畴服务方法的全生命周期对象。如;用户PO长期化对象,会涵盖,用户的开户实体、授信实体、额度实体对象。也包罗如商品下单时候的购物车实体对象。这个对象也通常是范畴服务方法的入参对象。

  • 概念:实体 = 唯一标识 + 状态属性 + 举动动作(功能),是DDD中的一个基本构建块,它代表了具有唯一标识的范畴对象。实体不仅仅包含数据(状态属性),还包含了相关的举动(功能),并且它的标识在整个生命周期中保持不变。
  • 特性:

    • 唯一标识:实体具有一个可以区分其他实体的标识符。这个标识符可以是一个ID、一个复合键大概是一个自然键,关键是它能够唯一地标识实体实例。
    • 范畴标识:实体的标识通常泉源于业务范畴,比方用户ID、订单ID等。这些标识符在业务上有特定的含义,并且在体系中是唯一的。
    • 委派标识:在某些情况下,实体的标识可能是由ORM(对象关系映射)框架自动天生的,如数据库中的自增主键。这种标识符虽然可以唯一标识实体,但它并不直接泉源于业务范畴。

  • 用途:

    • 表达业务概念:实体用于在软件中表达具体的业务概念,如用户、订单、交易等。通过实体的属性和举动,可以描述这些业务对象的特性和能力。
    • 封装业务逻辑:实体不仅仅承载数据,还封装了业务规则和逻辑。这些逻辑包罗验证数据的有效性、实行业务规则、计算属性值等。这样做的目的是保证业务逻辑的集中和划一性。
    • 保持数据划一性:实体负责维护自身的状态和数据划一性。它确保本身的属性和关联关系在任何时候都是正确和完整的,从而避免数据的不划一性。

  • 实现本领:

    • 界说实体类:在代码中界说一个类,该类包含实体的属性、构造函数、方法等。
    • 实现唯一标识:为实体类提供一个唯一标识的属性,如ID,并确保在实体的生命周期中这个标识保持不变。
    • 封装举动:在实体类中实现业务逻辑的方法,这些方法可以操作实体的状态,并实行相关的业务规则。
    • 利用ORM框架:利用ORM框架将实体映射到数据库表中,这样可以简化数据长期化的操作。
    • 实现范畴服务:对于跨实体或跨聚合的操作,可以实现范畴服务来处理这些操作,而不是在实体中直接实现。
    • 利用范畴事件:当实体的状态发生变化时,可以发布范畴事件,这样可以通知其他部分的体系进行相应的处理。

值对象
这个对象在范畴服务方法的生命周期过程内是不可变对象,也没有唯一标识。它通常是配合实体对象利用。如为实体对象提供对象属性值的描述,好比;一个公司雇员的级别值对象,一个下单的商品收货的四级地址信息对象。所以在开辟值对象的时候,通常不会提供 setter 方法,而是提供构造函数大概 Builder 方法来实例化对象。这个对象通常不会独立作为方法的入参对象,但做可以独立作为出参对象利用。

  • 概念:值对象是由一组属性组成的,它们共同描述了一个范畴概念。与实体(Entity)差别,值对象不需要有一个唯一的标识符来区分它们。值对象通常是不可变的,这意味着一旦创建,它们的状态就不应该改变。
  • 特性:

    • 不可变性(Immutability):值对象一旦被创建,它的状态就不应该发生变化。这有助于保证范畴模型的划一性和线程安全性。
    • 等价性(Equality):值对象的等价性不是基于身份或引用,而是基于对象的属性值。如果两个值对象的全部属性值都相称,那么这两个对象就被认为是等价的。
    • 替换性(Replaceability):由于值对象是不可变的,任何需要改变值对象的操作都会导致创建一个新的值对象实例,而不是修改现有的实例。
    • 侧重于描述事物的状态:值对象通常用来描述事物的状态,而不是事物的唯一身份。
    • 可复用性(Reusability):值对象可以在差别的范畴实体或其他值对象中重复利用。

  • 用途:

    • 金额和货币(如代价、工资、费用等)
    • 度量和数据(如重量、长度、体积等)
    • 范围或区间(如日期范围、温度区间等)
    • 复杂的数学模型(如坐标、向量等)
    • 任何其他需要封装的属性集合

  • 实现本领:

    • 界说不可变类:确保类的全部属性都是私有的,并且只能通过构造函数来设置。
    • 重写equals和hashCode方法:这样可以确保值对象的等价性是基于它们的属性值,而不是对象的引用。
    • 提供只读访问器:只提供获取属性值的方法,不提供修改属性值的方法。
    • 利用工厂方法或构造函数创建实例:这有助于确保值对象的有效性和划一性。
    • 考虑序列化支持:如果值对象需要在网络上传输或存储到数据库中,需要提供序列化和反序列化的支持。

聚合
当你对数据库的操作需要利用到多个实体时,可以创建聚合对象。一个聚合对象,代表着一个数据库事件,具有事件划一性。聚合中的实体可以由聚合提供创建操作,实体也被称为聚合根对象。一个订单的聚合,会涵盖;下单用户实体对象、订单实体、订单明细实体和订单收货四级地址值对象。而谁人作为入参的购物车实体对象,已经被转换为实体对象了。—— 聚合内事件划一性,聚合外最终划一性。

  • 概念:聚合是范畴模型中的一个关键概念,它是一组具有内聚性的相关对象的集合,这些对象一起工作以实行某些业务规则或操作。聚合界说了一组对象的边界,这些对象可以被视为一个单一的单元进行处理。
  • 特性:

    • 划一性边界:聚合确保其内部对象的状态变化是划一的。当对聚合内的对象进行操作时,这些操作必须保持聚合内全部对象的划一性。
    • 根实体:每个聚合都有一个根实体(Aggregate Root),它是聚合的入口点。根实体拥有一个全局唯一的标识符,其他对象通过根实体与聚合交互。
    • 事件边界:聚合也界说了事件的边界。在聚合内部,全部的变更操作应该是原子的,即它们要么全部乐成,要么全部失败,以此来保证数据的划一性。

  • 用途:

    • 1.封装业务逻辑:聚合通过将相关的对象和操作封装在一起,提供了一个清晰的业务逻辑模型,有助于业务规则的实施和维护。
    • 2.保证划一性:聚合确保内部状态的划一性,通过界说清晰的边界和规则,聚合可以在内部强制实行业务规则,从而保证数据的划一性。
    • 3.简化复杂性:聚合通过组织相关的对象,简化了范畴模型的复杂性。这有助于开辟者更好地明白和扩展体系。

  • 实现本领:

    • 界说聚合根:选择符合的聚合根是实现聚合的第一步。聚合根应该是能够代表整个聚合的实体,并且拥有唯一标识。
    • 限定访问路径:只能通过聚合根来修改聚合内的对象,不答应直接修改聚合内部对象的状态,以此来维护边界和划一性。
    • 设计事件策略:在聚合内部实现事件划一性,确保操作要么全部完成,要么全部回滚。对于聚合之间的交互,可以采用范畴事件或其他机制来实现最终划一性。
    • 封装业务规则:在聚合内部实现业务规则和逻辑,确保全部的业务操作都遵照这些规则。
    • 长期化:聚合根通常与数据长期化层交互,以保存聚合的状态。这通常涉及到对象-关系映射(ORM)或其他数据映射技术。

仓储和适配器

在 DDD 的设计方法中,范畴层做到了只关心范畴服务实现。最能体现这样设计的就是仓库和适配器的设计。通常在 Service + 数据模型的设计中,会在 Service 中引入 Redis、RPC、配置中心等各类其他外部服务。但在 DDD 中,通过仓储和适配器以及根本设施层的界说,解耦了这部分内容。


  • 特性:

    • 封装长期化操作:Repository负责封装全部与数据源交互的操作,如创建、读取、更新和删除(CRUD)操作。这样,范畴层的代码就可以避免直接处理数据库或其他存储机制的复杂性。
    • 范畴对象的集合管理:Repository通常被视为范畴对象的集合,提供了查询和过滤这些对象的方法,使得范畴对象的获取和管理更加方便。
    • 抽象接口:Repository界说了一个与长期化机制无关的接口,这使得范畴层的代码可以在差别的长期化机制之间切换,而不需要修改业务逻辑。

  • 用途:

    • 数据访问抽象:Repository为范畴层提供了一个清晰的数据访问接口,使得范畴对象可以专注于业务逻辑的实现,而不是数据访问的细节。
    • 范畴对象的查询和管理:Repository使得对范畴对象的查询和管理变得更加方便和灵活,支持复杂的查询逻辑。
    • 范畴逻辑与数据存储分离:通过Repository模式,范畴逻辑与数据存储逻辑分离,进步了范畴模型的纯粹性和可测试性。
    • 优化数据访问:Repository实现可以包含数据访问的优化策略,如缓存、批处理操作等,以进步应用程序的性能。

  • 实现本领:

    • 界说Repository接口:在范畴层界说一个或多个Repository接口,这些接口声明白所需的数据访问方法。
    • 实现Repository接口:在根本设施层或数据访问层实现这些接口,具体实现可能是利用ORM(对象关系映射)框架,如MyBatis、Hibernate等,大概直接利用数据库访问API,如JDBC等。
    • 依赖注入:在应用程序中利用依赖注入(DI)来将具体的Repository实现注入到需要它们的范畴服务或应用服务中。这样做可以进一步解耦范畴层和数据访问层,同时也便于单元测试。
    • 利用规范模式(Specification Pattern):偶然候,为了构建复杂的查询,可以结合利用规范模式,这是一种答应将业务规则封装为单独的业务逻辑单元的模式,这些单元可以被Repository用来构建查询。

Repository模式是DDD(范畴驱动设计)中的一个核心概念,它有助于保持范畴模型的聚焦和清晰,同时提供了灵活、可测试和可维护的数据访问策略。
仓储解耦的本领利用了依赖倒置的设计,全部范畴需要的外部服务,不在直接引入外部的服务,而是通过界说接口的方式,让根本设施层实现范畴层接口(仓储/适配器)的方式来处理。
那么也就是根本设置层负责原则对接数据库、缓存、配置中心、RPC接口、HTTP接口、MQ推送等各项资源,并承接范畴服务的接口调用各项服务为范畴层提供数据能力。
同时这也会体现出,范畴层的实现是具有业务语义的,而到了根本设施层则没有业务语义,都是原子的方法。通过原子方法的组合为范畴业务语义提供支撑。
范畴编排

在 DDD 中,每一个范畴都是边界上下文拆分的独立结果,而实现业务流程的功能则需要串联各个范畴模块提供一整条链路的完整服务。所以也常说范畴内事件划一性,范畴外最终划一性。
同时这些范畴模块由于是独立的,所以也可以被复用。在差别的场景功能诉求下,可以选择差别的范畴模块进行组装,这个过程就像搭积木一样。
但这里有一个取舍,如果项目相对来说并不大,也没有太多的编排处理。那么可以直接让触发器层对接范畴层,减少编排层后,编码会更加便捷。
触发器

在全部的模型都界说完成后,范畴业务被串联了。那么接下来则是利用,而利用的方式可以包罗;接口(http/rpc)、消息监听、定时任务等方式,这些方式统一被界说为触发动作。
由触发发起对编排功能的调用处理。如;定时任务做信贷的计息、开户乐成消息通知返利优惠券、提供接口让外部调用授信逻辑等。这些都是触发动作。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

泉缘泉

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表