精华内容
下载资源
问答
  • 领域驱动实践总结二:架构分析与代码设计 领域驱动设计DDD是一种设计思想,它可以同时指导中台业务建模和微服务设计(中台本质是业务模型,微服务是业务模型的系统落地),领域驱动设计强调领域模型和微服务设计的一体...

    目录

    领域驱动实践总结二:架构分析与代码设计

    一、微服务架构模型的对比与选择

    (一)整洁架构

    (二)六边形架构

    (三)DDD 分层架构

    1.用户接口层

    2.应用层

    3.领域层

    4.基础层

    5.从三层架构向 DDD 分层架构演进

    (四)三种微服务架构模型的对比和分析

    二、领域驱动设计分层架构与微服务代码模型

    (一)代码模型总目录结构

    1.微服务一级目录结构

    2.用户接口层目录结构、职能和代码形态

    3.应用层目录结构、职能和代码形态

    4.领域层目录结构、职能和代码形态

    5.基础层层目录结构、职能和代码形态

    (二)应用层的领域对象分析

    1.实体方法的封装

    2.领域服务的组合和封装

    3.应用服务的组合和编排

    (三)领域层的领域对象分析

    1.设计实体

    2.找出聚合根

    3.设计值对象

    4.设计领域事件

    5.设计领域服务

    6.设计仓储

    (四)代码模型强调内容

    第一点:聚合之间的代码边界一定要清晰。

    第二点:你一定要有代码分层的概念。

    三、正确理解微服务的边界

    (一)逻辑边界

    (二)物理边界

    (三)代码边界

    四、正确认识服务和数据在微服务各层的协作

    (一)正确认识服务的协作

    1. 服务的类型

    2. 服务的调用(三类主要场景)

    微服务内跨层服务调用

    微服务之间的服务调用

    领域事件驱动

    3. 服务的封装与组合

    (二)正确认识服务数据的协作

    1.基础层数据协作

    2.领域层数据协作

    3.应用层数据协作

    4.用户接口层数据协作

    5.前端应用数据协作

    参考书籍、文献和资料


    领域驱动实践总结二:架构分析与代码设计

    领域驱动设计DDD是一种设计思想,它可以同时指导中台业务建模和微服务设计(中台本质是业务模型,微服务是业务模型的系统落地),领域驱动设计强调领域模型和微服务设计的一体性,先有领域模型然后才有微服务,而不是脱离领域模型来谈微服务设计。

    微服务拆分困境产生的根本原因:不知道业务或者微服务的边界到底在什么地方。

    DDD 核心思想:通过领域驱动设计方法定义领域模型,从而确定业务和应用边界,保证业务模型与代码模型的一致性。

    对于领域驱动设计的学习做的总结主要写三篇博客,主要包括三部分:基本理论总结与分析、架构分析与代码设计、具体应用设计分析,主要参考的资料为极客时间的欧创新架构师的《DDD》实战,其他参考书籍在文章下方的参考书籍中。

    本次主要总结DDD架构分析与代码设计:

    一、微服务架构模型的对比与选择

    微服务架构模型现有的选择模型包括:整洁架构、CQRS 和六边形架构、DDD 分层架构等。

    (注:CQRS架构之前博客中有讲,本次不做分析)

    每种架构模式虽然提出的时代和背景不同,但其核心理念都是为了设计出“高内聚低耦合”的架构,轻松实现架构演进。

    DDD 分层架构的思想使架构边界变得越来越清晰,它在微服务架构模型中,占有非常重要的位置。建议选择DDD 分层架构

    (一)整洁架构

    在整洁架构里,同心圆代表应用软件的不同部分,从里到外依次是领域模型、领域服务、应用服务和最外围的容易变化的内容,比如用户界面和基础设施。

    整洁架构最主要的原则是依赖原则,它定义了各层的依赖关系,越往里依赖越低,代码级别越高,越是核心能力。外圆代码依赖只能指向内圆,内圆不需要知道外圆的任何情况。

    (二)六边形架构

    六边形架构的核心理念是:应用是通过端口与外部进行交互的。

    也就是说,在下图的六边形架构中,红圈内的核心业务逻辑(应用程序和领域模型)与外部资源(包括 APP、Web 应用以及数据库资源等)完全隔离,仅通过适配器进行交互。它解决了业务逻辑与用户界面的代码交错问题,很好地实现了前后端分离。

    六边形架构各层的依赖关系与整洁架构一样,都是由外向内依赖。

    六边形架构的一个端口可能对应多个外部系统,不同的外部系统也可能会使用不同的适配器,由适配器负责协议转换。这就使得应用程序能够以一致的方式被用户、程序、自动化测试和批处理脚本使用。

    (三)DDD 分层架构

    从上到下依次是:用户接口层、应用层、领域层和基础层。

    1.用户接口层

    用户接口层负责向用户显示信息和解释用户指令。

    这里的用户可能是:用户、程序、自动化测试和批处理脚本等等。

    2.应用层

    应用层是很薄的一层,理论上不应该有业务规则或逻辑,主要面向用例和流程相关的操作

    位于领域层之上,领域层包含多个聚合,所以它可以协调多个聚合的服务和领域对象完成服务编排和组合,协作完成业务操作。

    应用层也是微服务之间交互的通道,它可以调用其它微服务的应用服务,完成微服务之间的服务组合和编排。

    注意

    • 在设计和开发时,不要将本该放在领域层的业务逻辑放到应用层中实现。因为庞大的应用层会使领域模型失焦,时间一长你的微服务就会演化为传统的三层架构,业务逻辑会变得混乱。
    • 应用服务是在应用层的,它负责服务的组合、编排和转发,负责处理业务用例的执行顺序以及结果的拼装,以粗粒度的服务通过 API 网关向前端发布。
    • 应用服务还可以进行安全认证、权限校验、事务控制、发送或订阅领域事件等。

    3.领域层

    领域层的作用是实现企业核心业务逻辑,通过各种校验手段保证业务的正确性

    领域层主要体现领域模型的业务能力,它用来表达业务概念、业务状态和业务规则。

    领域层包含聚合根、实体、值对象、领域服务等领域模型中的领域对象。

    注意:

    • 领域模型的业务逻辑主要是由实体和领域服务来实现的,其中实体会采用充血模型来实现所有与之相关的业务功能。
    • 实体和领域服务在实现业务逻辑上不是同级的,当领域中的某些功能,单一实体(或者值对象)不能实现时,领域服务就会出马,它可以组合聚合内的多个实体(或者值对象),实现复杂的业务逻辑。

    4.基础层

    基础层是贯穿所有层的,它的作用就是为其它各层提供通用的技术和基础服务,包括第三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等。比较常见的功能还是提供数据库持久化。

    基础层包含基础服务,它采用依赖倒置设计,封装基础资源服务,实现应用层、领域层与基础层的解耦,降低外部资源变化对应用的影响。

    5.从三层架构向 DDD 分层架构演进

    DDD 分层架构中的要素其实和三层架构类似,只是在 DDD 分层架构中,这些要素被重新归类,重新划分了层,确定了层与层之间的交互规则和职责边界。

    • 三层架构向 DDD 分层架构演进,主要发生在业务逻辑层和数据访问层
    • DDD 分层架构在用户接口层引入了 DTO,给前端提供了更多的可使用数据和更高的展示灵活性。
    • DDD 分层架构对三层架构的业务逻辑层进行了更清晰的划分,改善了三层架构核心业务逻辑混乱,代码改动相互影响大的情况。
    • DDD 分层架构将业务逻辑层的服务拆分到了应用层和领域层应用层快速响应前端的变化领域层实现领域模型的能力
    • 数据访问层和基础层之间三层架构数据访问采用 DAO 方式DDD 分层架构的数据库等基础资源访问,采用了仓储(Repository)设计模式,通过依赖倒置实现各层对基础资源的解耦。仓储又分为两部分:仓储接口和仓储实现仓储接口放在领域层中,仓储实现放在基础层。原来三层架构通用的第三方工具包、驱动、Common、Utility、Config 等通用的公共的资源类统一放到了基础层。

    (四)三种微服务架构模型的对比和分析

    • 重点关注图中的红色线框,它们是非常重要的分界线,这三种架构里面都有,它的作用就是将核心业务逻辑与外部应用、基础资源进行隔离。
    • 红色框内部主要实现核心业务逻辑,划分了应用层和领域层,来承担不同的业务逻辑。
    • 领域层实现面向领域模型,实现领域模型的核心业务逻辑,属于原子模型,它需要保持领域模型和业务逻辑的稳定,对外提供稳定的细粒度的领域服务,所以它处于架构的核心位置。
    • 应用层实现面向用户操作相关的用例和流程,对外提供粗粒度的 API 服务。它就像一个齿轮一样进行前台应用和领域层的适配,接收前台需求,随时做出响应和调整,尽量避免将前台需求传导到领域层。应用层作为配速齿轮则位于前台应用和领域层之间。

    二、领域驱动设计分层架构与微服务代码模型

    DDD 并没有给出标准的代码模型,不同的人可能会有不同理解。这里我们在使用的时候还是建议按照欧创新架构师总结的来进行适用,具体如下:

    (一)代码模型总目录结构

    根据 DDD 分层架构模型建立了标准的微服务代码模型,在代码模型里面,各代码对象各据其位、各司其职,共同协作完成微服务的业务逻辑。它包括用户接口层、应用层、领域层和基础层,分层架构各层的职责边界非常清晰,又能有条不紊地分层协作。

    • 用户接口层:面向前端提供服务适配,面向资源层提供资源适配。这一层聚集了接口适配相关的功能。
    • 应用层职责:实现服务组合和编排,适应业务流程快速变化的需求。这一层聚集了应用服务和事件相关的功能。
    • 领域层:实现领域的核心业务逻辑。这一层聚集了领域模型的聚合、聚合根、实体、值对象、领域服务和事件等领域对象,以及它们组合所形成的业务能力。
    • 基础层:贯穿所有层,为各层提供基础资源服务。这一层聚集了各种底层资源相关的服务和能力。

    业务逻辑从领域层、应用层到用户接口层逐层封装和协作,对外提供灵活的服务,既实现了各层的分工,又实现了各层的协作。

    1.微服务一级目录结构

    微服务一级目录是按照 DDD 分层架构的分层职责来定义的。从下面这张图中,我们可以看到,在代码模型里分别为用户接口层、应用层、领域层和基础层,建立了 interfaces、application、domain 和 infrastructure 四个一级代码目录。

    2.用户接口层目录结构、职能和代码形态

    主要存放用户接口层与前端交互、展现数据相关的代码。前端应用通过这一层的接口,向应用服务获取展现所需的数据。这一层主要用来处理用户发送的 Restful 请求,解析用户输入的配置文件,并将数据传递给 Application 层。数据的组装、数据传输格式以及 Facade 接口等代码都会放在这一层目录里。

    具体如下:

    • Assembler:实现 DTO 与领域对象之间的相互转换和数据交换。一般来说 Assembler 与 DTO 总是一同出现。
    • Dto:它是数据传输的载体,内部不存在任何业务逻辑,我们可以通过 DTO 把内部的领域对象与外界隔离。
    • Facade:提供较粗粒度的调用接口,将用户请求委派给一个或多个应用服务进行处理。

    3.应用层目录结构、职能和代码形态

    主要存放应用层服务组合和编排相关的代码。应用服务向下基于微服务内的领域服务或外部微服务的应用服务完成服务的编排和组合向上为用户接口层提供各种应用数据展现支持服务应用服务和事件等代码会放在这一层目录里

    具体如下:

    • Event(事件):这层目录主要存放事件相关的代码。它包括两个子目录:publish 和 subscribe。前者主要存放事件发布相关代码,后者主要存放事件订阅相关代码(事件处理相关的核心业务逻辑在领域层实现)。为了实现事件的统一管理,建议你将微服务内所有事件的发布和订阅的处理都统一放到应用层,事件相关的核心业务逻辑实现放在领域层。通过应用层调用领域层服务,来实现完整的事件发布和订阅处理流程。
    • Service(应用服务):这层的服务是应用服务。应用服务会对多个领域服务或外部应用服务进行封装、编排和组合,对外提供粗粒度的服务。应用服务主要实现服务组合和编排,是一段独立的业务逻辑。你可以将所有应用服务放在一个应用服务类里,也可以把一个应用服务设计为一个应用服务类,以防应用服务类代码量过大。

    4.领域层目录结构、职能和代码形态

    主要存放领域层核心业务逻辑相关的代码。领域层可以包含多个聚合代码包,它们共同实现领域模型的核心业务逻辑。聚合以及聚合内的实体、方法、领域服务和事件等代码会放在这一层目录里。

    具体如下:

    • Aggregate(聚合):它是聚合软件包的根目录,可以根据实际项目的聚合名称命名,比如权限聚合。在聚合内定义聚合根、实体和值对象以及领域服务之间的关系和边界。聚合内实现高内聚的业务逻辑,它的代码可以独立拆分为微服务。以聚合为单位的代码放在一个包里的主要目的是为了业务内聚,而更大的目的是为了以后微服务之间聚合的重组。聚合之间清晰的代码边界,可以让你轻松地实现以聚合为单位的微服务重组,在微服务架构演进中有着很重要的作用。
    • Entity(实体):它存放聚合根、实体、值对象以及工厂模式(Factory)相关代码。实体类采用充血模型,同一实体相关的业务逻辑都在实体类代码中实现。跨实体的业务逻辑代码在领域服务中实现
    • Event(事件):它存放事件实体以及与事件活动相关的业务逻辑代码
    • Service(领域服务):它存放领域服务代码一个领域服务是多个实体组合出来的一段业务逻辑。你可以将聚合内所有领域服务都放在一个领域服务类中,你也可以把每一个领域服务设计为一个类。如果领域服务内的业务逻辑相对复杂,建议将一个领域服务设计为一个领域服务类,避免由于所有领域服务代码都放在一个领域服务类中,而出现代码臃肿的问题。领域服务封装多个实体或方法后向上层提供应用服务调用
    • Repository(仓储):它存放所在聚合的查询或持久化领域对象的代码,通常包括仓储接口和仓储实现方法。为了方便聚合的拆分和组合,我们设定了一个原则:一个聚合对应一个仓储。(特别说明:按照 DDD 分层架构,仓储实现本应该属于基础层代码,但为了在微服务架构演进时,保证代码拆分和重组的便利性,把聚合仓储实现的代码放到了聚合包内。)

    5.基础层层目录结构、职能和代码形态

    主要存放基础资源服务相关的代码,为其它各层提供的通用技术能力、三方软件包、数据库服务、配置和基础资源服务的代码都会放在这一层目录里。

    具体如下:

    • Config:主要存放配置相关代码。
    • Util:主要存放平台、开发框架、消息、数据库、缓存、文件、总线、网关、第三方类库、通用算法等基础代码,你可以为不同的资源类别建立不同的子目录。

    (二)应用层的领域对象分析

    应用层的主要领域对象是应用服务和事件的发布以及订阅。

    在事件风暴或领域故事分析时,我们往往会根据用户或系统发起的命令,来设计服务或实体方法。为了响应这个命令,我们需要分析和记录:

    • 在应用层和领域层分别会发生哪些业务行为;
    • 各层分别需要设计哪些服务或者方法;
    • 这些方法和服务的分层以及领域类型(比如实体方法、领域服务和应用服务等),它们之间的调用和组合的依赖关系。

    在严格分层架构模式下,不允许服务的跨层调用,每个服务只能调用它的下一层服务。服务从下到上依次为:实体方法、领域服务和应用服务。建议采用服务逐层封装的方式,服务的封装和调用主要有以下几种方式:

    1.实体方法的封装

    实体方法是最底层的原子业务逻辑。

    • 如果单一实体的方法需要被跨层调用,你可以将它封装成领域服务,这样封装的领域服务就可以被应用服务调用和编排了。
    • 如果它还需要被用户接口层调用,需要将这个领域服务封装成应用服务

    经过逐层服务封装,实体方法就可以暴露给上面不同的层,实现跨层调用。

    封装时服务前面的名字可以保持一致,你可以用 *DomainService 或 *AppService 后缀来区分领域服务或应用服务

    2.领域服务的组合和封装

    领域服务会对多个实体和实体方法进行组合和编排,供应用服务调用。

    如果它需要暴露给用户接口层,领域服务就需要封装成应用服务

    3.应用服务的组合和编排

    应用服务会对多个领域服务进行组合和编排,暴露给用户接口层,供前端应用调用。

    多个应用服务可能会对多个同样的领域服务重复进行同样业务逻辑的组合和编排。当出现这种情况时,就需要分析是不是领域服务可以整合了。可以将这几个不断重复组合的领域服务,合并到一个领域服务中实现,这样领域模型将会越来越精炼,更能适应业务的要求。

    应用服务类放在应用层 Service 目录结构下。领域事件的发布和订阅类放在应用层 Event 目录结构下。

    (三)领域层的领域对象分析

    事件风暴结束时,领域模型聚合内一般会有:聚合、实体、命令和领域事件等领域对象

    在完成故事分析和微服务设计后,微服务的聚合内一般会有:聚合、聚合根、实体、值对象、领域事件、领域服务和仓储等领域对象。

    1.设计实体

    大多数情况下,领域模型的业务实体与微服务的数据库实体是一一对应的。

    但某些领域模型的实体在微服务设计时,可能会被设计为多个数据实体,或者实体的某些属性被设计为值对象。

    在分层架构里,实体采用充血模型,在实体类内实现实体的全部业务逻辑。这些不同的实体都有自己的方法和业务行为,比如地址实体有新增和修改地址的方法,银行账号实体有新增和修改银行账号的方法。

    实体类放在领域层的 Entity 目录结构下。

    2.找出聚合根

    聚合根来源于领域模型,聚合根是一种特殊的实体,它有自己的属性和方法聚合根可以实现聚合之间的对象引用,还可以引用聚合内的所有实体。

    • 在个人客户聚合里,个人客户这个实体是聚合根,它负责管理地址、电话以及银行账号的生命周期。
    • 个人客户聚合根通过工厂和仓储模式,实现聚合内地址、银行账号等实体和值对象数据的初始化和持久化。

    聚合根类放在代码模型的 Entity 目录结构下。聚合根有自己的实现方法,比如生成客户编码,新增和修改客户信息等方法。

    3.设计值对象

    根据需要将某些实体的某些属性或属性集设计为值对象。值对象类放在代码模型的 Entity 目录结构下。

    在个人客户聚合中,客户拥有客户证件类型,它是以枚举值的形式存在,所以将它设计为值对象。

    有些领域对象可以设计为值对象,也可以设计为实体,我们需要根据具体情况来分析:

    • 如果这个领域对象在其它聚合内维护生命周期,且在它依附的实体对象中只允许整体替换,我们就可以将它设计为值对象。
    • 如果这个对象是多条且需要基于它做查询统计,建议将它设计为实体。

    4.设计领域事件

    如果领域模型中领域事件会触发下一步的业务操作,我们就需要设计领域事件

    • 首先确定领域事件发生在微服务内还是微服务之间。
    • 然后设计事件实体对象,事件的发布和订阅机制,以及事件的处理机制。
    • 判断是否需要引入事件总线或消息中间件

    领域事件实体和处理类放在领域层的 Event 目录结构下。领域事件的发布和订阅类建议放在应用层的 Event 目录结构下。

    5.设计领域服务

    如果一个业务动作或行为跨多个实体,我们就需要设计领域服务。

    领域服务通过对多个实体和实体方法进行组合,完成核心业务逻辑。可以认为领域服务是位于实体方法之上和应用服务之下的一层业务逻辑。

    按照严格分层架构层的依赖关系

    • 如果实体的方法需要暴露给应用层,它需要封装成领域服务后才可以被应用服务调用。
    • 如果有的实体方法需要被前端应用调用,我们会将它封装成领域服务,然后再封装为应用服务。

    领域服务类放在领域层的 Service 目录结构下。

    6.设计仓储

    每一个聚合都有一个仓储,仓储主要用来完成数据查询和持久化操作。

    仓储包括仓储的接口和仓储实现,通过依赖倒置实现应用业务逻辑与数据库资源逻辑的解耦。

    仓储代码放在领域层的 Repository 目录结构下。

    (四)代码模型强调内容

    第一点:聚合之间的代码边界一定要清晰

    聚合之间的服务调用和数据关联应该是尽可能的松耦合和低关联,聚合之间的服务调用应该通过上层的应用层组合实现调用,原则上不允许聚合之间直接调用领域服务。

    这种松耦合的代码关联,在以后业务发展和需求变更时,可以很方便地实现业务功能和聚合代码的重组,在微服务架构演进中将会起到非常重要的作用。

    第二点:你一定要有代码分层的概念

    写代码时一定要搞清楚代码的职责,将它放在职责对应的代码目录内。

    应用层代码主要完成服务组合和编排,以及聚合之间的协作,它是很薄的一层,不应该有核心领域逻辑代码。

    领域层是业务的核心,领域模型的核心逻辑代码一定要在领域层实现。

    如果将核心领域逻辑代码放到应用层,你的基于 DDD 分层架构模型的微服务慢慢就会演变成传统的三层架构模型了。

    三、正确理解微服务的边界

    微服务设计的重点,就是看微服务设计是否能够支持架构长期、轻松的演进。

    在事件风暴中,我们会梳理出业务过程中的用户操作、事件以及外部依赖关系等,根据这些要素梳理出实体等领域对象。

    • 根据实体对象之间的业务关联性,将业务紧密相关的多个实体进行组合形成聚合,聚合之间是第一层边界。
    • 根据业务及语义边界等因素将一个或者多个聚合划定在一个限界上下文内,形成领域模型,限界上下文之间的边界是第二层边界。

    为了方便理解,我们将这些边界细分为:逻辑边界、物理边界和代码边界。

    (一)逻辑边界

    逻辑边界主要定义同一业务领域或应用内紧密关联的对象所组成的不同聚类的组合之间的边界

    微服务内聚合之间的边界就是逻辑边界。一般来说微服务会有一个以上的聚合,在开发过程中不同聚合的代码隔离在不同的聚合代码目录中。它是一个虚拟的边界,强调业务的内聚,可根据需要变成物理边界,也就是说聚合也可以独立为微服务。

    微服务的架构演进并不是随心所欲的,需要遵循一定的规则,这个规则就是逻辑边界。微服务架构演进时,在业务端以聚合为单位进行业务能力的重组,在微服务端以聚合的代码目录为单位进行微服务代码的重组。

    来看一个微服务实例,在下面这张图中,我们可以看到微服务里包含了两个聚合的业务逻辑,两个聚合分别内聚了各自不同的业务能力,聚合内的代码分别归到了不同的聚合目录下。

    那随着业务的快速发展,如果某一个微服务遇到了高性能挑战,需要将部分业务能力独立出去,我们就可以以聚合为单位,将聚合代码拆分独立为一个新的微服务,这样就可以很容易地实现微服务的拆分。

    另外,我们也可以对多个微服务内有相似功能的聚合进行功能和代码重组,组合为新的聚合和微服务,独立为通用微服务,有点做中台的感觉!

    (二)物理边界

    微服务之间的边界是物理边界。它强调微服务部署和运行的隔离,关注微服务的服务调用、容错和运行等。

    物理边界主要从部署和运行的视角来定义微服务之间的边界。不同微服务部署位置和运行环境是相互物理隔离的,分别运行在不同的进程中。这种边界就是微服务之间的物理边界。

    举例:

    有些项目团队在将集中式单体应用拆分为微服务时,首先进行的往往不是建立领域模型,而只是按照业务功能将原来单体应用的一个软件包拆分成多个所谓的“微服务”软件包,而这些“微服务”内的代码仍然是集中式三层架构的模式,“微服务”内的代码高度耦合,逻辑边界不清晰,这里我们暂且称它为“小单体微服务”

    而随着新需求的提出和业务的发展,这些小单体微服务会慢慢膨胀起来。当有一天你发现这些膨胀了的微服务,有一部分业务功能需要拆分出去,或者部分功能需要与其它微服务进行重组时,你会发现原来这些看似清晰的微服务,不知不觉已经摇身一变,变成了臃肿油腻的大单体了,而这个大单体内的代码依然是高度耦合且边界不清的。

    这种单体式微服务只定义了一个维度的边界,也就是微服务之间的物理边界,本质上还是单体架构模式。微服务设计时要考虑的不仅仅只有这一个边界,别忘了还要定义好微服务内的逻辑边界和代码边界,这样才能得到你想要的结果。

    (三)代码边界

    不同层或者聚合之间代码目录的边界是代码边界。它强调的是代码之间的隔离,方便架构演进时代码的重组。

    代码边界主要用于微服务内的不同职能代码之间的隔离

    微服务开发过程中会根据代码模型建立相应的代码目录,实现不同功能代码的隔离。由于领域模型与代码模型的映射关系,代码边界直接体现出业务边界。

    代码边界可以控制代码重组的影响范围,避免业务和服务之间的相互影响。

    微服务如果需要进行功能重组,只需要以聚合代码为单位进行重组就可以了。

    四、正确认识服务和数据在微服务各层的协作

    (一)正确认识服务的协作

    1. 服务的类型

    按照分层架构设计出来的微服务,其内部有 Facade 服务、应用服务、领域服务和基础服务。之前都有提到,这里可以回忆一下!

    2. 服务的调用(三类主要场景)

    微服务的服务调用包括三类主要场景:微服务内跨层服务调用,微服务之间服务调用和领域事件驱动。

    • 微服务内跨层服务调用

    前端应用调用发布在 API 网关上的 Facade 服务,Facade 定向到应用服务应用服务作为服务组织和编排者,它的服务调用有这样两种路径:

    • 第一种是应用服务调用并组装领域服务。此时领域服务会组装实体和实体方法,实现核心领域逻辑。领域服务通过仓储服务获取持久化数据对象,完成实体数据初始化。
    • 第二种是应用服务直接调用仓储服务。这种方式主要针对像缓存、文件等类型的基础层数据访问。这类数据主要是查询操作,没有太多的领域逻辑,不经过领域层,不涉及数据库持久化对象。

     

    • 微服务之间的服务调用

    微服务之间的应用服务可以直接访问,也可以通过 API 网关访问

    由于跨微服务操作,在进行数据新增和修改操作时,你需关注分布式事务,保证数据的一致性。

    • 领域事件驱动

    领域事件驱动包括微服务内和微服务之间的事件。

    • 微服务内通过事件总线(EventBus)完成聚合之间的异步处理。
    • 微服务之间通过消息中间件完成。异步化的领域事件驱动机制是一种间接的服务访问方式。

    当应用服务业务逻辑处理完成后,如果发生领域事件,可调用事件发布服务,完成事件发布。

    当接收到订阅的主题数据时,事件订阅服务会调用事件处理领域服务,完成进一步的业务操作。

    3. 服务的封装与组合

    微服务的服务是从领域层逐级向上封装、组合和暴露的。基本如下图体现:

    (二)正确认识服务数据的协作

    在 DDD 中有很多的数据对象,这些对象分布在不同的层里。它们在不同的阶段有不同的形态:

    • 数据持久化对象 PO(Persistent Object),与数据库结构一一映射,是数据持久化过程中的数据载体。
    • 领域对象 DO(Domain Object),微服务运行时的实体,是核心业务的载体。
    • 数据传输对象 DTO(Data Transfer Object),用于前端与应用层或者微服务之间的数据组装和传输,是应用之间数据传输的载体。
    • 视图对象 VO(View Object),用于封装展示层指定页面或组件的数据。

    微服务各层数据对象的职责和转换过程如下:

    1.基础层数据协作

    基础层的主要对象是 PO 对象。

    我们需要先建立 DO 和 PO 的映射关系

    • 当 DO 数据需要持久化时,仓储服务会将 DO 转换为 PO 对象,完成数据库持久化操作。
    • 当 DO 数据需要初始化时,仓储服务从数据库获取数据形成 PO 对象,并将 PO 转换为 DO,完成数据初始化。

    大多数情况下 PO 和 DO 是一一对应的。但也有 DO 和 PO 多对多的情况,在 DO 和 PO 数据转换时,需要进行数据重组。

    2.领域层数据协作

    领域层的主要对象是 DO 对象。

    DO 是实体和值对象的数据和业务行为载体,承载着基础的核心业务逻辑

    通过 DO 和 PO 转换,我们可以完成数据持久化和初始化。

    3.应用层数据协作

    应用层的主要对象是 DO 对象。

    如果需要调用其它微服务的应用服务,DO 会转换为 DTO,完成跨微服务的数据组装和传输。

    用户接口层先完成 DTO 到 DO 的转换,然后应用服务接收 DO 进行业务处理。如果 DTO 与 DO 是一对多的关系,这时就需要进行 DO 数据重组

    4.用户接口层数据协作

    用户接口层会完成 DO 和 DTO 的互转,完成微服务与前端应用数据交互及转换。

    Facade 服务会对多个 DO 对象进行组装,转换为 DTO 对象,向前端应用完成数据转换和传输。

    5.前端应用数据协作

    前端应用主要是 VO 对象

    展现层使用 VO 进行界面展示,通过用户接口层与应用层采用 DTO 对象进行数据交互。

     

    参考书籍、文献和资料

    1.极客时间课程《DDD实战》,欧创新,2019.

    2.郑天民. 微服务设计原理与架构. 北京:人民邮电出版社,2018.

    3.陈超、秦金卫、张逸等. 高可用可伸缩微服务架构. 电子工业出版社. 2019.

    4.Eric Evans. 领域驱动设计-软件核心复杂性应对之道。 人民邮电出版社. 2018.

    展开全文
  • 领域驱动实践总结三:具体应用设计分析 领域驱动设计DDD是一种设计思想,它可以同时指导中台业务建模和微服务设计(中台本质是业务模型,微服务是业务模型的系统落地),领域驱动设计强调领域模型和微服务设计的一体性...

    目录

    领域驱动实践总结三:具体应用设计分析

    一、应用项目的基本背景

    二、针对项目进行领域驱动的战略设计阶段

    (一)事件风暴确定产品愿景

    (二)事件风暴进行业务场景分析

    场景分析一:请假       用户:请假人

    场景分析二:审批       用户:审批人

    场景分析三:人员组织关系     详细的分析过程以及考勤的场景分析就不描述了

    (三)事件风暴进行领域建模

    第一步:找出领域实体和值对象等领域对象

    第二步:找出聚合根,根据实体、值对象与聚合根的依赖关系,建立聚合

    第三步:根据业务及语义边界等因素,定义限界上下文

    (四)事件风暴进行微服务的拆分

    三、针对项目进行领域驱动的战术设计阶段

    (一)分析微服务领域对象

    1.具体服务的识别和设计

    2.聚合中的对象分析

    3.微服务内的对象清单

    (二)设计微服务代码结构

    1.应用层代码结构

    2.领域层代码结构

    (三)后续的工作:详细设计 + 代码开发和测试

    1. 详细设计

    2. 代码开发和测试

    四、具体代码参考

    参考书籍、文献和资料


    领域驱动实践总结三:具体应用设计分析

    领域驱动设计DDD是一种设计思想,它可以同时指导中台业务建模和微服务设计(中台本质是业务模型,微服务是业务模型的系统落地),领域驱动设计强调领域模型和微服务设计的一体性,先有领域模型然后才有微服务,而不是脱离领域模型来谈微服务设计。

    微服务拆分困境产生的根本原因:不知道业务或者微服务的边界到底在什么地方。

    DDD 核心思想:通过领域驱动设计方法定义领域模型,从而确定业务和应用边界,保证业务模型与代码模型的一致性。

    对于领域驱动设计的学习做的总结主要写三篇博客,主要包括三部分:基本理论总结与分析、架构分析与代码设计、具体应用设计分析,主要参考的资料为极客时间的欧创新架构师的《DDD》实战,其他参考书籍在文章下方的参考书籍中。

    本次主要总结DDD具体应用设计分析:

    一、应用项目的基本背景

    项目的目标是实现在线请假和考勤管理功能描述如下

    • 请假人填写请假单提交审批,根据请假人身份、请假类型和请假天数进行校验,根据审批规则逐级递交上级审批,逐级核批通过则完成审批,否则审批不通过退回申请人。
    • 根据考勤规则,核销请假数据后,对考勤数据进行校验,输出考勤统计。

    备注:本想以大视频业务系统为背景自己弄一个的,但是时间太紧,暂时还是以欧创新架构师提供的案例做总结和分析理解,后期有时间会自己出一个关于视频系统的。

    二、针对项目进行领域驱动的战略设计阶段

    战略设计采用的方法是事件风暴,包括:产品愿景、场景分析、领域建模和微服务拆分等几个主要过程。

    战略设计是根据用户旅程分析,找出领域对象和聚合根,对实体和值对象进行聚类组成聚合,划分限界上下文,建立领域模型的过程。

    战略设计阶段建议参与人员:领域专家、业务需求方、产品经理、架构师、项目经理、开发经理和测试经理。

    (一)事件风暴确定产品愿景

    产品愿景是对产品顶层价值设计,对产品目标用户、核心价值、差异化竞争点等信息达成一致,避免产品偏离方向

    事件风暴时,所有参与者针对每一个要点,在贴纸上写出自己的意见,贴到白板上。

    事件风暴主持者会对每个贴纸,讨论并对发散的意见进行收敛和统一,形成下面的产品愿景图。其实,也就是让所有人确定统一的大方向统一语言,我们究竟在干什么,目的和意义是什么等的基本问题。

    产品愿景分析对于初创系统明确系统建设重点,统一团队建设目标和建立通用语言是很有价值的。

    针对本项目,最后确定的产品愿景图整理成一段文字就是

    为了满足内外部人员,他们的在线请假、自动考勤统计和外部人员管理的需求,我们建设这个在线请假考勤系统,它是一个在线请假平台,可以自动考勤统计。它可以同时支持内外网请假,同时管理内外部人员请假和定期考勤分析,而不像 HR 系统,只管理内部人员,且只能内网使用。我们的产品内外网皆可使用,可实现内外部人员无差异管理。

    达到的目的与意义

    通过产品愿景分析,项目团队统一了系统名称——在线请假考勤系统,明确了项目目标和关键功能,与竞品(HR)的关键差异以及自己的优势和核心竞争力等。

    (二)事件风暴进行业务场景分析

    场景分析是从用户视角出发,探索业务领域中的典型场景,产出领域中需要支撑的场景分类、用例操作以及不同子域之间的依赖关系,用以支撑领域建模。

    项目团队成员一起用事件风暴分析请假和考勤的用户旅程。

    根据不同角色的旅程和场景分析,尽可能全面地梳理从前端操作到后端业务逻辑发生的所有操作、命令、领域事件以及外部依赖关系等信息

    以请假和人员场景作为示例------------

    场景分析一:请假       用户:请假人

    • 请假人登录系统:从权限微服务获取请假人信息和权限数据,完成登录认证。
    • 创建请假单:打开请假页面,选择请假类型和起始时间,录入请假信息。保存并创建请假单,提交请假审批。
    • 修改请假单:查询请假单,打开请假页面,修改请假单,提交请假审批。
    • 提交审批:获取审批规则,根据审批规则,从人员组织关系中获取审批人,给请假单分配审批人。

    场景分析二:审批       用户:审批人

    • 审批人登录系统:从权限微服务获取审批人信息和权限数据,完成登录认证。
    • 获取请假单:获取审批人名下请假单,选择请假单。
    • 审批:填写审批意见。
    • 逐级审批:如果还需要上级审批,根据审批规则,从人员组织关系中获取审批人,给请假单分配审批人。重复以上 4 步。最后审批人完成审批。

    备注:完成审批后,产生请假审批已通过领域事件。后续有两个进一步的业务操作:发送请假审批已通过的通知,通知邮件系统告知请假人;将请假数据发送到考勤以便核销。

    场景分析三:人员组织关系     详细的分析过程以及考勤的场景分析就不描述了

    (三)事件风暴进行领域建模

    领域建模是通过对业务和问题域进行分析,建立领域模型。

    向上通过限界上下文指导微服务边界设计,向下通过聚合指导实体对象设计

    领域建模是一个收敛的过程,分三步:

    第一步:找出领域实体和值对象等领域对象

    根据场景分析,分析并找出发起或产生这些命令或领域事件的实体和值对象,将与实体或值对象有关的命令和事件聚集到实体

    根据场景分析,分析并找出发起或产生这些命令或领域事件的实体和值对象,将与实体或值对象有关的命令和事件聚集到实体。

    注意表达中的名词和动词等,名词往往是实体、值对象等,动词往往对应着命令的相关行为

    第二步:找出聚合根,根据实体、值对象与聚合根的依赖关系,建立聚合

    定义聚合前,先找出聚合根

    从上面的实体中,我们可以找出“请假单”和“人员”两个聚合根然后找出与聚合根紧密依赖的实体和值对象。我们发现审批意见、审批规则和请假单紧密关联,组织关系和人员紧密关联。

    找出这些实体的关系后,我们发现还有刷卡明细、考勤明细和考勤统计,这几个实体没有聚合根。这种情形在领域建模时你会经常遇到,对于这类场景我们需要分情况特殊处理:

    • 刷卡明细、考勤明细和考勤统计这几个实体,它们之间相互独立,找不出聚合根,不是富领域模型,但它们一起完成考勤业务逻辑,具有很高的业务内聚性。
    • 我们将这几个业务关联紧密的实体,放在一个考勤聚合内。
    • 微服务设计时,我们依然采用 DDD 的设计和分析方法。由于没有聚合根来管理聚合内的实体,我们可以用传统的方法来管理实体

    经过分析,我们建立了请假、人员组织关系和考勤三个聚合。其中请假聚合有请假单、审批意见实体和审批规则等值对象。人员组织关系聚合有人员和组织关系等实体。考勤聚合有刷卡明细、考勤明细和考勤统计等实体。

    第三步:根据业务及语义边界等因素,定义限界上下文

    由于人员组织关系聚合与请假聚合,共同完成请假的业务功能,两者在请假的限界上下文内。

    考勤聚合则单独构成考勤统计限界上下文。

    因此我们为业务划分请假和考勤统计两个限界上下文,建立请假和考勤两个领域模型。

    (四)事件风暴进行微服务的拆分

    理论上一个限界上下文就可以设计为一个微服务,但还需要综合考虑多种外部因素,比如:职责单一性、敏态与稳态业务分离、非功能性需求(如弹性伸缩、版本发布频率和安全等要求)、软件包大小、团队沟通效率和技术异构等非业务要素。

    在这个项目,我们划分微服务主要考虑职责单一性原则

    因此根据限界上下文就可以拆分为请假和考勤两个微服务。其中请假微服务包含人员组织关系和请假两个聚合,考勤微服务包含考勤聚合。

    三、针对项目进行领域驱动的战术设计阶段

    战术设计是根据领域模型进行微服务设计的过程。这个阶段主要梳理微服务内的领域对象,梳理领域对象之间的关系,确定它们在代码模型和分层架构中的位置,建立领域模型与微服务模型的映射关系,以及服务之间的依赖关系

    战术设计阶段建议参与人员:领域专家、产品经理、架构师、项目经理、开发经理和测试经理等。战术设计包括以下阶段:

    (一)分析微服务领域对象

    事件风暴基础上,我们进一步细化领域对象以及它们的关系,补充事件风暴可能遗漏的业务和技术细节

    • 我们分析微服务内应该有哪些服务?
    • 服务的分层?
    • 应用服务由哪些服务组合和编排完成?
    • 领域服务包括哪些实体和实体方法?
    • 哪个实体是聚合根?
    • 实体有哪些属性和方法?
    • 哪些对象应该设计为值对象等。

    1.具体服务的识别和设计

    事件风暴的命令是外部的一些操作和业务行为,也是微服务对外提供的能力。它往往与微服务的应用服务或者领域服务对应。我们可以将命令作为服务识别和设计的起点

    具体步骤如下:

    • 根据命令设计应用服务,确定应用服务的功能,服务集合,组合和编排方式。服务集合中的服务包括领域服务或其它微服务的应用服务。
    • 根据应用服务功能要求设计领域服务,定义领域服务。这里需要注意:应用服务可能是由多个聚合的领域服务组合而成的。
    • 根据领域服务的功能,确定领域服务内的实体以及功能。
    • 设计实体基本属性和方法。
    • 考虑领域事件的异步化处理。

    以提交审批这个动作为例,来说明服务的识别和设计。提交审批的大体流程是:

    • 根据请假类型和时长,查询请假审批规则,获取下一步审批人的角色。
    • 根据审批角色从人员组织关系中查询下一审批人。
    • 为请假单分配审批人,并将审批规则保存至请假单。

    通过分析,我们需要在应用层和领域层设计以下服务和方法。

    • 应用层:提交审批应用服务。
    • 领域层:领域服务有查询审批规则、修改请假流程信息服务以及根据审批规则查询审批人服务,分别位于请假和人员组织关系聚合。请假单实体有修改请假流程信息方法,审批规则值对象有查询审批规则方法。人员实体有根据审批规则查询审批人方法。下图是我们分析出来的服务以及它们之间的依赖关系。

    2.聚合中的对象分析

    请假单聚合中,聚合根是请假单

    • 请假单经多级审核后,会产生多条审批意见,为了方便查询,我们可以将审批意见设计为实体。
    • 请假审批通过后,会产生请假审批通过的领域事件,因此还会有请假事件实体。

    请假聚合有以下实体审批意见(记录审批人、审批状态和审批意见)和请假事件实体

    再来分析一下请假单聚合的值对象

    • 请假人和下一审批人数据来源于人员组织关系聚合中的人员实体,可设计为值对象。
    • 人员类型、请假类型和审批状态是枚举值类型,可设计为值对象。
    • 确定请假审批规则后,审批规则也可作为请假单的值对象。

    请假单聚合将包含以下值对象请假人、人员类型、请假类型、下一审批人、审批状态和审批规则

    -----------------------------------------------------------------------------------------------------------------------------------------------------

    人员组织关系聚合中,我们可以建立人员之间的组织关系,通过组织关系类型找到上级审批领导。

    它的聚合根是人员实体有组织关系(包括组织关系类型和上级审批领导),其中组织关系类型(如项目经理、处长、总经理等)是值对象。上级审批领导来源于人员聚合根,可设计为值对象。人员组织关系聚合将包含以下值对象:组织关系类型、上级审批领导。

    3.微服务内的对象清单

    确定各领域对象的属性后,我们就可以设计各领域对象在代码模型中的代码对象(包括代码对象的包名、类名和方法名),建立领域对象与代码对象的一一映射关系了。

    根据这种映射关系,相关人员可快速定位到业务逻辑所在的代码位置。

    (二)设计微服务代码结构

    根据 DDD 的代码模型和各领域对象所在的包、类和方法,可以定义出请假微服务的代码结构,设计代码对象。

    1.应用层代码结构

    应用层包括:应用服务、DTO 以及事件发布相关代码

    在 LeaveApplicationService 类内实现与聚合相关的应用服务,在 LoginApplicationService 封装外部微服务认证和权限的应用服务。

    (提醒一下:如果应用服务逻辑复杂的话,一个应用服务就可以构建一个类,这样可以避免一个类的代码过于庞大,不利于维护。)

    2.领域层代码结构

    领域层包括一个或多个聚合的实体类、事件实体类、领域服务以及工厂、仓储相关代码

    一个聚合对应一个聚合代码目录,聚合之间在代码上完全隔离,聚合之间通过应用层协调

    请假微服务领域层包含请假和人员两个聚合。人员和请假代码都放在各自的聚合所在目录结构的代码包中。

    如果随着业务发展,人员相关功能需要从请假微服务中拆分出来,我们只需将人员聚合代码包稍加改造,独立部署,即可快速发布为人员微服务。

    (三)后续的工作:详细设计 + 代码开发和测试

    1. 详细设计

    在完成领域模型和微服务设计后,我们还需要对微服务进行详细的设计。

    主要设计以下内容:实体属性、数据库表和字段、实体与数据库表映射、服务参数规约及功能实现等。

    2. 代码开发和测试

    开发人员只需要按照详细的设计文档和功能要求,找到业务功能对应的代码位置,完成代码开发就可以了。

    代码开发完成后,开发人员要编写单元测试用例,基于挡板模拟依赖对象完成服务测试。

    四、具体代码参考

    本次的总结都是按照极客时间课程《DDD实战》欧创新架构师的举例项目来进行的,相关代码可以克隆其代码:

    https://github.com/ouchuangxin/leave-sample.git

    后期在其基础上会有补充,以及对于视频系统的项目分析,后续博客中公布。

     

    参考书籍、文献和资料

    1.极客时间课程《DDD实战》,欧创新,2019.

    2.郑天民. 微服务设计原理与架构. 北京:人民邮电出版社,2018.

    展开全文
  • 领域驱动实践总结一:基本理论总结与分析 一、领域驱动设计两大设计:战略设计和战术设计 二、理解和分析领域+子域+核心域+通用域+支撑域 三、理解和分析界限上下文,定义领域边界 四、理解和分析实体和值...

    目录

    领域驱动实践总结一:基本理论总结与分析

    一、领域驱动设计两大设计:战略设计和战术设计

    (一)战略设计

    1.出发角度与目标

    2.实现方式:事件风暴与模型确立(用例分析、场景分析和用户旅程分析)

    3.用三步来划定领域模型和微服务的边界

    (二)战术设计

    1.出发角度与目标

    2.实现方式:DDD 分层架构、整洁架构、CQRS 和六边形架构等  (我们采用DDD 分层架构)

    3.代码模型强调两点:聚合之间的代码边界一定要清晰+一定要有代码分层的概念

    二、理解和分析领域+子域+核心域+通用域+支撑域

    (一)整体理解领域与子域的概念(以桃树生物学知识体系的建立为例来加深理解)

    (二)理解核心域、通用域和支撑域的划分及具体目的

    1.核心域

    2.通用域

    3.支撑域

    4.划分核心域、支撑域和通用域的主要目标

    三、理解和分析界限上下文,定义领域边界

    (一)通用语言

    (二)限界上下文

    四、理解和分析实体和值对象

    (一)对于实体的具体理解

    1.实体的业务形态

    2.实体的代码形态

    3.实体的运行形态

    4.实体的数据库形态

    (二)对于值对象的具体理解

    1.实体的业务形态

    2.实体的代码形态

    3.实体的运行形态

    4.实体的数据库形态

    5. 值对象的优势和局限

    (三)对于实体与值对象关系的理解

    1.基本的关系理解

    2.不同场景下关系的不同

    五、理解和分析聚合思想:聚合和聚合根

    (一)对聚合的理解和分析

    (二)对聚合根的理解和分析

    (三)聚合、聚合根、实体、值对象的对比

    (四)基本理解综合

    六、理解很分析领域事件来解耦微服务

    (一)领域事件的识别

    (二)微服务内外的领域事件分析

    1. 微服务内的领域事件

    2. 微服务之间的领域事件

    (三)领域事件总体架构

    1. 事件构建和发布

    2. 事件数据持久化

    3. 事件总线 (EventBus)

    4. 消息中间件

    5. 事件接收和处理

    (四)具体案例分析

    参考书籍、文献和资料


    领域驱动实践总结一:基本理论总结与分析

    领域驱动设计DDD是一种设计思想,它可以同时指导中台业务建模和微服务设计(中台本质是业务模型,微服务是业务模型的系统落地),领域驱动设计强调领域模型和微服务设计的一体性,先有领域模型然后才有微服务,而不是脱离领域模型来谈微服务设计。

    微服务拆分困境产生的根本原因:不知道业务或者微服务的边界到底在什么地方。

    DDD 核心思想:通过领域驱动设计方法定义领域模型,从而确定业务和应用边界,保证业务模型与代码模型的一致性。

    对于领域驱动设计的学习做的总结主要写三篇博客,主要包括三部分:基本理论总结与分析、架构分析与代码设计、具体应用设计分析,主要参考的资料为极客时间的欧创新架构师的《DDD》实战,其他参考书籍在文章下方的参考书籍中。

    本次主要总结DDD基本理论总结与分析

    一、领域驱动设计两大设计:战略设计和战术设计

    领域驱动设计认为:开发团队应该从业务需求中提炼出统一语言,再基于统一语言建立领域模型,这个领域模型会指导程序设计及编码实现,最后,又通过重构来发现隐式概念,并运用设计模式改进设计与开发质量。

    领域驱动设计的整个设计过程主要分为两个阶段:战略设计阶段与战术设计阶段。

    (一)战略设计

    1.出发角度与目标

    业务视角出发,建立业务领域模型,划分领域边界,建立通用语言的限界上下文,限界上下文可以作为微服务设计的参考边界。

    2.实现方式:事件风暴与模型确立(用例分析、场景分析和用户旅程分析)

    DDD 战略设计会建立领域模型,领域模型可以用于指导微服务的设计和拆分。

    事件风暴是建立领域模型的主要方法,它是一个从发散到收敛的过程。它通常采用用例分析、场景分析和用户旅程分析,尽可能全面不遗漏地分解业务领域,并梳理领域对象之间的关系,这是一个发散的过程。事件风暴过程会产生很多的实体、命令、事件等领域对象,我们将这些领域对象从不同的维度进行聚类,形成如聚合、限界上下文等边界,建立领域模型,这就是一个收敛的过程。

    3.用三步来划定领域模型和微服务的边界

    第一步:在事件风暴中梳理业务过程中的用户操作、事件以及外部依赖关系等,根据这些要素梳理出领域实体等领域对象。

    第二步:根据领域实体之间的业务关联性,将业务紧密相关的实体进行组合形成聚合,同时确定聚合中的聚合根、值对象和实体。在这个图里,聚合之间的边界是第一层边界,它们在同一个微服务实例中运行,这个边界是逻辑边界,所以用虚线表示。

    第三步:根据业务及语义边界等因素,将一个或者多个聚合划定在一个限界上下文内,形成领域模型。在这个图里,限界上下文之间的边界是第二层边界,这一层边界可能就是未来微服务的边界,不同限界上下文内的领域逻辑被隔离在不同的微服务实例中运行,物理上相互隔离,所以是物理边界,边界之间用实线来表示。

    (二)战术设计

    1.出发角度与目标

    技术视角出发,侧重于领域模型的技术实现,完成软件开发和落地,包括:聚合根、实体、值对象、领域服务、应用服务和资源库等代码逻辑的设计和实现。

    2.实现方式:DDD 分层架构、整洁架构、CQRS 和六边形架构等  (我们采用DDD 分层架构)

    从战略设计向战术设计的实施过程中,我们会将领域模型中的领域对象与代码模型中的代码对象建立映射关系,将业务架构和系统架构进行绑定。当我们去响应业务变化调整业务架构和领域模型时,系统架构也会同时发生调整,并同步建立新的映射关系。

    微服务架构模型有好多种,例如DDD 分层架构、整洁架构、CQRS 和六边形架构等,其核心理念都是为了设计出“高内聚低耦合”的架构,轻松实现架构演进。而 DDD 分层架构的出现,使架构边界变得越来越清晰,它在微服务架构模型中,占有非常重要的位置。

    DDD 分层架构包含用户接口层、应用层、领域层和基础层。通过这些层次划分,我们可以明确微服务各层的职能,划定各领域对象的边界,确定各领域对象的协作方式。

    3.代码模型强调两点:聚合之间的代码边界一定要清晰+一定要有代码分层的概念

    根据 DDD 分层架构模型建立了标准的微服务代码模型,在代码模型里面,各代码对象各据其位、各司其职,共同协作完成微服务的业务逻辑。

    第一点:聚合之间的代码边界一定要清晰。

    聚合之间的服务调用和数据关联应该是尽可能的松耦合和低关联,聚合之间的服务调用应该通过上层的应用层组合实现调用,原则上不允许聚合之间直接调用领域服务。这种松耦合的代码关联,在以后业务发展和需求变更时,可以很方便地实现业务功能和聚合代码的重组,在微服务架构演进中将会起到非常重要的作用。

    第二点:一定要有代码分层的概念。

    写代码时一定要搞清楚代码的职责,将它放在职责对应的代码目录内。应用层代码主要完成服务组合和编排,以及聚合之间的协作,它是很薄的一层,不应该有核心领域逻辑代码。领域层是业务的核心,领域模型的核心逻辑代码一定要在领域层实现。如果将核心领域逻辑代码放到应用层,你的基于 DDD 分层架构模型的微服务慢慢就会演变成传统的三层架构模型了。

    二、理解和分析领域+子域+核心域+通用域+支撑域

    (一)整体理解领域与子域的概念(以桃树生物学知识体系的建立为例来加深理解)

    领域就是用来确定范围的,范围即边界,这也是 DDD 在设计中不断强调边界的原因。

    DDD 的领域就是这个边界内要解决的业务问题域。

    领域可以进一步划分为子领域。我们把划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或更小的业务范围。

    领域建模和微服务建设的过程和方法基本类似,其核心思想就是将问题域逐步分解,降低业务理解和系统实现的复杂度。

    如何给桃树建立一个完整的生物学知识体系,它的研究过程如下:

    --->确定研究对象,即研究领域,这里是一棵桃树           

          zyf@@@zyf  对应 DDD 的领域,研究的具体问题域

    --->对研究对象进行细分,将桃树细分为器官,器官又分为营养器官和生殖器官两种。其中营养器官包括根、茎和叶,生殖器官包括花、果实和种子。

         zyf@@@zyf  具体问题域划分为两大问题域,根、茎、叶、花、果实和种子等器官则是细分后的问题子域。

                               DDD 将领域细分为多个子域的过程。      

    --->对器官进行细分,将器官细分为组织。比如,叶子器官可细分为保护组织、营养组织和输导组织等。

          zyf@@@zyf  DDD 将子域进一步细分为多个子域。     

    --->对组织进行细分,将组织细分为细胞,细胞成为我们研究的最小单元。细胞之间的细胞壁确定了单元的边界。

          zyf@@@zyf  DDD 将子域进一步细分确定研究的最小边界

                                可以把细胞(细胞核、线粒体、细胞膜等物质)理解为 DDD 的聚合,细胞内的这些物质就可以理解为聚合里面的聚合根、实体以及值对象等。    

    (二)理解核心域、通用域和支撑域的划分及具体目的

    子域可以根据自身重要性和功能属性划分为三类子域,它们分别是:核心域、通用域和支撑域。

    公司在 IT 系统建设过程中,由于预算和资源有限,对不同类型的子域应有不同的关注度和资源投入策略,记住好钢要用在刀刃上。很多公司的业务,表面看上去相似,但商业模式和战略方向是存在很大差异的,因此公司的关注点会不一样,在划分核心域、通用域和支撑域时,其结果也会出现非常大的差异。

    比方都是电商平台的淘宝、天猫、京东和苏宁易购,他们的商业模式是不同,在面对各个子域的划分上侧重点都不一样!

    1.核心域

    最重要的,决定产品和公司核心竞争力的子域,它是业务成功的主要因素和公司的核心竞争力。

    在公司领域细分、建立领域模型和系统建设时,我们就要结合公司战略重点和商业模式,找到核心域了,且重点关注核心域。

    建议技术团队要将核心域的建设排在首位,最好是有绝对的掌控能力和自主研发能力!

    2.通用域

    没有太多个性化的诉求,同时被多个子域使用的通用功能子域是通用域。

    通用域则是你需要用到的通用系统,比如认证、权限等等,这类应用很容易买到,没有企业特点限制,不需要做太多的定制化。

    3.支撑域

    既不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域,它就是支撑域。

    支撑域则具有企业特性,但不具有通用性,例如数据代码类的数据字典等系统。

    4.划分核心域、支撑域和通用域的主要目标

    通过领域划分,区分不同子域在公司内的不同功能属性和重要性,从而公司可对不同子域采取不同的资源投入和建设策略,其关注度也会不一样。

    三、理解和分析界限上下文,定义领域边界

    在 DDD 领域建模和系统建设过程中,领域专家、产品经理、项目经理、架构师、开发经理和测试经理等对同样的领域知识,不同的参与角色可能会有不同的理解,避免交流障碍,DDD 中就出现了“通用语言”和“限界上下文”这两个重要的概念。

    通用语言定义上下文含义,限界上下文则定义领域边界,以确保每个上下文含义在它特定的边界内都具有唯一的含义,领域模型则存在于这个边界之内。

    (一)通用语言

    在事件风暴过程中,通过团队交流达成共识的,能够简单、清晰、准确描述业务涵义和规则的语言就是通用语言。

    通用语言包含术语和用例场景,并且能够直接反映在代码中。

    • 通用语言中的名词可以给领域对象命名,如商品、订单等,对应实体对象;
    • 而动词则表示一个动作或事件,如商品已下单、订单已付款等,对应领域事件或者命令。

    下面是一个微服务设计实例的部分数据(也是后面实践的应用案例分析),表格中的这些名词术语就是项目团队在事件风暴过程中达成一致、可用于团队内部交流的通用语言。

    在这个表格里面我们可以看到,DDD 分析过程中所有的领域对象以及它们的属性都被记录下来了,除了 DDD 的领域对象,

    DDD 分析和设计过程中的每一个环节都需要保证限界上下文内术语的统一,在代码模型设计的时侯就要建立领域对象和代码对象的一一映射,从而保证业务模型和代码模型的一致,实现业务语言与代码语言的统一。还记录了在微服务设计过程中领域对象所对应的代码对象,并将它们一一映射。

    (二)限界上下文

    领域专家、架构师和开发人员的主要工作就是通过事件风暴来划分限界上下文。

    可以将限界上下文拆解为两个词:限界和上下文。限界就是领域的边界,而上下文则是语义环境。

    限界上下文的定义就是:用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。

    业务的通用语言就有它的业务边界,我们不大可能用一个简单的术语没有歧义地去描述一个复杂的业务领域。限界上下文就是用来细分领域,从而定义通用语言所在的边界。

    正如电商领域的商品一样,商品在不同的阶段有不同的术语,在销售阶段是商品,而在运输阶段则变成了货物。同样的一个东西,由于业务领域的不同,赋予了这些术语不同的涵义和职责边界,这个边界就可能会成为未来微服务设计的边界。看到这,我想你应该非常清楚了,领域边界就是通过限界上下文来定义的

    理论上限界上下文就是微服务的边界。我们将限界上下文内的领域模型映射到微服务,就完成了从问题域到软件的解决方案。

    可以说,限界上下文是微服务设计和拆分的主要依据。在领域模型中,如果不考虑技术异构、团队沟通等其它外部因素,一个限界上下文理论上就可以设计为一个微服务。

    四、理解和分析实体和值对象

    (一)对于实体的具体理解

    是一个具体类对象,它们拥有唯一标识符,且标识符在历经各种状态变更后仍能保持一致

    对这些对象而言,重要的不是其属性,而是其延续性和标识,对象的延续性和标识会跨越甚至超出软件的生命周期。我们把这样的对象称为实体。

    具体从实体的业务形态、代码形态、运行形态和数据库形态去进一步理解:

    1.实体的业务形态

    领域模型中的实体是多个属性、操作或行为的载体

    在事件风暴中,我们可以根据命令、操作或者事件,找出产生这些行为的业务实体对象,进而按照一定的业务规则将依存度高和业务关联紧密的多个实体对象和值对象进行聚类,形成聚合。

    实体和值对象是组成领域模型的基础单元。

    2.实体的代码形态

    代码模型中,实体的表现形式是实体类,这个类包含了实体的属性和方法,通过这些方法实现实体自身的业务逻辑。

    在 DDD 里,这些实体类通常采用充血模型与这个实体相关的所有业务逻辑都在实体类的方法中实现跨多个实体的领域逻辑则在领域服务中实现

    3.实体的运行形态

    实体以 DO(领域对象)的形式存在,每个实体对象都有唯一的 ID。

    可以对一个实体对象进行多次修改,修改后的数据和原来的数据可能会大不相同。但是,由于它们拥有相同的 ID,它们依然是同一个实体。

    4.实体的数据库形态

    与传统数据模型设计优先不同,DDD 是先构建领域模型,针对实际业务场景构建实体对象和行为,再将实体对象映射到数据持久化对象。

    在领域模型映射到数据模型时,一个实体可能对应 0 个、1 个或者多个数据库持久化对象。大多数情况下实体与持久化对象是一对一。

    • 在某些场景中,有些实体只是暂驻静态内存的一个运行态实体,它不需要持久化。比如,基于多个价格配置数据计算后生成的折扣实体。
    • 在有些复杂场景下,实体与持久化对象则可能是一对多或者多对一的关系。比如,用户 user 与角色 role 两个持久化对象可生成权限实体,一个实体对应两个持久化对象,这是一对多的场景。
    • 有些场景为了避免数据库的联表查询,提升系统性能,会将客户信息 customer 和账户信息 account 两类数据保存到同一张数据库表中,客户和账户两个实体可根据需要从一个持久化对象中生成,这就是多对一的场景。

    (二)对于值对象的具体理解

    《实现领域驱动设计》一书中对值对象的定义:通过对象属性值来识别的对象,它将多个相关属性组合为一个概念整体。在 DDD 中用来描述领域的特定方面,并且是一个没有标识符的对象,叫作值对象。

    值对象描述了领域中的一件东西,这个东西是不可变的,它将不同的相关属性组合成了一个概念整体。简单来说,值对象本质上就是一个集。在领域建模的过程中,值对象可以保证属性归类的清晰和概念的完整性,避免属性零碎。

    举例:人员实体原本包括姓名、年龄、性别以及人员所在的省、市、县和街道等属性。这样显示地址相关的属性就很零碎了对不对?现在,我们可以将“省、市、县和街道等属性”拿出来构成一个“地址属性集合”,这个集合就是值对象了。

    具体从实体的业务形态、代码形态、运行形态、数据库形态和值对象的优势和局限去进一步理解:

    1.实体的业务形态

    本质上,实体是看得到、摸得着的实实在在的业务对象,实体具有业务属性、业务行为和业务逻辑。

    值对象只是若干个属性的集合,只有数据初始化操作和有限的不涉及修改数据的行为,基本不包含业务逻辑。在逻辑上它仍然是实体属性的一部分,用于描述实体的特征。

    值对象中也有部分共享的标准类型的值对象,它们有自己的限界上下文,有自己的持久化对象,可以建立共享的数据类微服务,比如数据字典。

    2.实体的代码形态

    代码模型中,有这样两种形态:

    • 如果值对象是单一属性,则直接定义为实体类的属性;
    • 如果值对象是属性集合,则把它设计为 Class 类,Class 将具有整体概念的多个属性归集到属性集合,这样的值对象没有 ID,会被实体整体引用。

    3.实体的运行形态

    实体实例化后的 DO 对象的业务属性和业务行为非常丰富,但值对象实例化的对象则相对简单和乏味。除了值对象数据初始化和整体替换的行为外,其它业务行为就很少了。

    值对象嵌入到实体的话,有这样两种不同的数据格式,也可以说是两种方式,分别是属性嵌入的方式序列化大对象的方式

    引用单一属性的值对象或只有一条记录的多属性值对象的实体,可以采用属性嵌入的方式嵌入。以上面的代码为例:

    引用一条或多条记录的多属性值对象的实体,可以采用序列化大对象的方式嵌入。以上面的代码为例:

    4.实体的数据库形态

    DDD 引入值对象是希望实现从“数据建模为中心”向“领域建模为中心”转变,减少数据库表的数量和表与表之间复杂的依赖关系,尽可能地简化数据库设计,提升数据库性能。

    值对象在数据库持久化方面简化了设计,它的数据库设计大多采用非数据库范式值对象的属性值和实体对象的属性值保存在同一个数据库实体表中

    在这块建议的具体做法是:

    • 在领域建模时,我们可以将部分对象设计为值对象,保留对象的业务涵义,同时又减少了实体的数量;
    • 在数据建模时,我们可以将值对象嵌入实体,减少实体表的数量,简化数据库设计。

    上面的代码为例,在领域建模时,我们可以把地址作为值对象,人员作为实体,这样就可以保留地址的业务涵义和概念完整性。而在数据建模时,我们可以将地址的属性值嵌入人员实体数据库表中,只创建人员数据库表

    5. 值对象的优势和局限

    值对象采用序列化大对象的方法简化了数据库设计,减少了实体表的数量,可以简单、清晰地表达业务概念。

    这种设计方式虽然降低了数据库设计的复杂度,但却无法满足基于值对象的快速查询,会导致搜索值对象属性值变得异常困难。

    值对象采用属性嵌入的方法提升了数据库的性能,但如果实体引用的值对象过多,则会导致实体堆积一堆缺乏概念完整性的属性,这样值对象就会失去业务涵义,操作起来也不方便。

    在使用时要充分的考虑值对象的优缺点。

    (三)对于实体与值对象关系的理解

    1.基本的关系理解

    实体和值对象是微服务底层的最基础的对象,一起实现实体最基本的核心领域逻辑。

    DDD 提倡从领域模型设计出发,而不是先设计数据模型。传统的数据模型设计通常是一个表对应一个实体,一个主表关联多个从表,当实体表太多的时候就很容易陷入无穷无尽的复杂的数据库设计,领域模型就很容易被数据模型绑架。可以说,值对象的诞生,在一定程度上,和实体是互补的。

    2.不同场景下关系的不同

    同样的对象在不同的场景下,可能会设计出不同的结果。

    有些场景中,地址会被某一实体引用,它只承担描述实体的作用,并且它的值只能整体替换,这时候你就可以将地址设计为值对象,比如收货地址。

    而在某些业务场景中,地址会被经常修改,地址是作为一个独立对象存在的,这时候它应该设计为实体,比如行政区划中的地址信息维护。

    五、理解和分析聚合思想:聚合和聚合根

    在事件风暴中,我们会根据一些业务操作和行为找出实体(Entity)或值对象(ValueObject),进而将业务关联紧密的实体和值对象进行组合,构成聚合,再根据业务语义将多个聚合划定到同一个限界上下文(Bounded Context)中,并在限界上下文内完成领域建模。

    (一)对聚合的理解和分析

    聚合就是由业务和逻辑紧密关联的实体和值对象组合而成的,聚合是数据修改和持久化的基本单元每一个聚合对应一个仓储,实现数据的持久化。

    聚合有一个聚合根和上下文边界,这个边界根据业务单一职责和高内聚原则,定义了聚合内部应该包含哪些实体和值对象,而聚合之间的边界是松耦合的。

    聚合在 DDD 分层架构里属于领域层,领域层包含了多个聚合,共同实现核心业务逻辑。

    聚合内实体以充血模型实现个体业务能力,以及业务逻辑的高内聚。跨多个实体的业务逻辑通过领域服务来实现,跨多个聚合的业务逻辑通过应用服务来实现。

    (二)对聚合根的理解和分析

    聚合根的主要目的是为了避免由于复杂数据模型缺少统一的业务规则控制,而导致聚合、实体之间数据不一致性的问题。

    如果把聚合比作组织,那聚合根就是这个组织的负责人。聚合根也称为根实体,它不仅是实体,还是聚合的管理者。

    • 首先它作为实体本身,拥有实体的属性和业务行为,实现自身的业务逻辑。
    • 其次它作为聚合的管理者,在聚合内部负责协调实体和值对象按照固定的业务规则协同完成共同的业务逻辑。
    • 最后在聚合之间,它还是聚合对外的接口人,以聚合根 ID 关联的方式接受外部任务和请求,在上下文内实现聚合之间的业务协同。

    (三)聚合、聚合根、实体、值对象的对比

       
    聚合

    *高内聚、低耦合,它是领域模型中最底层的边界,可以作为拆分微服务的最小单位,但不建议你对微服务过度拆分。

    *在对性能有极致要求的场景中,聚合可以独立作为一个微服务,以满足版本的高频发布和极致的弹性伸缩能力。

    *一个微服务可以包含多个聚合,聚合之间的边界是微服务内天然的逻辑边界。

    聚合根

    *聚合根是实体,有实体的特点,具有全局唯一标识,有独立的生命周期。

    *一个聚合只有一个聚合根,聚合根在聚合内对实体和值对象采用直接对象引用的方式进行组织和协调,聚合根与聚合根之间通过 ID 关联的方式实现聚合之间的协同。

    实体

    *有 ID 标识,通过 ID 判断相等性,ID 在聚合内唯一即可。

    *状态可变,它依附于聚合根,其生命周期由聚合根管理。

    *实体一般会持久化,但与数据库持久化对象不一定是一对一的关系。

    *实体可以引用聚合内的聚合根、实体和值对象。

    值对象

    *无 ID,不可变,无生命周期,用完即扔。

    *值对象之间通过属性值判断相等性。

    *核心本质是值,是一组概念完整的属性组成的集合,用于描述实体的状态和特征。

    *值对象尽量只引用值对象。

    (四)基本理解综合

    实体和值对象都只是个体化的对象,它们的行为表现出来的是个体的能力。实体一般对应业务对象,它具有业务属性和业务行为;而值对象主要是属性集合,对实体的状态和特征进行描述。

    领域模型内的实体和值对象就好比个体,而能让实体和值对象协同工作的组织就是聚合,它用来确保这些领域对象在实现共同的业务逻辑时,能保证数据的一致性一个聚合只有一个聚合根聚合根在聚合内对实体和值对象采用直接对象引用的方式进行组织和协调,聚合根与聚合根之间通过 ID 关联的方式实现聚合之间的协同聚合根是实体,有实体的特点,具有全局唯一标识,有独立的生命周期。

    六、理解很分析领域事件来解耦微服务

    领域事件是领域模型中非常重要的一部分,用来表示领域中发生的事件。一个领域事件将导致进一步的业务操作,在实现业务解耦的同时,还有助于形成完整的业务闭环。

    (一)领域事件的识别

    我们在做事件风暴的时候,进行用户旅程或者场景分析时,我们要捕捉业务、需求人员或领域专家口中的关键词:“如果发生……,则……”“当做完……的时候,请通知……”“发生……时,则……”等。在这些场景中,如果发生某种事件后,会触发进一步的操作,那么这个事件很可能就是领域事件。

    领域事件驱动设计可以切断领域模型之间的强依赖关系,事件发布完成后,发布方不必关心后续订阅方事件处理是否成功,这样可以实现领域模型的解耦,维护领域模型的独立性和数据的一致性。

    在领域模型映射到微服务系统架构时,领域事件可以解耦微服务,微服务之间的数据不必要求强一致性,而是基于事件的最终一致性

    (二)微服务内外的领域事件分析

    1. 微服务内的领域事件

    当领域事件发生在微服务内的聚合之间,领域事件发生后完成事件实体构建和事件数据持久化,发布方聚合将事件发布到事件总线,订阅方接收事件数据完成后续业务操作。

    微服务内大部分事件的集成,都发生在同一个进程内,进程自身可以很好地控制事务,因此不一定需要引入消息中间件。

    但一个事件如果同时更新多个聚合,按照 DDD“一次事务只更新一个聚合”的原则,你就要考虑是否引入事件总线

    微服务内的事件总线,可能会增加开发的复杂度,因此你需要结合应用复杂度和收益进行综合考虑

    微服务内应用服务,可以通过跨聚合的服务编排和组合,以服务调用的方式完成跨聚合的访问,这种方式通常应用于实时性和数据一致性要求高的场景。这个过程会用到分布式事务,以保证发布方和订阅方的数据同时更新成功。

    2. 微服务之间的领域事件

    跨微服务的领域事件会在不同的限界上下文或领域模型之间实现业务协作,其主要目的是实现微服务解耦,减轻微服务之间实时服务访问的压力。

    跨微服务的事件可以推动业务流程或者数据在不同的子域或微服务间直接流转。

    跨微服务的事件机制要总体考虑事件构建、发布和订阅、事件数据持久化、消息中间件,甚至事件数据持久化时还可能需要考虑引入分布式事务机制等。

    微服务之间的访问也可以采用应用服务直接调用的方式,实现数据和服务的实时访问,弊端就是跨微服务的数据同时变更需要引入分布式事务,以确保数据的一致性。

    分布式事务机制会影响系统性能,增加微服务之间的耦合,所以我们还是要尽量避免使用分布式事务。

    (三)领域事件总体架构

    领域事件总体技术架构图,领域事件处理包括:事件构建和发布、事件数据持久化、事件总线、消息中间件、事件接收和处理等。

    1. 事件构建和发布

    事件基本属性至少包括:事件唯一标识、发生时间、事件类型和事件源,其中事件唯一标识应该是全局唯一的,以便事件能够无歧义地在多个限界上下文中传递。

    事件基本属性主要记录事件自身以及事件发生背景的数据。

    事件基本属性和业务属性一起构成事件实体,事件实体依赖聚合根。领域事件发生后,事件中的业务数据不再修改,因此业务数据可以以序列化值对象的形式保存,这种存储格式在消息中间件中也比较容易解析和获取。

    事件发布之前需要先构建事件实体并持久化

    事件发布的方式有很多种,你可以通过应用服务或者领域服务发布到事件总线或者消息中间件,也可以从事件表中利用定时程序或数据库日志捕获技术获取增量事件数据,发布到消息中间件。

    2. 事件数据持久化

    事件数据持久化可用于系统之间的数据对账,或者实现发布方和订阅方事件数据的审计。

    事件数据持久化有两种方案,在实施过程中你可以根据自己的业务场景进行选择:

    • 持久化到本地业务数据库的事件表中,利用本地事务保证业务和事件数据的一致性。
    • 持久化到共享的事件数据库中。这里需要注意的是:业务数据库和事件数据库不在一个数据库中,它们的数据持久化操作会跨数据库,因此需要分布式事务机制来保证业务和事件数据的强一致性,结果就是会对系统性能造成一定的影响。

    3. 事件总线 (EventBus)

    事件总线是实现微服务内聚合之间领域事件的重要组件,它提供事件分发和接收等服务

    事件总线是进程内模型,它会在微服务内聚合之间遍历订阅者列表,采取同步或异步的模式传递数据。

    事件分发流程大致如下:

    如果是微服务内的订阅者(其它聚合),则直接分发到指定订阅者;

    如果是微服务外的订阅者,将事件数据保存到事件库(表)并异步发送到消息中间件;

    如果同时存在微服务内和外订阅者,则先分发到内部订阅者,将事件消息保存到事件库(表),再异步发送到消息中间件。

    4. 消息中间件

    跨微服务的领域事件大多会用到消息中间件,实现跨微服务的事件发布和订阅。

    5. 事件接收和处理

    微服务订阅方在应用层采用监听机制,接收消息队列中的事件数据,完成事件数据的持久化后,就可以开始进一步的业务处理。

    领域事件处理可在领域服务中实现。

    (四)具体案例分析

    领域事件是 DDD 的一个重要概念,在设计时我们要重点关注领域事件,用领域事件来驱动业务的流转,尽量采用基于事件的最终一致,降低微服务之间直接访问的压力,实现微服务之间的解耦,维护领域模型的独立性和数据一致性。

    这里的具体用例结合后面的案例一起讲解,此处先预留。

     

    参考书籍、文献和资料

    1.极客时间课程《DDD实战》,欧创新,2019.

    2.郑天民. 微服务设计原理与架构. 北京:人民邮电出版社,2018.

    3.陈超、秦金卫、张逸等. 高可用可伸缩微服务架构. 电子工业出版社. 2019.

    4.Eric Evans. 领域驱动设计-软件核心复杂性应对之道。 人民邮电出版社. 2018.

    展开全文
  • DDD(领域驱动设计)

    万次阅读 多人点赞 2019-07-08 15:25:45
     领域驱动设计(简称 ddd)概念来源于2004年著名建模专家eric evans发表的他最具影响力的书籍:《domain-driven design –tackling complexity in the heart of software》(中文译名:领域驱动设计—软件核心复杂性...

    基本概念:

      领域驱动设计(简称 ddd)概念来源于2004年著名建模专家eric evans发表的他最具影响力的书籍:《domain-driven design –tackling complexity in the heart of software》(中文译名:领域驱动设计—软件核心复杂性应对之道)一书。,书中提出了“领域驱动设计(简称 ddd)”的概念。

             领域驱动设计一般分为两个阶段:

            1.   以一种领域专家、设计人员、开发人员都能理解的“通用语言”作为相互交流的工具,在不断交流的过程中发现和挖出一些主要的领域概念,然后将这些概念设计成一个领域模型;

             2.   由领域模型驱动软件设计,用代码来表现该领域模型。领域需求的最初细节,在功能层面通过领域专家的讨论得出。

           领域驱动设计告诉我们,在通过软件实现一个业务系统时,建立一个领域模型是非常重要和必要的,因为领域模型具有以下特点:

         1.   领域模型是对具有某个边界的领域的一个抽象,反映了领域内用户业务需求的本质;领域模型是有边界的,只反应了我们在领域内所关注的部分;

         2.   领域模型只反映业务,和任何技术实现无关;领域模型不仅能反映领域中的一些实体概念,如货物,书本,应聘记录,地址,等;还能反映领域中的一些过程概念,如资金转账,等;

         3.   领域模型确保了我们的软件的业务逻辑都在一个模型中,都在一个地方;这样对提高软件的可维护性,业务可理解性以及可重用性方面都有很好的帮助;

         4.   领域模型能够帮助开发人员相对平滑地将领域知识转化为软件构造;

         5.  领域模型贯穿软件分析、设计,以及开发的整个过程;领域专家、设计人员、开发人员通过领域模型进行交流,彼此共享知识与信息;因为大家面向的都是同一个模型,所以可以防止需求走样,可以让软件设计开发人员做出来的软件真正满足需求;

         6.  要建立正确的领域模型并不简单,需要领域专家、设计、开发人员积极沟通共同努力,然后才能使大家对领域的认识不断深入,从而不断细化和完善领域模型;

         7.  为了让领域模型看的见,我们需要用一些方法来表示它;图是表达领域模型最常用的方式,但不是唯一的表达方式,代码或文字描述也能表达领域模型;

         8.  领域模型是整个软件的核心,是软件中最有价值和最具竞争力的部分;设计足够精良且符合业务需求的领域模型能够更快速的响应需求变化;

    领域驱动设计中的一些基本概念:

    1.实体(entity):

            根据eric evans的定义,”一个由它的标识定义的对象叫做实体”。通常实体具备唯一id,能够被持久化,具有业务逻辑,对应现实世界业务对象。

             实体一般和主要的业务/领域对象有一个直接的关系。一个实体的基本概念是一个持续抽象的生命,可以变化不同的状态和情形,但总是有相同的标识。

    需要注意的是:

             一些开发人员将实体当成了orm意义上的实体,而不是业务所有和业务定义的领域对象。在一些实现中采用了transaction script风格的架构,使用贫血的领域模型。这种认识上的混乱,在领域驱动架构中,不愿意在领域对象中加入业务逻辑而导致贫血的领域模型,同时还可能使混乱的服务对象激增。

    2.值对象(value object)

            值对象的定义是:描述事物的对象;更准确的说,一个没有概念上标识符描述一个领域方面的对象。这些对象是用来表示临时的事物,或者可以认为值对象是实体的属性,这些属性没有特性标识但同时表达了领域中某类含义的概念。

            通常值对象不具有唯一id,由对象的属性描述,可以用来传递参数或对实体进行补充描述。

            作为实体属性的描述时,值对象也会被存储。在uml的类图上显现为一对多或一对一的关系。在orm映射关系上需要采用较复杂的一对多或一对一关系映射。

            关于实体与值对象的一个例子:比如员工信息的属性,如住址,电话号码都可以改变;然而,同一个员工的实体的标识将保持不变。因此,一个实体的基本概念是一个持续抽象的生命,可以变化不同的状态和情形,但总是有相同的标识。

    实体与值对象的区别

             实体具有唯一标识,而值对象没有唯一标识,这是实体和值对象间的最大不同。

            实体就是领域中需要唯一标识的领域概念。有两个实体,如果唯一标识不一样,那么即便实体的其他所有属性都一样,也认为是两个不同的实体;一个实体的基本概念是一个持续抽象的生命,可以变化不同的状态和情形,但总是有相同的标识。

            不应该给实体定义太多的属性或行为,而应该寻找关联,发现其他一些实体或值对象,将属性或行为转移到其他关联的实体或值对象上。

            如果两个对象的所有的属性的值都相同,我们会认为它们是同一个对象的话,那么我们就可以把这种对象设计为值对象。值对象在判断是否是同一个对象时是通过它们的所有属性是否相同,如果相同则认为是同一个值对象;而实体是否为同一个实体的区分,只是看实体的唯一标识是否相同,而不管实体的属性是否相同。

            值对象另外一个明显的特征是不可变,即所有属性都是只读的。因为属性是只读的,所以可以被安全的共享;当共享值对象时,一般有复制和共享两种做法,具体采用哪种做法还要根据实际情况而定。

            箴言:如果值对象时可共享的,它们应该是不可变的。(值对象应该保持尽量的简单)

             值对象的设计应尽量简单,不要让它引用很多其他的对象,因为本质上讲值对象只是代表一个值。

    3.聚合及聚合根(aggregate、aggregate root):

            聚合是用来定义领域对象所有权和边界的领域模式。聚合的作用是帮助简化模型对象间的关系。聚合,它通过定义对象之间清晰的所属关系和边界来实现领域模型的内聚,并避免了错综复杂的难以维护的对象关系网的形成。聚合定义了一组具有内聚关系的相关对象的集合,我们把聚合看作是一个修改数据的单元。

            划分aggregation是对领域模型的进一步深化,aggregation能阐释领域模型内部对象之间的深层关联.对aggregation的划分会直接映射到程序结构上.比如:ddd推荐按aggregation设计model的子包.每个aggregation配备一个repository.aggregation内部的非root对象是通过导航获得的.        

            一个聚合是一组相关的被视为整体的对象。每个聚合都有一个根对象(聚合根实体),从外部访问只能通过这个对象。根实体对象有组成聚合所有对象的引用,但是外部对象只能引用根对象实体。

             只有聚合根才能使用仓储库直接查询,其它的只能通过相关的聚合访问。如果根实体被删除,聚合内部的其它对象也将被删除。

             通常,我们把聚合组织到一个文件夹或一个包中。每一个聚集对应一个包,并且每个聚集成员包括实体、值对象,domain事件,仓储接口和其它工厂对象。

     

    聚合有以下一些特点:

      1. 每个聚合有一个根和一个边界,边界定义了一个聚合内部有哪些实体或值对象,根是聚合内的某个实体;

      2. 聚合内部的对象之间可以相互引用,但是聚合外部如果要访问聚合内部的对象时,必须通过聚合根开始导航,绝对不能绕过聚合根直接访问聚合内的对象,也就是说聚合根是外部可以保持对它的引用的唯一元素;

      3. 聚合内除根以外的其他实体的唯一标识都是本地标识,也就是只要在聚合内部保持唯一即可,因为它们总是从属于这个聚合的;

      4. 聚合根负责与外部其他对象打交道并维护自己内部的业务规则

      5. 基于聚合的以上概念,我们可以推论出从数据库查询时的单元也是以聚合为一个单元,也就是说我们不能直接查询聚合内部的某个非根的对象;

      6. 聚合内部的对象可以保持对其他聚合根的引用;

      7. 删除一个聚合根时必须同时删除该聚合内的所有相关对象,因为他们都同属于一个聚合,是一个完整的概念。

    如何识别聚合?

            聚合中的对象关系是内聚的,即这些对象之间必须保持一个固定规则,固定规则是指在数据变化时必须保持不变的一致性规则。

            当我们在修改一个聚合时,我们必须在事务级别确保整个聚合内的所有对象满足这个固定规则。

            作为一条建议,聚合尽量不要太大,否则即便能够做到在事务级别保持聚合的业务规则完整性,也可能会带来一定的性能问题。

            有分析报告显示,通常在大部分领域模型中,有70%的聚合通常只有一个实体,即聚合根,该实体内部没有包含其他实体,只包含一些值对象;另外30%的聚合中,基本上也只包含两到三个实体。这意味着大部分的聚合都只是一个实体,该实体同时也是聚合根。

     

    如何识别聚合根?

      如果一个聚合只有一个实体,那么这个实体就是聚合根;如果有多个实体,可以思考聚合内哪个对象有独立存在的意义并且可以和外部直接进行交互。

           并不是所有的实体都是聚集根,但只有实体才能成为聚集根。

    4.工厂(factories):

           工厂用来封装创建一个复杂对象尤其是聚合时所需的知识,作用是将创建对象的细节隐藏起来。客户传递给工厂一些简单的参数,然后工厂可以在内部创建出一个复杂的领域对象然后返回给客户。当创建 实体和值对象复杂时建议使用工厂模式。

           不意味着我们一定要使用工厂模式。如果创建对象很简单,使用构造器或者控制反转/依赖注入容器足够创建对象的依赖。此时,我们就不需要通用工厂模式来创建实体或值对象。

           良好工厂的要求:

           每个创建方法都是原子的。一个工厂应该只能生产透明状态的对象。对于实体,意味着创建整个聚合时满足所有的不变量。

          一个单独的工厂通常生产整个聚合,传出一个根实体的引用,确保聚合的不变量都有。如果对象的内部聚合需要工厂,通常工厂方法的逻辑放在在聚合根上。这样对外部隐藏了聚合内聚的实现,同时赋予了根确保聚合完整的职责。如果聚合根不是子实体工厂的合适的家,那么继续创建一个单独的工厂。

     

    5.仓储(repositories):

            仓储是用来管理实体的集合。

             仓储里面存放的对象一定是聚合,原因是domain是以聚合的概念来划分边界的;聚合作为一个整体概念,要么一起被取出来,要么一起被删除。外部访问不会单独对某个聚合内的子对象进行单独操作。因此,我们只对聚合设计仓储。

             仓储还有一个重要的特征就是分为仓储定义部分和仓储实现部分,我们在领域模型中定义仓储的接口,而在基础设施层实现具体的仓储。也符合按照接口分离模式在领域层定义仓储库接口的原则。

            注意:repositories本身是一种领域组件,但repositories的实现却不是领域层中的。

    respositories和dao:

             dao和repository在领域驱动设计中都很重要。dao是面向数据访问的,是关系型数据库和应用之间的契约。

            repository:位于领域层,面向aggregation root。repository是一个独立的抽象,使用领域的通用语言,它与dao进行交互,并使用领域理解的语言提供对领域模型的数据访问服务的“业务接口”。

      dao方法是细粒度的,更接近数据库,而repository方法的粒度粗一些,而且更接近领域。领域对象应该只依赖于repository接口。客户端应该始终调用领域对象,领域对象再调用dao将数据持久化到数据 存储中。

      处理领域对象之间的依赖关系(比如实体及其repository之间的依赖关系)是开发人员经常遇到的典型问题。解决这个问题通 常的设计方案是让服务类或外观类直接调用repository,在调用repository的时候返回实体对象给客户端。

    6.服务(services):

             服务这个词在服务模式中是这么定义的:服务提供的操作是它提供给使用它的客户端,并突出领域对象的关系。

             所有的service只负责协调并委派业务逻辑给领域对象进行处理,其本身并真正实现业务逻辑,绝大部分的业务逻辑都由领域对象承载和实现了。

             service可与多种组件进行交互,这些组件包括:其他的service、领域对象和repository 或 dao。

             通常,应用中一般包括:domain模型服务和应用层服务:

            *  domain services encapsulate domain concepts that just are not naturally modeled as things.

            *  application services constitute the application, or service, layer.

            当一个领域操作被视为一个重要的领域概念,一般就应该作为领域服务。 服务应该是无状态的。

            设计实现领域服务来协调业务逻辑,只在领域服务中实现领域逻辑的调用。

            领域服务逻辑须以非常干净简洁的代码实现。因此,我们必须实现对领域低层组件的调用。通常应用的调用,例如仓储库的调用,创建事务等,不应该在这里实现。这些操作应该在应用层实现。

              通常服务对象名称中都应包含一个动词。 service接口的传入传出参数也都应该是dto,可能包含的工作有领域对象和dto的互转换以及事务。

          服务的3个特征:

      a. 服务执行的操作涉及一个领域概念,这个领域概念通常不属于一个实体或者值对象

      b. 被执行的操作涉及到领域中其它的对象

      c. 操作时无状态的

    推荐:最好显式声明服务,因为它创建了领域中一个清晰的特性,封装了一个概念领域层服务和基础设施层服务:均建立在领域实体和值对象的上层,以便直接为这些相关的对象提供所需的服务;

     

    领域服务与domain对象的区别

            一般的领域对象都是有状态和行为的,而领域服务没有状态只有行为。需要强调的是领域服务是无状态的,它存在的意义就是协调领域对象共同完成某个操作,所有的状态还是都保存在相应的领域对象中。

            通常,对开发人员来说创建不应该存在的服务相当容易;要么在服务中包含了本应存在于领域对象中的领域逻辑,要么扮演了缺失的领域对象角色,而这些领域对象并没有作为模型的一部分去创建。

     

    7.domain事件

            domain event模式最初由udi dahan提出,发表在自己的博客上:http://www.udidahan.com/2009/06/14/domain-events-salvation/

            企业级应用程序事件大致可以分为三类:系统事件、应用事件和领域事件。领域事件的触发点在领域模型(domain model)中。它的作用是将领域对象从对repository或service的依赖中解脱出来,避免让领域对象对这些设施产生直接依赖。它的做法就是当领域对象的业务方法需要依赖到这些对象时就发出一个事件,这个事件会被相应的对象监听到并做出处理。

            通过使用领域事件,我们可以实现领域模型对象状态的异步更新、外部系统接口的委托调用,以及通过事件派发机制实现系统集成。另外,领域事件本身具有自描述性。它不仅能够表述系统发生了什么事情,而且还能够描述发生事件的动机。

             domain事件也用表进行存储。

    8.DTO

           dto- datatransfer object(数据传输对象):dto在设计之初的主要考量是以粗粒度的数据结构减少网络通信并简化调用接口。

    领域驱动架构与n层架构设计

    领域驱动架构

            eric  evans的“领域驱动设计- 应对软件的复杂性“一书中描述和解释了建议的n层架构高层次的图:

     

    user interface:

            该层包含与其他系统/客户进行交互的接口与通信设施,在多数应用里,该层可能提供包括web services、rmi或rest等在内的一种或多种通信接口。该层主要由facade、dto和assembler三类组件构成,三类组件均是典型的j2ee模式。

            dto的作用最初主要是以粗粒度的数据结构减少网络通信并简化调用接口。在领域驱动设计中,采用dto模型,可以起到隐藏领域细节,帮助实现独立封闭的领域模型的作用。

            dto与领域对象之间的相互转换工作多由assembler承担,也有一些系统使用反射机制自动实现dto与领域对象之间的相互转换,如apache common beanutils。

            facade的用意在于为远程客户端提供粗粒度的调用接口。facade本身不处理任何的业务逻辑,它的主要工作就是将一个用户请求委派给一个或多个service进行处理,同时借助assembler将service传入或传出的领域对象转化为dto进行传输。

     

    application:

             application层中主要组件就是service。这里需要注意的是,service的组织粒度和接口设计依据与传统transaction script风格的service是一致的,但是两者的实现却有质的区别。

      transaction script(事务脚本)的核心是过程,通过过程的调用来组织业务逻辑,业务逻辑在服务(service)层进行处理。大部分业务应用都可以被看成一系列事务。

             transaction script的特点是简单容易理解,面向过程设计。  如果应用相对简单,在应用的生命周期里不会有基础设施技术的改变,尤其是业务逻辑很少会变动,采用transaction script风格简单自然,性能良好,容易理解。

            transaction script的缺点在于,对于复杂的业务逻辑难以保持良好的设计,事务之间的冗余代码不断增多。应用架构容易出现“胖服务层”和“贫血的领域模型”。同时,service层积聚越来越多的业务逻辑,导致可维护性和扩展性变差

      领域模型属于面向对象设计,领域模型具备自己的属性行为和状态,领域对象元素之间通过聚合配合解决实际业务应用。可复用,可维护,易扩展,可以采用合适的设计模型进行详细设计。缺点是相对复杂,要求设计人员有良好的抽象能力。

            transactionscript风格业务逻辑主要在service中实现,而在领域驱动设计的架构里,service只负责协调并委派业务逻辑给领域对象进行处理。因此,我们可以考察这一点来识别系统是transaction script架构还是domain model架构。在实践中,设计良好的领域设计架构在开发过程中也容易向transaction script架构演变。

     

    domain:

            domain层是整个系统的核心层,该层维护一个使用面向对象技术实现的领域模型,几乎全部的业务逻辑会在该层实现。domain层包含entity(实体)、valueobject(值对象)、domain event(领域事件)和repository(仓储)等多种重要的领域组件。

    infrastructure:

            infrastructure(基础设施层)为interfaces、application和domain三层提供支撑。所有与具体平台、框架相关的实现会在infrastructure中提供,避免三层特别是domain层掺杂进这些实现,从而“污染”领域模型。infrastructure中最常见的一类设施是对象持久化的具体实现。

     

    n层架构设计

            层(layers)被视为构成应用或服务的水平堆叠的一组逻辑上的组件。它们帮助区分完成不同任务的组件,提供一个最大化复用和可维护性的设计。简言之,是关于在架构方面应用关注点分离的原则。         在传统的多层架构中,每个解决方案的组件必须分隔到不同的层。每层的组件必须内聚而且有大约相同的抽象级别。每个一级层应该和其他的一级层松耦合。

            从最底层的抽象级别看,例如第1层。这是系统的基础层。这些抽象的步骤是一步一步的最后到最顶层。

            多层应用的关键在于对依赖的管理。传统的多层架构,层内的组件只能和同级或者低级层的组件交互。这有利于减少不同层内组件的依赖。通常有两种多层架构的设计方法:严格和灵活的。          *   “严格的层设计”限定层内的组件只能和同一层、或者下一层的组件通信。即第n层只能和第n-1层交互,n-1层只能和n-2层交互,等等。

             *  “灵活的层设计”允许层内的组件和任何低级别层交互。这种设计中,第n层可以和n-1,n-2层交互。

            这种设计由于不需要对其他层进行重复的调用,从而可以提高性能。然而,这种设计不提供层之间的同层隔离级别,使得它难以在不影响多个高级层的时候替换一个低级的层。

            由于层之间是通过定义明确的接口进行交互这一事实,很容易为各层添加替代的实现(例如 mock  and stubs)。

             因为高层的组件只能和底层的交互,在单独的组件上进行测试是很容易的。

    使用层的好处  -  功能容易确定位置,解决方案也就容易维护。层内高内聚,层间松耦合使得维护/组合层更容易。 -  其他的解决方案可以重用由不同层暴露的功能。 -  当项目按逻辑分层时,分布式的部署更容易实现。 -  把层分布到不同的物理层可以提高可伸缩性;然后这一步应该进行仔细的评估,因为可能对性能带来负面影响。

    面向领域架构的分层:

            在面向领域架构中,关键是要清楚界定和分离领域模型层和其余的层。

    领域驱动与项目开发

             一般适合结合使用scrum(适用于项目管理)和xp(适用于软件开发目标)方法对处理ddd实施项目。敏捷方法注重于交付商业价值,而ddd侧重于结合软件系统和业务模型。此 外,就ddd迭代的特性来说,scrum或dsdm这样的敏捷方法对项目管理来说也是更好的框架。

           ddd迭代周期的项目管理模型如图所示。

    本图根据《domain driven design and development in practice》一文中插图进行了部分修改。

      领域建模结束时可以开始领域驱动设计。关于如何开始实现领域对象模型,ramnivas laddad推荐如下的步骤。他强调要更侧重于领域模型中的领域对象,而不是服务。

           *   从领域实体和领域逻辑开始。

           *   不要一开始就从服务层开始,只添加那些逻辑不属于任何领域实体或值对象的服务。

           *   利用通用语言、契约式设计(dbc)、自动化测试、  ci和重构,使实现尽可能地与领域模型紧密结合。

     

    设计领域模型的一般步骤:

           1.   根据需求建立一个初步的领域模型,识别出一些明显的领域概念以及它们的关联,关联可以暂时没有方向但需要有(1:1,1:n,m:n)这些关系;可以用文字精确的没有歧义的描述出每个领域概念的涵义以及包含的主要信息;

           2.   分析主要的软件应用程序功能,识别出主要的应用层的类;这样有助于及早发现哪些是应用层的职责,哪些是领域层的职责;

           3.   进一步分析领域模型,识别出哪些是实体,哪些是值对象,哪些是领域服务;

           4.   分析关联,通过对业务的更深入分析以及各种软件设计原则及性能方面的权衡,明确关联的方向或者去掉一些不需要的关联;

           5.   找出聚合边界及聚合根,这是一件很有难度的事情;因为你在分析的过程中往往会碰到很多模棱两可的难以清晰判断的选择问题,所以,需要我们平时一些分析经验的积累才能找出正确的聚合根;

           6.   为聚合根配备仓储,一般情况下是为一个聚合分配一个仓储,此时只要设计好仓储的接口即可;

           7.   走查场景,确定我们设计的领域模型能够有效地解决业务需求

           8.   考虑如何创建领域实体或值对象,是通过工厂还是直接通过构造函数;

           9.   停下来重构模型。寻找模型中觉得有些疑问或者是蹩脚的地方,比如思考一些对象应该通过关联导航得到还是应该从仓储获取?聚合设计的是否正确?考虑模型的性能怎样,等等;

             领域建模是一个不断重构,持续完善模型的过程,大家会在讨论中将变化的部分反映到模型中,从而是模型不断细化并朝正确的方向走。

      从设计和实现的角度来看,典型的ddd框架应该支持以下特征。

           *   应该是一个以pojo为基础的架构。

           *   应该支持使用ddd概念的业务领域模型的设计和实现。

           *   应该支持像依赖注入(di)和面向方向编程(aop)这些概念的开箱即用。

           *   与单元测试框架整合。

           *   与其它java/java ee框架进行良好的集成,比如jpa、hibernate、toplink等。

    一些反模式:

           *   贫血的领域对象

           *   重复的dao

           *   肥服务层:服务类在这里最终会包含所有的业务逻辑。

           *   依恋情结(feature envy):函数对某个类的兴趣高过对自己所处类的兴趣。

    一些思考

    1.   建立完整自封闭的领域模型。

            领域驱动架构相对比较容易理解,但建立一个完整自封闭的领域模型却很困难。“领域模型”是一个针对业务逻辑抽象的分析模型,它反映出对领域问题的整体描述。领域模型不是编程的实现模型,而是一组抽象概念的集合。一个领域概念不一定映射成一个类,也有可能会映射很多的类(包括多个实体或值对象)。领域需求的最初细节,在功能层面通过领域专家的讨论得出。领域专家并不一定需要熟知软件开发领域的知识,相反强调的是具有领域中的相关知识。领域需求在相互讨论中不断得到细化,还有可能在开发过程出现需求的反复或变更,这都要求领域模型的建立完善是一个反复重构的过程。敏捷开发是一种应对快速变化的需求的一种软件开发能力。强调程序员团队与业务专家之间的紧密协作、面对面的沟通(认为比书面的文档更有效)、频繁交付新的软件版本、紧凑而自我组织型的团队、能够很好地适应需求变化的代码编写和团队组织方法。故采用敏捷开发有利于领域模型的建立完善,以更能符合用户的实际需求。

          关于领域模型分析存在有多种分析方法。也许并不是能经常能有机会去实践这些分析方法或分析领域模型。但关于领域驱动架构的理解,有助于帮助我们去理解领域驱动的设计,实现一些高内聚、低耦合的代码实现。

    2.  领域服务建模

              建立和识别领域服务也比较容易出错。通常的ssh分层架构与领域驱动架构相近,而ssh架构开发更容易导致transaction script架构而非领域驱动架构。在ssh分层架构上,开发人员更容易建立”贫血”模型,而在service里实现业务逻辑。而ddd强调“充血模型”,“薄”service层。建立领域服务需要识别出领域业务逻辑,并将业务实现到领域模型中。一方面,业务需求充满着变化,在开发过程中难以把握。当业务不明需求不清时,“贫血模型”就更容易被人接受。另一方面,在构建领域模型时,orm映射显示十分重要并且也非常复杂,包括类继承体系与数据库的映射,抓取策略和缓存管理在内的一系列问题等.“贫血模型”有时会简化这种映射关系,同时,在处理对象依赖关系上显得更加灵活性。而领域模型强调了领域边界,对领域对象的访问总是通过聚合根开始,在有时候,模型的某些遍历会带来更大的性能和稳定性上的问题。而解决这些问题时,又常常会从实效性上出发而牺牲模型个别的清晰性和纯洁性。

    3.领域对象、领域服务以及repository之间的互相依赖

            在实际开发中,开发人员会经常需要处理领域对象之间的依赖关系,以及领域对象与repository间的依赖。通常可能的方案是让service或façade直接调用repository,从而获得返回的领域对象。这种方式导致各层间的依赖,通常我们应该考虑解耦这种依赖。当前实现组件解耦常用的技术无非是:控制反转(ioc)、依赖注入(di)、面向方面编程(aop)以及分布式服务接口。因此,解决依赖的一种思路利用di或aop将repository和服务注入到领域对象中。spring框架提供了相关的机制。在spring环境中,利用spring实例化对象,注入依赖,将服务、repository等领域对象联系起来。

     

    阿里盒马领域驱动设计实践

    领域驱动设计在美团点评业务系统的实践

    文章来源:https://www.cnblogs.com/firstdream/p/8669611.html

    展开全文
  • 领域驱动

    2019-10-03 05:19:54
    NET 领域驱动设计实战系列总结 http://www.cnblogs.com/zhili/p/DDDSummary.html 转载于:https://www.cnblogs.com/latebrose/p/5108858.html
  • 领域驱动设计

    2018-08-16 11:03:06
    领域驱动设计 领域驱动设计 领域驱动设计 领域驱动设计
  • 领域驱动设计(Domain Driven Design)参考架构详解

    万次阅读 多人点赞 2011-08-12 11:33:02
    领域驱动设计(Domain Driven Design)参考架构详解摘要本文将介绍领域驱动设计(Domain Driven Design)的官方参考架构,该架构分成了Interfaces、Applications和Domain三层以及包含各类基础设施的Infrastructure。...
  • 这个系列的教程会和大家一起一步一步搭建一个简单实用的基于领域驱动设计(DDD)开发模式的一个项目开发框架。 通过结合实际的代码应用让大家对领域驱动开发有一个更好的理解。 章节1:基础介绍 课时1课程介绍 课时2...
  • 实现领域驱动设计

    2018-01-09 11:07:49
    实现领域驱动设计 实现领域驱动设计实现领域驱动设计实现领域驱动设计实现领域驱动设计实现领域驱动设计实现领域驱动设计实现领域驱动设计实现领域驱动设计实现领域驱动设计实现领域驱动设计实现领域驱动设计实现...
  • 领域驱动设计实践

    2021-02-27 03:31:25
    领域驱动设计的关注重心是领域,尤其在面对复杂的领域逻辑时,它总能够帮助我们很好地分析领域。领域驱动设计的基础是领域建模。Eric认为需要和领域专家良好地合作,从交谈中发现通用语言,找到领域的关键词。领域...
  • 实现领域驱动

    2018-04-03 22:55:00
    领域驱动设计(DDD)是教我们如何做好软件的,同时也是教我们如何更好地使用面向对象技术的。它为我们提供了设计软件的全新视角,同时也给开发者留下了一大难题:如何将领域驱动设计付诸实践?Vaughn Vernon 的这本...
  • 学习领域驱动设计

    2021-02-26 12:45:10
    最近在翻阅博客园的文章的时候,不断的有这么几个字冲击我的眼球"领域驱动模型","DDD"等相关字眼。对此产生浓厚的兴趣,想要深入了解下,这些高大上的名词背后究竟有什么东西让人如何着魔!学习书籍《实现领域驱动...
  • 领域驱动设计架构

    2021-01-27 13:12:33
    领域驱动设计架构分成接口层(interfaces)、应用层(Applications)、领域层(Domain)以及基础设施层(Infrastructure)。下图描述这四者的简略图:四者的详细架构图:图二:领域驱动设计参考架构传统的三层构图:图三:...
  • 领域驱动开发

    2018-05-16 12:02:11
    Evans DDD是一套综合软件系统分析和设计的面向对象建模方法讲述领域驱动设计的方法
  • 收集来自主流的DDD开发理论知识培训课程,包括: 领域驱动设计简介 领域通用语言 领域驱动设计的构造块 领域驱动设计编程实践 CQRS架构 模型驱动开发
  • 领域驱动设计概览

    2021-02-24 14:12:07
    领域驱动设计(DomainDrivenDesign,DDD)是由EricEvans最早提出的综合软件系统分析和设计的面向对象建模方法,如今已经发展为一种针对大型复杂系统的领域建模与分析方法。它完全改变了传统软件开发工程师针对数据库...
  • 2004年Eric Evans 发表Domain-Driven Design –Tackling Complexity in the Heart of Software (领域驱动设计),简称Evans DDD。领域驱动设计分为两个阶段:以一种领域专家、设计人员、开发人员都能理解的通用语言...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 16,751
精华内容 6,700
关键字:

领域驱动