当前位置: 首页 > 科技观察

《查漏补缺》,梳理DDD的核心概念

时间:2023-03-12 21:28:11 科技观察

1.概述什么是DDD。DDD的英文全称是Domain-DrivenDesign,翻译过来就是领域驱动设计。这种设计一般用在微服务系统中。当我们谈论微服务时,争论最多的就是如何拆分微服务,这也是争议最大的地方。说到微服务,就免不了要说到中台。什么是中台?Zhongtai中泰自2015年提出以来就为我们所熟知,但大家对Zhongtai的理解可能千差万别。是否有一个每个人都同意的定义?将通用的可复用业务能力沉淀到中台业务模型中,实现企业级能力复用。因此,中台面临的首要问题是中台领域模型的重构。中台落地后,依然会面临微服务设计和拆分的问题。微服务:需要微服务来支撑中台的实现。中台:复用服务,实现企业级能力复用。DDD:对中台进行领域建模,实现适合企业发展的中台。DDD可以说是微服务和中台的产品经理。我们在写业务功能的时候,是面向领域的,而不是面向数据库表来实现代码的。2.什么是DDD?DDD的核心思想是通过领域驱动的设计方法来定义领域模型,从而确定业务和应用的边界,保证业务模型和代码模型的一致性。DDD是一种处理高度复杂领域的设计思想。它试图将技术实现的复杂性分离出来,围绕业务概念构建领域模型来控制业务的复杂性,从而解决软件难以理解和难以演进的问题。战略设计:主要从业务角度出发,建立业务领域模型,划分领域边界,建立通用语言的有界上下文。限界上下文可以作为微服务设计的参考边界。战术设计:从技术角度出发,重点关注领域模型的技术实现,完成软件开发和实现,包括:聚合根、实体、值对象、领域服务、应用服务的代码逻辑设计和实现,和资源库。3.DDD架构分层首先我们来看一下架构分层示意图:用户界面层用户界面层主要包括用户界面和Web服务。用户界面层负责向用户显示信息和解释用户指令。这里的用户可能是:用户、程序、自动化测试和批处理脚本等。应用层应用层不应该有业务逻辑。它是一个很薄的层,理论上应该没有业务规则和逻辑,主要面向用例和流程相关的操作。应用服务在应用层,负责服务的组合、编排和转发,业务用例的执行顺序和结果的组装,并通过API网关向前端发布粗粒度的服务。此外,应用服务还可以进行安全认证、权限验证、事务控制、发送或订阅领域事件等。领域层领域层主要实现企业的核心业务逻辑,与以前的三层架构。领域层还包括聚合,它包含领域模型中的领域对象,例如聚合根、实体、值对象和领域服务。领域模型的业务逻辑主要通过实体和领域服务来实现。使用充血模型时,首先要实现所有相关的业务功能。稍后将解释充血模型。当无法实现单个实体(或值对象)时,通过领域服务聚合多个实体(或值对象)来实现复杂的业务逻辑。基础层基础层为其他层提供通用技术和基础服务,包括数据库服务、消息中间件、对象存储、缓存服务等。它封装了所有的基础服务。切换基础组件时,只需要对基础服务稍作修改即可。比如之前用的object文件存储组件是阿里的,现在想换成腾讯的。我可以把基础服务稍微改一下,换成腾讯的。无需更改业务逻辑代码。这是采用依赖倒置的原则,通过解耦保持独立的核心业务逻辑。传统三层架构到DDD四层架构传统的三层架构是controller->service->model模型,我们的思维习惯是基于数据库表来开发业务功能。这种分层架构给开发者带来了方便,但是如果别人来看你的代码,他就很难从业务的角度去理解,因为这些代码是为了操作数据库表而写的。使用DDD,代码面向业务功能,而不是数据库表。DDD分层架构将业务逻辑层的服务拆分为应用层和领域层。应用层快速响应前端的变化,领域层实现领域模型的能力。三层架构的数据访问采用DAO方式,DDD分层架构数据库等基础资源访问,采用Repository设计模式,通过依赖倒置实现各层基础资源的解耦。4.DDD中的各种Object数据持久化对象(PersistentObject,PO)与数据库结构一一映射,是数据持久化过程中的数据载体。领域对象(DomainObject,DO),微服务运行时核心业务对象的载体,DO一般包括实体或值对象。DataTransferObject(DTO),用于前端应用程序与微服务应用层或微服务之间的数据组装和传输,是应用程序之间数据传输的载体。视图对象(ViewObject,VO)用于封装表现层中指定页面或组件的数据。微服务基础层的主要数据对象是PO。在设计的时候,我们需要先建立DO和PO的映射关系。大多数情况下DO和PO是一一对应的。但是也有DO和PO是多对多的情况。DO和PO数据转换时,需要进行数据重组。对于DO对象更复杂的数据转换操作,可以使用工厂模式在聚合中实现。当需要持久化DO数据时,首先将DO转化为PO对象,由仓库实现服务完成数据库持久化操作。当需要构造DO并初始化数据时,仓库实现服务首先从数据库中获取PO对象,将PO转化为DO,完成DO数据构造和初始化。领域层主要是DO对象。DO是实体和值对象的数据和业务行为载体,承载着基本的核心业务逻辑,多个紧密依赖的DO对象形成一个聚合。Domain层DO对象在持久化时需要转换为PO对象。应用层的主要对象是DO对象,但也可能有DTO对象。应用层在安排不同聚合的领域服务时,一般推荐使用聚合根ID引用方式,尽量避免不同聚合之间直接引用DO对象,避免聚合之间产生依赖。当涉及到跨微服务应用服务调用时,在调用其他微服务应用服务之前,会先将DO转换为DTO,完成跨微服务DTO数据组装,所以才会有DTO对象。前端调用后端应用服务时,用户界面层首先完成DTO到DO的转换,然后DO作为应用服务的参数,传给领域层完成业务逻辑处理。用户界面层主要完成DO和DTO的相互转换,完成微服务与前端应用数据的交互和转换。门面接口服务完成后端应用服务封装后,会组装多个DO对象,转换为DTO对象,完成数据转换并传输给前端应用。门面接口服务接收到前端应用的DTO后,完成DTO到多个DO对象的转换,调用后端应用服务完成业务逻辑处理。前端应用主要是VO对象。表现层使用VO进行界面展示,使用DTO对象通过用户界面层和应用层进行数据交互。五、领域分类DDD在研究和解决业务问题时,会按照一定的规则对业务领域进行细分。当领域被细分到一定程度时,DDD会将问题的范围限制在特定的边界内。在系统内部建立领域模型,然后用代码实现领域模型,解决相应的业务问题。简而言之,DDD的领域就是在这个边界内要解决的业务问题的领域。域又可以划分为多个子域,子域包括核心域、通用域和支撑域。核心领域:核心业务,是决定产品和企业核心竞争力的子领域。通用域:多个子域同时使用的通用功能子域。支持域:支持其他子域、非核心域和通用域。六、实施DDD流程的第一步:事件风暴,这里的风暴可以理解为头脑风暴,领域专家会与设计人员和开发人员一起构建领域模型。第二步:分析领域涉及的场景(用户故事)。第三步:分析完场景后,需要定义领域对象。设计实体,寻找聚合根,设计价值对象,设计领域事件,设计领域服务,设计存储。第四步:领域对象需要包含业务逻辑,因此会形成一个代码模型映射。第五步:根据代码模型实现代码落地。六、限界上下文和通用语言限界上下文域边界是通过限界上下文来定义的。它用于封装通用语言和领域对象,提供上下文,确保领域中的一些术语和业务相关对象(通用语言)具有准确的含义,没有歧义。理论上的上界上下文是微服务的边界。我们将限界上下文中的领域模型映射到微服务,完成从问题领域到软件的解决方案。如果不考虑技术异构、团队沟通等外部因素,限界上下文理论上可以设计成微服务。逻辑边界:微服务内聚合之间的边界是逻辑边界。它是虚拟边界,强调业务的内聚性,可以根据需要变成物理边界,也就是说聚合也可以是独立的微服务。物理边界:微服务之间的边界是物理边界。强调微服务部署和运行的隔离,关注微服务的服务调用、容错和运行。代码边界:不同层或聚合之间代码目录的边界就是代码边界。它强调代码之间的隔离,以方便架构演化过程中的代码重组。通用语言DDD分析设计过程中的每一个环节都需要保证限界上下文中术语的统一。在设计代码模型时,需要在领域对象和代码对象之间建立一对一的映射关系,以保证业务模型和代码模型的一致性。一致,实现业务语言和代码语言的统一。7.Entity实体概念实体和值对象是领域模型的基本单位。类包含了实体的属性和方法,通过类实现实体本身的业务逻辑。实体以DO(DomainObject)的形式存在,每个实体对象都有一个唯一的ID。该字段的值可以更改。实体是真实的、看得见、摸得着的业务对象。实体具有业务属性、业务行为和业务逻辑。实体特征通过ID标识,通过ID判断是否相等,在聚合内唯一。依附于聚合根,生命周期由聚合根管理。实体一般是持久化的,但与数据库持久化对象不一定是一对一的关系。实体可以引用聚合中的聚合根、实体和值对象。如下代码所示,Product属于产品实体,具有产品的唯一id。Location是一个值对象,值对象后面会解释。publicclassProduct{//产品实体privatelongid;//value对象,产品的唯一idprivateStringname;//单属性值对象privateLocation位置;//属性值对象,实体引用}publicclassLocation{//值对象,无主键idprivateStringcountry;//值对象privateStringprovince;//值对象privateStringcity;//值对象privateStringstreet;//valueobject}实体类通常采用充血模型。拥塞模型和贫血模型的区别贫血模型:数据和业务逻辑分离到不同的类中,比如Model类和Service类。拥塞模型:数据和业务逻辑封装在同一个实体类中。8.值对象值对象概念值对象描述了域中的一个事物,它是不可变的,它将不同的相关属性组合成一个概念整体。值对象是DDD领域模型中的一个基本对象。和实体一样,它来自事件风暴构建的领域模型。它包含几个属性,并与实体一起形成一个聚合。值对象只是若干属性的集合,只有数据初始化操作和不涉及修改数据的有限行为,基本不包含业务逻辑。值对象的属性集虽然在物理上是独立的,但在逻辑上仍然是实体属性的一部分,用来描述实体的特性。值对象的特点是没有ID,不可变,没有生命周期,用完了就不需要了。值对象之间的相等性是通过属性值来判断的。核心本质是值,是一组概念上完整的属性集合,用于描述实体的状态和特征。值对象尽可能只引用值对象。9.聚合与聚合根聚合聚合是与业务和逻辑密切相关的实体和值对象的组合。聚合是数据修改和持久化的基本单位。每个聚合对应一个仓库,实现数据持久化。聚合具有聚合根和方便的上下文。根据业务职责单一、高内聚的原则,定义了聚合中应该包含哪些实体和值对象,聚合之间的边界是松耦合的。聚合属于DDD的领域层,领域层包含多个聚合共同实现核心业务逻辑。聚合中的实体在血腥模型中实现个体业务能力和业务逻辑的高内聚性。跨多个实体的业务逻辑通过领域服务实现,跨多个聚合的业务逻辑通过应用服务实现。特点:高内聚低耦合。它是领域模型中的最低边界,可以作为拆分微服务的最小单元。但是,不建议过度拆分微服务。一个聚合可以作为一个微服务来满足版本的高频发布和极致的弹性扩展。一个微服务也可以包含多个聚合,这些聚合可以拆分和组合。聚合根聚合根是为了避免在复杂的数据模型中,由于缺乏统一的业务规则控制,导致聚合与实体之间数据不一致的问题。聚合可以比作一个组织,聚合根就是这个组织的负责人。外部对象不能直接访问聚合中的实体,需要先访问聚合根,再导航到聚合内部实体。特点:聚合根是一个实体,具有实体的特征,具有全局唯一标识,具有独立的生命周期。一个聚合只有一个聚合根。聚合根通过直接对象引用组织和协调聚合内的实体和值对象。聚合根与聚合根通过ID关联实现聚合间的协同。10.领域事件领域事件用于表示领域内发生的事件。域事件将导致进一步的业务操作。在实现业务解耦的同时,有助于形成完整的业务闭环。领域事件驱动设计可以切断领域模型之间的强依赖关系。事件发布完成后,发布者无需关心订阅者后续的事件处理是否成功。可以实现领域模型的解耦,保持领域模型的独立性和数据性的一致性。微服务之间的数据不需要强一致性,而是基于事件的最终一致性。领域事件的执行需要一系列的组件和技术来支持:事件构建与发布、事件数据持久化、事件总线、消息中间件、事件接收与处理。