- 特 点
- 需要多种知识,需要不断改进
- 分 类
- 平面设计、装修设计、服装设计等
- 中文名
- 设计
- 外文名
- Design
-
领域驱动实践总结(基本理论总结与分析+架构分析与代码设计V+具体应用设计分析)
2020-03-26 18:34:46领域驱动设计DDD是一种设计思想,它可以同时指导中台业务建模和微服务设计(中台本质是业务模型,微服务是业务模型的系统落地),领域驱动设计强调领域模型和微服务设计的一体性,先有领域模型然后才有微服务,而不是...目录
领域驱动实践总结二:架构分析与代码设计
领域驱动设计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.
-
领域驱动实践总结(基本理论总结与分析+架构分析与代码设计+具体应用设计分析V)
2020-03-27 19:04:30领域驱动设计DDD是一种设计思想,它可以同时指导中台业务建模和微服务设计(中台本质是业务模型,微服务是业务模型的系统落地),领域驱动设计强调领域模型和微服务设计的一体性,先有领域模型然后才有微服务,而不是...目录
场景分析三:人员组织关系 详细的分析过程以及考勤的场景分析就不描述了
第二步:找出聚合根,根据实体、值对象与聚合根的依赖关系,建立聚合
领域驱动实践总结三:具体应用设计分析
领域驱动设计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.
-
领域驱动实践总结(基本理论总结与分析V+架构分析与代码设计+具体应用设计分析)
2020-03-24 19:03:13一、领域驱动设计两大设计:战略设计和战术设计 二、理解和分析领域+子域+核心域+通用域+支撑域 三、理解和分析界限上下文,定义领域边界 四、理解和分析实体和值对象 五、理解和分析聚合思想:聚合和聚合...目录
2.实现方式:事件风暴与模型确立(用例分析、场景分析和用户旅程分析)
2.实现方式:DDD 分层架构、整洁架构、CQRS 和六边形架构等 (我们采用DDD 分层架构)
3.代码模型强调两点:聚合之间的代码边界一定要清晰+一定要有代码分层的概念
(一)整体理解领域与子域的概念(以桃树生物学知识体系的建立为例来加深理解)
领域驱动实践总结一:基本理论总结与分析
领域驱动设计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.
-
史上最全设计模式导学目录(完整版)
2013-12-24 23:15:162012年-2013年,Sunny在CSDN技术博客中陆续发表了100多篇与设计模式相关的文章,涵盖了七个面向对象设计原则和24个设计模式(23个GoF设计模式 + 简单工厂模式),为了方便大家学习,现将所有与设计模式学习相关文章...圣诞献礼!
2012年-2013年,Sunny在CSDN技术博客中陆续发表了100多篇与设计模式学习相关的文章,涵盖了七个面向对象设计原则和24个设计模式(23个GoF设计模式 + 简单工厂模式),为了方便大家学习,现将所有文章的链接进行了整理,希望能给各位带来帮助!
祝大家圣诞节快乐! 花絮:本文的工作量大大超过之前的估计,几乎整个平安夜都花在它身上了,
基础知识
设计模式概述
从招式与内功谈起——设计模式概述(一):设计模式从何而来?
从招式与内功谈起——设计模式概述(二):设计模式是什么?
从招式与内功谈起——设计模式概述(三):设计模式有什么用?附:个人观点
面向对象设计原则
六个创建型模式
简单工厂模式-Simple Factory Pattern【学习难度:★★☆☆☆,使用频率:★★★☆☆】
工厂三兄弟之简单工厂模式(一):图表库的设计
工厂三兄弟之简单工厂模式(二):简单工厂模式概述
工厂三兄弟之简单工厂模式(三):图表库的简单工厂模式解决方案
工厂三兄弟之简单工厂模式(四):图表库解决方案的改进,简单工厂模式的简化,简单工厂模式总结
工厂方法模式-Factory Method Pattern【学习难度:★★☆☆☆,使用频率:★★★★★】
工厂三兄弟之工厂方法模式(一):日志记录器的设计
工厂三兄弟之工厂方法模式(二):工厂方法模式概述
工厂三兄弟之工厂方法模式(三):日志记录器的工厂方法模式解决方案,反射与配置文件
工厂三兄弟之工厂方法模式(四):重载的工厂方法,工厂方法的隐藏,工厂方法模式总结
抽象工厂模式-Abstract Factory Pattern【学习难度:★★★★☆,使用频率:★★★★★】
工厂三兄弟之抽象工厂模式(一):界面皮肤库的初始设计
工厂三兄弟之抽象工厂模式(二):产品等级结构与产品族
工厂三兄弟之抽象工厂模式(三):抽象工厂模式概述
工厂三兄弟之抽象工厂模式(四):界面皮肤库的抽象工厂模式解决方案
工厂三兄弟之抽象工厂模式(五):“开闭原则”的倾斜性,抽象工厂模式总结
单例模式-Singleton Pattern【学习难度:★☆☆☆☆,使用频率:★★★★☆】
确保对象的唯一性——单例模式 (一):单例模式的动机,单例模式概述
确保对象的唯一性——单例模式 (二):负载均衡器的设计与实现
确保对象的唯一性——单例模式 (三):饿汉式单例与懒汉式单例的讨论
确保对象的唯一性——单例模式 (四):一种更好的单例实现方法(静态内部类)
确保对象的唯一性——单例模式 (五):单例模式总结
原型模式-Prototype Pattern【学习难度:★★★☆☆,使用频率:★★★☆☆】
对象的克隆——原型模式(一):大同小异的工作周报,原型模式概述
对象的克隆——原型模式(二):工作周报的原型模式解决方案
对象的克隆——原型模式(三):带附件的周报【浅克隆,深克隆】
对象的克隆——原型模式(四):原型管理器的引入和实现,原型模式总结
建造者模式-Builder Pattern【学习难度:★★★★☆,使用频率:★★☆☆☆】
复杂对象的组装与创建——建造者模式(一):游戏角色设计,建造者模式概述
复杂对象的组装与创建——建造者模式(二):游戏角色设计的建造者模式解决方案
复杂对象的组装与创建——建造者模式(三):关于Director的进一步讨论,建造者模式总结
七个结构型模式
适配器模式-Adapter Pattern【学习难度:★★☆☆☆,使用频率:★★★★☆】
不兼容结构的协调——适配器模式(一):没有源码的算法库,适配器模式概述
不兼容结构的协调——适配器模式(二):没有源码的算法库的适配器模式解决方案
不兼容结构的协调——适配器模式(三):类适配器,双向适配器
不兼容结构的协调——适配器模式(四):缺省适配器,适配器模式总结
桥接模式-Bridge Pattern【学习难度:★★★☆☆,使用频率:★★★☆☆】
处理多维度变化——桥接模式(一):跨平台图像浏览系统
处理多维度变化——桥接模式(二):桥接模式概述
处理多维度变化——桥接模式(三):跨平台图像浏览系统的桥接模式解决方案
处理多维度变化——桥接模式(四):适配器模式与桥接模式的联用,桥接模式总结
组合模式-Composite Pattern【学习难度:★★★☆☆,使用频率:★★★★☆】
树形结构的处理——组合模式(一):设计杀毒软件的框架结构
树形结构的处理——组合模式(二):组合模式概述
树形结构的处理——组合模式(三):杀毒软件的框架结构的组合模式解决方案
树形结构的处理——组合模式(四):透明组合模式与安全组合模式
树形结构的处理——组合模式(五):公司组织结构,组合模式总结
装饰模式-Decorator Pattern【学习难度:★★★☆☆,使用频率:★★★☆☆】
扩展系统功能——装饰模式(一):图形界面构件库的设计
扩展系统功能——装饰模式(二):装饰模式概述
扩展系统功能——装饰模式(三):图形界面构件库的装饰模式解决方案
扩展系统功能——装饰模式(四):透明装饰模式与半透明装饰模式,装饰模式注意事项,装饰模式总结
外观模式-Facade Pattern【学习难度:★☆☆☆☆,使用频率:★★★★★】
深入浅出外观模式(一):外观模式概述,外观模式结构与实现
深入浅出外观模式(二):外观模式应用实例(文件加密模块)
深入浅出外观模式(三):抽象外观类,外观模式效果与适用场景
享元模式-Flyweight Pattern【学习难度:★★★★☆,使用频率:★☆☆☆☆】
实现对象的复用——享元模式(一):围棋棋子的设计,享元模式概述(上)
实现对象的复用——享元模式(二):享元模式概述(下)
实现对象的复用——享元模式(三):围棋棋子的享元模式解决方案
实现对象的复用——享元模式(四):带外部状态的围棋棋子解决方案
实现对象的复用——享元模式(五):单纯享元模式和复合享元模式,关于享元模式的几点补充,享元模式总结
代理模式-Proxy Pattern【学习难度:★★★☆☆,使用频率:★★★★☆】
代理模式(一):代理模式概述,代理模式结构与实现
代理模式(二):代理模式应用实例(收费商务信息查询系统)
代理模式(三):远程代理,虚拟代理,缓冲代理
代理模式(四):代理模式效果与适用场景
十一个行为型模式
职责链模式-Chain of Responsibility Pattern【学习难度:★★★☆☆,使用频率:★★☆☆☆】
请求的链式处理——职责链模式(一):采购单的分级审批
请求的链式处理——职责链模式(二):职责链模式概述
请求的链式处理——职责链模式(三):采购单分级审批的职责链模式解决方案
请求的链式处理——职责链模式(四):纯与不纯的职责链模式,职责链模式总结
命令模式-Command Pattern【学习难度:★★★☆☆,使用频率:★★★★☆】
请求发送者与接收者解耦——命令模式(一):自定义功能键,命令模式概述
请求发送者与接收者解耦——命令模式(二):自定义功能键的命令模式解决方案
请求发送者与接收者解耦——命令模式(三):命令队列的实现
请求发送者与接收者解耦——命令模式(四):撤销操作的简单实现
请求发送者与接收者解耦——命令模式(五):请求日志
请求发送者与接收者解耦——命令模式(六):宏命令,命令模式总结
解释器模式-Interpreter Pattern【学习难度:★★★★★,使用频率:★☆☆☆☆】
自定义语言的实现——解释器模式(一):机器人控制程序
自定义语言的实现——解释器模式(二):文法规则和抽象语法树
自定义语言的实现——解释器模式(三):解释器模式概述
自定义语言的实现——解释器模式(四):机器人控制程序的解释器模式解决方案
自定义语言的实现——解释器模式(五):再谈Context的作用
自定义语言的实现——解释器模式(六):解释器模式总结
迭代器模式-Iterator Pattern【学习难度:★★★☆☆,使用频率:★★★★★】
遍历聚合对象中的元素——迭代器模式(一):销售管理系统中数据的遍历
遍历聚合对象中的元素——迭代器模式(二):迭代器模式概述
遍历聚合对象中的元素——迭代器模式(三):销售管理系统中数据的遍历的迭代器模式解决方案
遍历聚合对象中的元素——迭代器模式(四):使用内部类实现迭代器
遍历聚合对象中的元素——迭代器模式(五):JDK内置迭代器的使用
遍历聚合对象中的元素——迭代器模式(六):迭代器模式总结
中介者模式-Mediator Pattern【学习难度:★★★☆☆,使用频率:★★☆☆☆】
协调多个对象之间的交互——中介者模式(一):客户信息管理窗口的初始设计
协调多个对象之间的交互——中介者模式(二):中介者模式概述
协调多个对象之间的交互——中介者模式(三):客户信息管理窗口的中介者模式解决方案
协调多个对象之间的交互——中介者模式(四):中介者与同事类的扩展
协调多个对象之间的交互——中介者模式(五):中介者模式总结
备忘录模式-Memento Pattern【学习难度:★★☆☆☆,使用频率:★★☆☆☆】
撤销功能的实现——备忘录模式(一):可悔棋的中国象棋
撤销功能的实现——备忘录模式(二):备忘录模式概述
撤销功能的实现——备忘录模式(三):中国象棋的备忘录模式解决方案
撤销功能的实现——备忘录模式(四):实现多次撤销
撤销功能的实现——备忘录模式(五):再谈备忘录的封装,备忘录模式总结
观察者模式-Observer Pattern【学习难度:★★★☆☆,使用频率:★★★★★】
对象间的联动——观察者模式(一):多人联机对战游戏的设计
对象间的联动——观察者模式(二):观察者模式概述
对象间的联动——观察者模式(三):多人联机对战游戏的观察者模式解决方案
对象间的联动——观察者模式(四):JDK对观察者模式的支持
对象间的联动——观察者模式(五):观察者模式与Java事件处理
对象间的联动——观察者模式(六):观察者模式与MVC,观察者模式总结
状态模式-State Pattern【学习难度:★★★☆☆,使用频率:★★★☆☆】
处理对象的多种状态及其相互转换——状态模式(一):银行系统中的账户类设计
处理对象的多种状态及其相互转换——状态模式(二):状态模式概述
处理对象的多种状态及其相互转换——状态模式(三):账户类的状态模式解决方案
处理对象的多种状态及其相互转换——状态模式(四):共享状态的实现
处理对象的多种状态及其相互转换——状态模式(五):使用环境类实现状态转换
处理对象的多种状态及其相互转换——状态模式(六):状态模式总结
策略模式-Strategy Pattern【学习难度:★☆☆☆☆,使用频率:★★★★☆】
算法的封装与切换——策略模式(一):电影票打折方案
算法的封装与切换——策略模式(二):策略模式概述
算法的封装与切换——策略模式(三):电影票打折方案的策略模式解决方案
算法的封装与切换——策略模式(四):策略模式的两个典型应用,策略模式总结
模板方法模式-Template Method Pattern【学习难度:★★☆☆☆,使用频率:★★★☆☆】
模板方法模式深度解析(一):模板方法模式概述,模板方法模式结构与实现
模板方法模式深度解析(二):模板方法模式应用实例(银行利息计算模块)
模板方法模式深度解析(三):钩子方法的使用,模板方法模式效果与适用场景
访问者模式-Visitor Pattern【学习难度:★★★★☆,使用频率:★☆☆☆☆】
操作复杂对象结构——访问者模式(一):OA系统中员工数据汇总
操作复杂对象结构——访问者模式(二):访问者模式概述
操作复杂对象结构——访问者模式(三):OA系统中员工数据汇总的访问者模式解决方案
操作复杂对象结构——访问者模式(四):访问者模式与组合模式联用,访问者模式总结
设计模式趣味学习(复习)
设计模式与足球(一):创建型模式
设计模式与足球(二):结构型模式
设计模式与足球(三):行为型模式(上)
设计模式与足球(四):行为型模式(下)
设计模式综合应用实例
多人联机射击游戏
多人联机射击游戏中的设计模式应用(一):抽象工厂模式,建造者模式,工厂方法模式,迭代器模式,命令模式
多人联机射击游戏中的设计模式应用(二):观察者模式,单例模式,状态模式,适配器模式
数据库同步系统
设计模式综合实例分析之数据库同步系统(一):数据库同步系统概述,建造者模式,简单工厂模式
设计模式综合实例分析之数据库同步系统(二):享元模式,单例模式,观察者模式,模板方法模式
设计模式综合实例分析之数据库同步系统(三):策略模式,组合模式,命令模式,职责链模式
友情提示:请尊重作者劳动成果,如需转载本博客文章请注明出处!谢谢合作!
【作者:刘伟 http://blog.csdn.net/lovelion】
-
2021软考软件设计师--基础知识培训视频
2015-07-21 10:25:48学员可以通过视频进行软考软件设计师相应的基础知识培训,掌握软件开发与设计的基础知识包括:数据结构、软件工程、操作系统、数据库原理、软件设计、UML分析与设计等内容,为考试和自身能力提高打下坚实基础。... -
DDD(领域驱动设计)
2019-07-08 15:25:45领域驱动设计(简称 ddd)概念来源于2004年著名建模专家eric evans发表的他最具影响力的书籍:《domain-driven design –tackling complexity in the heart of software》(中文译名:领域驱动设计—软件核心复杂性... -
23种设计模式汇总整理
2015-04-09 10:57:11设计模式分为三大类: 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式 结构型模式,共七种:适配器模式、装饰者模式、代理模式、外观模式、桥接模式、组合模式、享元模式。 ... -
C语言程序设计第五版 谭浩强 第五版课后答案
2019-06-16 00:27:29谭浩强 C语言程序设计第五版 第4章课后答案 3.求两个正整数m和n,求其最大公约数和最小公倍数。 #include<stdio.h> void main() { int m, n, t, i, a = 1; scanf("%d%d", &m, &n); if (m < n) ... -
Android组件设计思想
2013-10-23 01:11:07Android的组件设计思想与传统的组件设计思想最大的区别在于,前者不依赖于进程。也就是说,进程即使由于内存紧张被强行杀掉了,但是运行在里面的组件还是存在的。这样就可以在组件再次需要使用时,原地满血复活,就... -
C++ 设计模式
2018-02-09 09:26:25设计模式代表了最佳的实践,在面向对象的编程中被很多老鸟们反复使用。使用设计模式有很多好处:可重用代码、保证代码可靠性、使代码更易被他人理解 ...... -
电子设计从这开始——51单片机25个小作品
2016-09-23 13:09:09这是大二我做某实验室管理员,为了方便自己管理整理,都是一些51单片机的小作品,适合给刚学完51单片机的新手练手 ...6基于51单片机的无线温湿度控制系统设计 7基于51单片机脉搏心率计 8.基于51单片机led音乐频谱 -
12个优秀的 HTML5 网站设计案例欣赏
2018-05-20 14:39:5612个优秀的 HTML5 网站设计案例欣赏 欣赏地址:12个优秀的 HTML5 网站设计案例欣赏 -
android 漂亮的UI界面 完整的界面设计
2012-02-17 22:19:40声明:这也是我学习时在网上下载的,鉴于分享精神,并且觉得很不错才上传...android 漂亮的UI界面 完整的界面设计 这是一个完整的UI设计,但是没写动作,这是一个公司程序员的公司任务设计,请别商用,要用请修改。。。 -
基于SSM框架的毕业设计管理系统的设计与实现(附资源下载)
2019-09-25 14:43:16我的毕业设计就叫毕业设计管理系统的设计与实现,哈哈 目录 1.摘要 2.需求分析 3.数据库设计 4.功能模块的设计与实现 5.项目截图 6.项目源码 1.摘要 毕业设计是本科教育的最后一个环节,整个过程包括课题... -
软件设计是怎样炼成的?
2015-06-09 22:44:27软件设计视频培训教程,该课程告诉大家,软件设计并不是概要设计与详细设计这么简单,更加不是纸上谈兵的事情。课程全程活用UML(统一建模语言或标准建模语言),为你分享架构设计、数据库设计、用户体验设计和详细... -
OA多级审批流程表设计方案以及开发思路(非常细节)
2020-07-27 15:39:30OA审批工作流设计解决方案 -
2021软考软件设计师-上午真题解析视频课程
2016-04-20 15:07:45在掌握软件设计师基础知识的基础上,通过对历年上午真题逐题的分析讲解,明确软件设计师上午题的重要考点、加深对软件工程、数据库、计算机网络、设计模式等知识的理解,为考试和自身能力提高做进一步努力。 -
数据库设计(一)-设计模型与三大范式和五大约束
2020-05-26 20:53:344)详细设计 模型设计 5)编码阶段 代码文档 6)测试阶段 瀑布模型的特点是在每个阶段的工作都清晰详尽,容易预估风险和开发成本,每个阶段人员安排也非常清晰。 瀑布模型的缺点是中途不能出现任何问题,例如客户要... -
从MVC框架看MVC架构的设计
2011-08-16 09:57:37从MVC框架看MVC架构的设计尽管MVC早已不是什么新鲜话题了,但是从近些年一些优秀MVC框架的设计上,我们还是会发现MVC在架构设计上的一些新亮点。本文将对传统MVC架构中的一些弊病进行解读,了解一些优秀MVC框架是... -
设计模式看了又忘,忘了又看?
2019-05-27 22:45:28设计模式收藏这篇就够了 -
UI设计师必备的五款界面设计工具
2018-08-07 19:29:17在前几篇文章中,我们分享了一些最受欢迎的原型设计工具,今天我们来谈谈界面设计工具。工具的重要性对于设计师来讲不言而喻。任何想法都需要借助工具来实现。想要成为一名出色的UI设计师,你需要多掌握一些技能,... -
2021软考软件设计师下午案例分析视频培训课程
2015-09-10 17:39:02软考软件设计师下午案例分析视频培训教程:掌握软件设计师案例分析答题技巧和解题思路,通过对历年案例分析真题的讲解,掌握相应的知识点和答题技巧,为顺利通过软考和自身能力提高打下坚实基础。 -
计算机程序设计艺术--pdf--百度云--4卷全
2019-07-24 21:04:21他的系列巨著《计算机程序设计艺术》在计算机科学界享誉多年。多年前,高德纳对现有的数学文本处理工具感到不满,于是创建了自己的工具 TeX 和 Metafont。如今,这两个工具成为广泛应用的免费软件。 需要的... -
数据库设计三大范式
2016-10-20 21:59:49数据库设计三大范式 为了建立冗余较小、结构合理的数据库,设计数据库时必须遵循一定的规则。在关系型数据库中这种规则就称为范式。范式是符合某一种设计要求的总结。要想设计一个结构合理的关系型数据库,必须... -
计算机专业毕业设计题目大全——各种类型系统设计大全
2019-02-24 21:52:34计算机专业毕业设计题目大全 一、ASP类计算机专业毕业设计题目 1.网络留言薄2.客户管理系统3.多媒体积件管理库的开发与应用4.基于WEB的多媒体素材管理库的开发与应用5.网络教学软件中的教学设计与应用6.小型教育... -
2021软考网络规划设计师基础知识视频教程
2016-07-25 17:33:13课程紧跟软考网络规划设计师考试大纲,通过作者多年的软考网规辅导经验及实际的网规工作实战经历,本视频课程对网规考试中的所有知识点进行了分类的讲解及总结;对于考试中重点考、反复考的知识点做了专题强化训练;... -
为什么要报考系统架构设计师考试
2019-08-02 17:03:09一、强迫自己,去系统学习软件架构设计的理论,追踪业界架构设计的发展动态。去学习的动力有很多,如为了兴趣,为了工作,为了职位升迁,为了大幅提升薪水等。其实,为了应付考试,通过考试,也是学习知识的一种很好... -
设计模式——设计模式概述
2019-10-12 08:07:49软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。设计模式主要是为了解决某类重复出现的问题而出现的一套成功或有效的解决方案。设计模式提供... -
数据库结构设计(逻辑设计和物理设计)
2018-11-15 12:28:271、数据库结构设计的步骤 需求分析:全面了解产品设计的存储需求 逻辑设计:设计数据的逻辑存储结构 物理设计:根据所用的数据库特点进行表结构设计 关系型数据库:Oracle、SQLServer、MySQL、postgresSQL 非... -
C语言及程序设计初步
2015-01-05 10:08:13课程针对没有任何程序设计基础的初学者,全面介绍C语言及利用C语言进行程序设计的方法。课程注重知识的传授,更关注学习者能够通过实践的方式,真正学会利用C语言解决问题,奠定程序设计的基础。为此,专门设计了...
-
Window10问题解决(持续更新)
-
php获取mysql表中数据总数(总行数)
-
网页bug测不出来?不知道的还
-
MySQL 数据库权限管理(用户高级管理和精确访问控制)
-
vue3从0到1-超详细
-
PHP实现守护进程方式,Linux后台运行
-
qBittorrentEE_v4.3.1.11_便携版.zip
-
MySQL删除30天以前的数据(PHP)
-
MySQL 性能优化(思路拓展及实操)
-
利用社交媒体创造销售奇迹的十大经典案例.jpg
-
零基础一小时极简以太坊智能合约开发环境搭建并开发部署
-
第四章 C语言 PTA数组——作业-答案.html
-
微信支付 api v3 支付通知 异步 验签失败 PHP
-
Python中__new__()方法的作用,举例说明,简单易懂
-
装备制造行业智能制造方案.pptx
-
基于电商业务的全链路数据中台落地方案(全渠道、全环节、全流程)
-
linux基础入门和项目实战部署系列课程
-
Amoeba 实现 MySQL 高可用、负载均衡和读写分离
-
23设计模式-适配器模式
-
C++代码规范和Doxygen根据注释自动生成手册