当前位置: 首页 > news >正文

湖南大型网站建设公司排名偷网站源码直接建站

湖南大型网站建设公司排名,偷网站源码直接建站,农村建设网站,在线网站分析工具说在前面 在微服务的应用开发中#xff0c;DDD 用得越来越普及。 在40岁老架构师 尼恩的读者交流群(50)中#xff0c;DDD是一个非常、非常高频的交流话题。 最近#xff0c;有小伙伴面试阿里时#xff0c;遇到一个面试题#xff1a; 谈谈你对DDD的理解#xff1f; 小伙…说在前面 在微服务的应用开发中DDD 用得越来越普及。 在40岁老架构师 尼恩的读者交流群(50)中DDD是一个非常、非常高频的交流话题。 最近有小伙伴面试阿里时遇到一个面试题 谈谈你对DDD的理解 小伙伴没有用过DDD一点概念都没有。当然面试也就挂了。 这里尼恩给大家做一下系统化、体系化的 DDD 梳理使得大家可以充分展示一下大家雄厚的 “技术肌肉”让面试官爱到 “不能自已、口水直流”。 也一并把这个题目以及参考答案收入咱们的 《尼恩Java面试宝典》V47版本供后面的小伙伴参考提升大家的 3高 架构、设计、开发水平。 注本文以 PDF 持续更新最新尼恩 架构笔记、面试题 的PDF文件请从这里获取码云 DDD(Domain Driven Design)理解 领域驱动设计(简称 DDD )历史悠久。 2004年著名建模专家eric evans埃里克埃文斯发表的他最具影响力的书籍: 《domain-driven design –tackling complexity in the heart of software》(中文译名领域驱动设计—软件核心复杂性应对之道)一书。标志着 DDD 这种 设计和架构方法的诞生。 我们在日常开发中经常针对一些功能点争论“这个功能不应该我改应该是你那边改”最终被妥协改了之后都改不明白为什么这个功能要在自己这边改。 区别于传统的数据驱动架构Data Driven Design设计领域驱动设计DDD也许在这个时候能帮助你做到清晰的划分。 什么是DDD 领域驱动设计最初由Eric Evans提出但是多年以来一直停留在理念阶段 然后真正能实现并且落地的项目和公司少之又少 近来包括阿里在内很多大厂都在大力推行DDD的设计方法 它主要可以帮助我们解决传统单体式集中架构难以快速响应业务需求落地的问题并且针对中台和微服务盛行的场景做出指导。 DDD为我们提供的是架构设计的方法论既面向技术也面向业务从业务的角度来把握设计方案。 DDD的作用 统一思想统一项目各方业务、产品、开发对问题的认知而不是开发和产品统一业务又和产品统一从而产生分歧。 明确分工域模型需要明确定义来解决方方面面的问题而针对这些问题则形成了团队分钟的理解。 反映变化需求是不断变化的因此我们的模型也是在不断的变化的。领域模型则可以真实的反映这些变化。 边界分离领域模型与数据模型分离用领域模型来界定哪些需求在什么地方实现保持结构清晰。 DDD的基本概念 领域驱动设计围绕着领域模型进行设计通过分层架构将领域独立出来。 这里有两个关键词 领域模型分层架构 领域模型的对象包括实体、值对象和领域服务领域逻辑都应该封装在这些对象中。 领域模型 Domain Model 领域反映到代码里就是模型模型是对领域某个方面的抽象并且可以用来解决相关域的问题 模型分为实体和值对象两种。 实体对象 Entities 有唯一标志的核心领域对象且这个标志在整个软件生命周期中都不会发生变化。 这个概念和我们平时软件模型中和数据库打交道的Entity实体比较接近 不同的是DDD中这些实体会包含与该实体相关的业务逻辑它是操作行为的载体。 实体 唯一身份标识 可变性【状态 行为】 DDD 中要求实体是唯一的且可持续变化的。 意思是说在实体的生命周期内无论其如何变化其仍旧是同一个实体。 唯一性由唯一的身份标识来决定的。 可变性也正反映了实体本身的状态和行为。 实体以 DO领域对象的形式存在每个实体对象都有唯一的 ID。 我们可以对一个实体对象进行多次修改修改后的数据和原来的数据可能会大不相同。 但是由于它们拥有相同的 ID它们依然是同一个实体。 比如商品是商品上下文的一个实体通过唯一的商品 ID 来标识不管这个商品的数据如何变化商品的 ID 一直保持不变它始终是同一个商品。 值对象 Value Object 依附于实体存在通过对象属性来识别的对象它将一些相关的实体属性打包在一起处理形成一个新的对象。 这些对象是用来表示临时的事物或者可以认为值对象是实体的属性这些属性没有特性标识但同时表达了领域中某类含义的概念。 通常值对象不具有唯一id由对象的属性描述可以用来传递参数或对实体进行补充描述。 举个栗子 比如用户实体包含用户名、密码、年龄、地址地址又包含省市区等属性而将省市区这些属性打包成一个属性集合就是值对象。 值对象与实体的区别是什么 值对象没有唯一标识和连续性任何属性发生变化, 都可以认为是新的值对象。判断对象是否相同值对象需要判断所有属性是否相同而实体只需要判断唯一标识是否相同。值对象一般依附于实体而存在是实体属性的一部分而非独立存在。值对象属性是只读的可以被安全的共享. 值对象 将一个值用对象的方式进行表述来表达一个具体的固定不变的概念。 还是举个订单的例子订单是一个实体里面包含地址这个地址可以只通过属性嵌入的方式形成的订单实体对象也可以将地址通过 json 序列化一个 string 类型的数据存到 DB 的一个字段中那么这个 Json 串就是一个值对象是不是很好理解 当你只关心某个对象的属性时该对象便可作为一个值对象。 我们需要将值对象看成不变对象不要给它任何身份标识注意**不要给它任何身份标识该对象便可作为一个值对象。 ** 注意应该尽量避免像实体对象一样的复杂性。 聚合 实体和值对象表现的是个体的能力而我们的业务逻辑往往很复杂依赖个体是无法完成的这时候就需要多个实体和值对象一起协同工作而这个协同的组织就是聚合。 聚合是数据修改和持久化的基本单元同一个聚合内要保证事务的一致性所以在设计的时候要保证聚合的设计拆分到最小化以保证效率和性能。 聚合根 也叫做根实体一个特殊的实体它是聚合的管理者代表聚合的入口抓住聚合根可以抓住整个聚合。 领域服务 有些领域的操作是一些动词并不能简单的把他们归类到某个实体或者值对象中。 领域的动作从领域中识别出来之后应该将它声明成一个服务它的作用仅仅是为领域提供相应的功能。 简单理解 就是业务方法 领域事件 在特定的领域由用户动作触发表示发生在过去的事件或者领域状态的变化。 比如: 充值成功充值失败的事件。 DDD四种模式 接下来看看DDD四种模式 失血模型 模型中只有简单的get set方法是对一个实体最简单的封装其他所有的业务行为由服务类来完成。 pojo里边光秃秃的get和set方法都没有 Data ToString public class User {private Long id;private String username;private String password;private Integer status;private Date createdAt;private Date updatedAt;private Integer isDeleted; }而且其他所有的业务行为由服务类来完成 public class UserService{public boolean isActive(User user){return user.getStatus().equals(StatusEnum.ACTIVE.getCode());} }贫血模型 贫血模型是指领域对象里只有get和set方法POJO所有的业务逻辑都不包含在内而是放在Business Logic层。 Data ToString public class User {private Long id;private String username;private String password;private Integer status;private Date createdAt;private Date updatedAt;private Integer isDeleted;public boolean isActive(User user){return user.getStatus().equals(StatusEnum.ACTIVE.getCode());}public void setUsername(String username){return username.trim();} }贫血模型在失血模型基础之上领域对象的包含一些状态变化但是停留在内存层面不关心数据持久化。 贫血模型所有的业务逻辑都不包含在内而是放在Business Logic层。 贫血模型优点是系统的层次结构清楚各层之间单向依赖Client-Business Facade-Business Logic-Data Access Object。 可见领域对象几乎只作传输介质之用不会影响到层次的划分。 这就是 传统的 数据驱动的开发。 在使用Spring的时候通常暗示着你使用了贫血模型我们把Domain类用来单纯地存储数据Spring管不着这些类的注入和管理Spring关心的逻辑层比如单例的被池化了的Business Logic层可以被设计成singleton的bean。 假使我们这里逆天而行硬要在Domain类中提供业务逻辑方法那么我们在使用Spring构造这样的数据bean的时候就遇到许多麻烦比如bean之间的引用可能引起大范围的bean之间的嵌套构造器的调用。 充血模型 在贫血模型基础上负责数据的持久化。 Data ToString public class User {private Long id;private String username;private String password;private Integer status;private Date createdAt;private Date updatedAt;private Integer isDeleted;private UserRepository userRepository;public boolean isActive(User user){return user.getStatus().equals(StatusEnum.ACTIVE.getCode());}public void setUsername(String username){this.username username.trim();userRepository.update(user);} }充血模型层次结构和上面的差不多不过大多业务逻辑放在Domain Object里面Business Logic只是简单封装部分业务逻辑以及控制事务、权限等这样层次结构就变成Client-Business Facade-Business Logic-Domain Object-Data Access Object。 它的优点是面向对象Business Logic符合单一职责不像在贫血模型里面那样包含所有的业务逻辑太过沉重。 胀血模型 service都不需要所有的业务逻辑、数据存储都放到一个类中。 对于DDD来说失血和胀血都是不合适的 失血太轻量没有聚合胀血那是初学者才这样写代码。 那么充血模型和贫血模型该怎么选择 充血模型依赖repository接口与数据存储紧密相关有破坏程序稳定性的风险。 DDD建模方法 用例分析法四色建模法事件风暴法 用例分析法 用例分析法是领域建模最简单可行的方式。 大致可以分为获取用例、收集实体、添加关联、添加属性、模型精化几个步骤。 获取用例提取领域规则描述收集实体定位实体添加关联两个实体间用动词关联起来添加属性获取实体属性模型精化可选的步骤可以用UML的泛华和组合来表达模型间的关系同时可以做子领域的划分 四色建模法 四色建模法源于《Java Modeling In Color With UML》它是一种模型的分析和设计方法 通过把所有模型分为四种类型帮助模型做到清晰、可追溯。 简单来说四色关注的是某个人的角色在某个地点的角色用某个东西的角色做了某件事情。 Peter Coad和Mark Mayfield奠定了4种架构型一种形式所有的东西都或多或少地遵守的早期工作。 4种架构型 Archetype MomentInterval 时刻时段 类型的 Archetype 一个时刻或一段时间粉红色表示。 一次销售是在一个时刻完成的——这次销售的日期和时间。一次租赁发生在一个时间段——从登记入住到归还。 Role 角色 类型的 Archetype 角色是一种参与方式 角色由 PartyPlaceThing 类型模型 来承担黄色表示。Description 描述 类型的 Archetype Description 是类似 “分类目录条目似的” 描述 Description 是一组反复应用的值 Description 也为所有对应到某个描述的东西提供行为蓝色表示。PartyPlaceThing 参与方-地点-物品 类型的 Archetype PartyPlaceThing 包括 参与方意味着人或组织机构、地点或物品 PartyPlaceThing 是扮演不同角色的人或物 PartyPlaceThing 绿色表示。 这4种颜色中, 每一种都对应一种架构型的特征属性、链接、方法、插入点和交互每一种颜色对应的类或多或少都包含了这些特性。 如何确定一个类的颜色类型 它是某个时刻或时段是出于某个原因业务原因或法律原因是系统需要追踪的东西吗 如果是这样那么它是粉红色的、时刻时段 MomentInterval 类型的 Archetype。否则它是一个角色吗 如果是这样那么它是黄色类型的、Role 角色 类型的 Archetype。否则它是一个类似分类目录似的描述包含了一组可以反复应用的值吗 如果是这样那么它是蓝色的、Description 描述 类型的 Archetype。否则它就是绿色的、PartyPlaceThing 参与方-地点-物品 类型的 Archetype。 事件风暴法 事件风暴法类似头脑风暴简单来说就是谁在何时基于什么做了什么产生了什么影响了什么事情。 DDD分层架构 DDD的概念 领域驱动设计围绕着领域模型进行设计通过分层架构将领域独立出来。 这里有两个关键词 领域模型分层架构 领域模型的对象包括实体、值对象和领域服务领域逻辑都应该封装在这些对象中。 聚合是一种边界它可以封装一到多个实体与值对象并维持该边界范围之内的业务完整性。在聚合中至少包含一个实体且只有实体才能作为聚合根。工厂和资源库都是对领域对象生命周期的管理。工厂负责领域对象的创建用于封装复杂或者可能变化的创建逻辑。资源库负责从存放资源的持久层获取、添加、删除或者修改领域对象。 传统的三层架构 传统的MVC模型把框架分成了三层显示层、控制层、模型层。 显示层负责显示用户界面控制层负责处理业务逻辑、而模型则负责与数据库通信对数据进行持久化的操作。 从代码角度来看这样的框架结构每个模块职责分离特别适合小型的应用系统。 最简单的分层方式自然就是“表现层、业务逻辑层和数据访问层”如下图 其中包含一些 帮助类/工具类比如 SQLHelper、StringUtility之类归纳到 基础结构层。 在传统的三层架构中表现层只能跟业务逻辑层打交道而业务逻辑层 只能跟 数据访问层 打交道。 这也是一般的编程规范都要求 表现层不能访问 DAO层表现层对数据访问层的内容一无所知。 从领域驱动的角度看这种分层的方式有一定的弊端。 主要有两点 首先 基础结构层作为 公共组件为各个层面提供服务职责职责比较紊乱。 基础结构层 既可以是纯粹的技术框架又可以包含或处理一定的业务逻辑这样一来业务逻辑层与“基础结构层”之间就会存在依赖关系 其次传统的三层架构数据表驱动编程或者可以说是面向数据表编程总之这种结构过分地突出了“数据访问”的地位。 这种面向数据表模式中“数据访问”甚至超过了 “业务逻辑”地位。一旦在数据访问弱化的场景中甚至不存在库表的场景中很多软件人员一上来就问“我没有表难道还要三层不用三层该怎么办 这就问题来了。 另外随着业务复杂度的上升会发现服务层的逻辑以及代码不断增长变得庞大且复杂、测试成本直线上升。由于各个Service的逻辑散落在各处后续新需求的维护的成本也非常高导致交付效率越来越低稳定性风险也越来越高。 除了日常开发的一些问题外由于缺乏一定的业务知识沉淀如果文档沉淀或者更新不及时的情况下新同学来接受一个新的小需求面对产品描述的需求改动点开发同学根本无从下手。一方面是新同学业务的生疏但真正根本原因还是开发与产品之间的语言不能保持一致双方对于同一事物的表达和理解有很大的区别。产品描述的更多是实际的业务场景而开发则更关注背后的具体实现逻辑加之文档的缺失可以说是面对一堆模型和代码两眼茫然。 怎么办DDD来解决这摊子问题。 DDD领域驱动设计的分层架构 DDD将软件系统分为四层基础结构层、领域层、应用层和表现层。 在**《领域驱动设计——软件核心复杂性的应对之道》**一书中DDD 范式的创始人 Evans 提出下图所示的这样一种分层架构 整个系统划分为 基础设施层Infrastructure领域层Domain应用层Application表示层也称为 用户接口层 User Interface 与上述的三层相比**数据访问层DAO层**已经不在了它被移到基础结构层了。 基础结构层 Infrastructure Layer 该层专为其它各层提供技术框架支持。 基础结构层的基本原则不会涉及任何业务知识与业务无关不涉及业务逻辑。 为啥数据访问的内容移动到了基础结构层 因为 数据的CRUD本质上不涉及业务逻辑或者说数据的读写是业务无关的。所以数据访问的内容也被放在了该层当中。 Infrastructure Layer 一些例子 领域层需要持久化服务在DDD中领域层通过仓储Repository接口定义持久化需求基础设施层通过采用JDBC、JPA、Hibernate、NoSQL等技术之一实现领域层的仓储接口为领域层提供持久化服务。领域层需要消息通知服务在领域层中定义了一个NotificationService领域服务接口基础设施层通过采用手机短信、电子邮件、Jabber等技术实现NotificationService领域服务接口为领域层提供消息通知服务。用户接口层需要一个对象序列化服务将任何JavaBean序列化为JSON字符串可以在用户接口层定义一个ObjectSerializer服务接口基础设施层通过采用Gson实现这一接口为用户接口层提供对象序列化服务。 领域层 Domain Layer 包含了业务所涉及的领域对象实体、值对象、领域服务以及它们之间的关系。 这部分内容的具体表现形式是 领域模型Domain Model。 DDD提倡富领域模型即尽量将业务逻辑归属到领域对象上实在无法归属的部分则以领域服务的形式进行定义。 什么是业务逻辑 业务逻辑就是存在于问题域即业务领域中的实体、概念、规则和策略等业务逻辑与软件实现无关主要包含下面的内容 业务实体领域对象。例如银行储蓄领域中的账户、信用卡等等业务实体。业务规则。例如借记卡取款数额不得超过账户余额信用卡支付不得超过授信金额转账时转出账户余额减少的数量等于转入账户余额增加的数量取款、存款和转账必须留下记录等等。业务策略。例如机票预订的超订策略卖出的票的数量稍微超过航班座位的数量以防有些旅客临时取消登机导致座位空置等。完整性约束。例如账户的账号不得为空借记卡余额不得为负数等等。本质上完整性约束是业务规则的一部分。业务流程。例如“在线订购”是一个业务流程它包括“用户登录-选择商品-结算-下订单-付款-确认收货”这一系列流程。 对领域层的进一步说明如下 领域层实现映射到领域模型是设计维度的领域模型Domain Model在软件中的具体实现。包含实体、值对象和领域服务等领域对象通常这些领域对象和问题域中的概念实体一一对应具有相同或相似的属性和行为。在实体、值对象和领域服务等领域对象的方法中封装实现业务规则和保证完整性约束这一点是与CRUD模式相比最明显的差别CRUD中的领域对象没有行为。领域对象在实现业务逻辑上具备坚不可摧的完整性意味着不管外界代码如何操作都不可能创建不合法的领域对象例如没有账户号码或余额为负数的借记卡对象亦不可能打破任何业务规则例如在多次转账之后钱凭空丢失或凭空产生。领域对象的功能是高度内聚的具有单一的职责任何不涉及业务逻辑的复杂的组合操作都不在领域层而在应用层中实现。领域层中的全部领域对象的总和在功能上是完备的意味着系统的所有行为都可以由领域层中的领域对象组合实现。 应用层/工作流层 Application Layer 应用层是领域驱动中最有争议的一个层次也会有很多人对其职责感到模糊不清。 应用层不包含任何领域逻辑但它会对任务进行协调并可以维护应用程序的状态 因此应用层更注重流程性的东西。在某些领域驱动设计的实践中也会将其称为“工作流层”。 既然不包含领域逻辑那应用层又如何协调工作任务呢 它通过排列组合领域层的领域对象来实现用例它的职责可表示为“编排和转发” 具体来说Application Layer 将要实现的功能委托给一个或多个领域对象来实现Application Layer 只负责安排工作顺序和拼装操作结果。 如果一定要进行类比的话 Application Layer 的职责 类似于微服务领域的 SpringCloud gateway或者服务总线Service Bus。 表现层/用户接口层User Interface 这个好理解跟三层架构里的表现层Controller意思差不多 表现层依赖于应用层但是表现层与应用层之间是通过数据传输对象DTO进行交互的 DTO数据传输对象是没有行为的POCOC#中的概念对象DTO的目的只是为了对领域对象Domain Object中的数据进行封装剥离了Domain Object中的行为实现层与层之间的数据传递。 为何不能直接将 Domain Object 用于数据传递两个原因 1) 因为Domain Object 更注重领域而DTO更注重数据。 (2) 由于“富领域模型”的特点这样做会直接将Domain Object 的行为暴露给表现层。 表现层为外部用户访问底层系统提供交互界面和数据表示。 表现层在底层系统之上封装了一层可访问外壳为特定类型的外部用户人或计算机程序访问底层系统提供访问入口并将底层系统的状态数据以该类型客户需要的形式呈现给它们。 表现层有两个任务 1从用户处接收命令操作改变底层系统状态 2从用户处接收查询操作将底层系统状态以合适的形式呈现给用户。 表现层说明 o 典型的用户是人类用户但是也可能是别的计算机系统。例如如果 ERP 系统要访问我们的系统获取信息它也是一种用户。 o 不同类型的用户需要不同形式的用户接口例如为人类用户提供 Web 界面和手机 App为 ERP 软件用户提供 REST 服务接口。所以REST 服务接口 也算是表现层。 o 不同类型的用户需要不同形式的数据表示包括表现形式的不同XML、JSON、HTML和内容的不同例如手机 App 中呈现的数据内容往往比 Web 页面中呈现的少。 o 表现层对应用层进行封装表现层的操作与应用层上定义的操作通常是一一对应的关系。表现层从外部用户处接受输入转换成应用层方法的参数形式调用应用层方法将任务交由底层系统执行并将返回结果转换成合适的形式返回给外部用户。 表现层的典型任务是下面三个 校验——校验外部客户输入的数据是否合法转换——将外部客户的输入转换成对底层系统的方法调用参数以及将底层系统的调用结果转换成外部客户需要的形式转发——将外部客户的请求转发给底层系统。 表现层也被称为用户界面层或用户接口层。 有时候为了某些需要我们可以从表现层中分离出一个亚层可命名为门面层Facade。位于真正的表现层和应用层之间。 门面层(Faced Layer) 门面层隔离前台和后台系统定义特定于表现层的数据结构从后台获取数据内容并转化为表现层的数据形式。 从表现层中分离出专门的门面层具有下面的优势 使得表现层能够独立于后台系统与后台系统并行开发。 表现层通过门面层接口达到和应用层、领域层解耦意味着表现层可以独立开发不必等待后台系统的完成亦不受后台系统重构的影响在需求调研阶段系统原型出来并得到用户确认之后就可以开始表现层的开发了。 可以根据界面原型定义表现层需要的数据结构该数据结构与底层数据结构解耦不需要知道底层数据类型和数据之间的关联关系。将应用层DTO数据和界面数据连接起来并相互转换是门面层实现类的职责这方面工作可以等待前后台系统分别完成之后进行。 使得分布式部署成为可能。 如果没有门面层的隔离表现层只能直接使用领域层的领域对象作为自己的数据展现结构。 这样我们就不能将系统进行分布式部署将表现层和后台系统领域层、应用层等分别部署到不同的服务器上。 因为在JPA和Hibernate等技术实现中领域实体绑定到当前服务器的持久化上下文中必须脱管之后才能够跨越JVM进行传输。 避免Hibernate中“会话已关闭”的问题消除成本巨大的“Open Session in View”模式的需要。 在采用JPA或Hibernate作为持久化手段的系统中存在臭名昭著的“会话已关闭”问题对付这一问题的主要手段是使用Open Session in View方案但是这个方案的性能很低。 把事务作用范围控制在后端缩短事务的跨度提升性能和系统的吞吐量。 更大的问题是事务问题事务要跨越服务器的边界复杂性增加性能严重下降。门面层的存在使得实体和事务都限制在后台系统不需要扩展到前台服务器。 如果不采用门面层隔离后台数据结构在前端展现数据需要访问实体的延迟初始化属性时就会遇到“会话已关闭”问题而采用Open Session in View模式去解决的话就意味着事务不是在后端独立完成这样事务就扩展到前端表现层在大流量、高吞吐的网站上把事务扩展到前端界面做造成事务时间跨度极度拉长从而带来严重的严重的性能问题大大降低吞吐量。 采用门面模式的话有关联关系的数据在后台拼装完毕再一次性返回给前端事务局限在后端范围不再有“会话已关闭”和性能问题。 门面层说明 门面层特定于表现层由表现层定义和控制包括操作和数据的形式和内容这意味着需要为不同类型的表现层开发专门的门面层。查询结果通常以数据传输对象DTO的形式表示。DTO的结构由表现层而不是后端决定代表前端需要的数据形式与底层数据结构脱耦。通过门面层实现类访问后端的应用层。实现类将后端数据拼装为DTO并返回给前端它可以将数据装配职责委托给专门的Assembler工具类去执行。在分布式系统中可以在前端和后端分别部署门面层。前后端的门面层接口相同但后端的门面层实现类负责数据装配和发布前端的门面层实现类负责通过某种通信机制Web Service等与后端门面层通讯获取后者装配好的数据。传输过程中DTO可能序列化为JSON或XML等形式。 分层架构的优点 分层架构的目的是通过关注点分离来降低系统的复杂度同时满足单一职责、高内聚、低耦合、提高可复用性和降低维护成本。 单一职责每一层只负责一个职责职责边界清晰如持久层只负责数据查询和存储领域层只负责处理业务逻辑。高内聚分层是把相同的职责放在同一个层中所有业务逻辑内聚在领域层。 这样做有什么好处呢试想一下假如业务逻辑分散在每一层修改功能需要去各层修改测试业务逻辑需要测试所有层的代码这样增加了整个软件的复杂度和测试难度。低耦合依赖关系非常简单上层只能依赖于下层没有循环依赖。可复用某项能力可以复用给多个业务流程。 比如持久层提供按照还款状态查询信用卡的服务既可以给申请信用卡做判断使用也可以给展示未还款信用卡使用。易维护面对变更容易修改。把所有对外接口都放在对外接口层一旦外部依赖的接口被修改只需要改这个层的代码即可。 以上这些既是分层的好处也是分层的原则大家在分层时需要遵循以上原则不恰当的分层会违背了分层架构的初衷。 分层架构的缺点 开发成本高因为多层分别承担各自的职责增加功能需要在多个层增加代码这样难免会增加开发成本。但是合理的能力抽象可以提高了复用性又能降低开发成本。性能略低业务流需要经过多层代码的处理性能会有所消耗。可扩展性低因为上下层之间存在耦合度所有有些功能变化可能涉及到多层的修改。 DDD的特点 DDD到底能帮助我们什么呢 用一段话总结下DDD改变了数据表驱动的设计方式设计驱动方向变为Domain Model驱动 DDD设计的时候首先将业务概念和业务规则 转换为 Domain Model(包括类型以及类型的属性与行为)而不是将业务概念和业务规则转换为 Entity和表结构 DDD设计通过运用面向对象的封装、继承、多态等设计要素降低或隐藏整个系统的业务复杂性使得复杂的分布式系统具有更好的扩展性应对纷繁多变的现实业务问题。 DDD试图解决的是软件的复杂性问题如果软件比较复杂或者是预期会很复杂那么都可以开始考虑DDD。 由于维系领域模型需要实现大量的封装和隔离DDD会带来较大的成本如果系统比较简单未来也不会有太多复杂的扩展那么传统的数据表驱动(/数据模型驱动)的设计方式更为合适。 使用DDD的好处 领域模型是整个软件的核心是软件中最有价值和最具竞争力的部分设计足够精良且符合业务需求的领域模型能够更快速的响应需求变化。通用领域模型语言在有界的上下文中形成统一的语言方便与产品同学或者开发同学之间沟通减少歧义和沟通成本。通用领域模型语言DDD帮助统一语言在有界的上下文中形成通用的语言方便与产品同学或者开发同学之间沟通减少歧义和沟通成本。业务领域知识沉淀领域驱动设计的核心是建立统一的领域模型领域模型不同于数据模型和任何技术实现及存储实现无关只反映业务本身业务通过核心稳定的领域模型领域知识进行传递沉淀业务知识。系统的架构设计传统的开发设计方式数据模型驱动是从数据出发设计数据库表编写DAO然后进行业务实现。而领域驱动设计从领域出发分析领域内模型及其关系并进行领域建模设计核心业务逻辑完成了领域模型与数据模型分离业务复杂度与技术复杂度分离。系统的具体实现领域驱动设计领域建模完成后确定了业务和应用边界保证业务模型与代码模型的一致性进而再进行技术细节实现。领域模型确保了我们的软件的业务逻辑都在一个模型模块中提高软件的可维护性业务可理解性以及可重用性。系统的扩展性领域模型划分出的边界沉淀的核心稳定的领域模型知识面对新来的需求可以快速判断需求的合理性需求的归属子域应该在哪个模块实现通过不断的抽象、不断的分治、拉齐团队内成员对需求的认知应对系统复杂性让设计更加清晰和规范。 DDD的难点 DDD有这么多优势为什么大家使用的还是不够多呢任何一个事物都有两面性不可能是完美DDD也有很多问题而且有些问题可能就是致命的让人望而却步的要求、难度系数高领域模型的正确构建首先需要有一个熟悉业务、建模的领域专家其次依赖编程人员对DDD的深刻理解对团队成员的本身素质要求较高。效率投入产出比正确的建模从方案讨论、设计、实践、落地往往需要花费一段时间面对业务紧急的需求以及倒排的工期可能满足不了上线的要求。而其他一般架构不需要这些时间短期投入成本高但是从长期看领域模型收益还是很高的。团队成员之间协作领域模型一般是整体的领域内是相互依赖的相互影响的不容易分割为可并行独立解耦开发的模块对开发同学的协作要求较高。技术上的缺陷DDD是基于聚合来组织代码对于高性能场景下加载聚合中大量的无用字段会严重影响性能实际场景中更多的高流量查询往往脱离聚合直接对某一个数据进行查询。此外事务被限定在限界上下文中跨多个限界上下文的场景需要开发者额外考虑分布式事务问题。 深入DDD 领域驱动设计是一种思维方式也是一组优先任务是针对复杂系统设计的一套软件工程方法。分为领域分析建模、领域设计建模与领域实现建模三个过程。 领域驱动设计围绕着领域模型进行设计通过分层架构将领域独立出来。 这里有两个关键词 领域模型分层架构 领域模型的对象包括实体、值对象和领域服务领域逻辑都应该封装在这些对象中。 聚合是一种边界它可以封装一到多个实体与值对象并维持该边界范围之内的业务完整性。 在聚合中至少包含一个实体且只有实体才能作为聚合根。 工厂和资源库都是对领域对象生命周期的管理。 工厂负责领域对象的创建用于封装复杂或者可能变化的创建逻辑。 资源库负责从存放资源的持久层获取、添加、删除或者修改领域对象。 聚合(aggregate) 聚合是领域对象的显式分组我们把一些关联性极强、生命周期一致的实体、值对象放到一个聚合里。 聚合定义了一组具有内聚关系的相关对象的集合每个聚合都有一个根对象聚合根实体。 我们把聚合看作是一个修改数据的单元。 一个聚合是一组相关的被视为整体的对象。每个聚合都有一个根对象聚合根实体从外部访问只能通过这个对象。 根实体对象有组成聚合所有对象的引用但是外部对象只能引用根对象实体。只有聚合根才能使用仓储库直接查询其它的只能通过相关的聚合访问。如果根实体被删除聚合内部的其它对象也将被删除。 为啥需要进行聚合旨在支持领域模型的行为和不变性同时充当一致性和事务性边界。 聚合有两个核心要素 一个聚合根一个上下文边界 这个边界 根据业务单一职责和高内聚原则定义了聚合内部应该包含哪些实体和值对象而聚合之间的边界是松耦合的。 按照这种方式设计出来的服务很自然就是“高内聚、低耦合”的。 聚合在 DDD 分层架构里属于领域层领域层包含了多个聚合共同实现核心业务逻辑。 聚合根(aggregate root) 如果把聚合比作组织那聚合根就是这个组织的负责人。 聚合根也称为根实体它不仅是实体还是聚合的管理者。 首先它作为实体本身拥有实体的属性和业务行为实现自身的业务逻辑。其次它作为聚合的管理者在聚合内部负责协调实体和值对象按照固定的业务规则协同完成共同的业务逻辑。最后在聚合之间它还是聚合对外的接口人以聚合根 ID 关联的方式接受外部任务和请求在上下文内实现聚合之间的业务协同。也就是说聚合之间通过聚合根 ID 关联引用如果需要访问其它聚合的实体就要先访问聚合根再导航到聚合内部实体外部对象不能直接访问聚合内实体。 上面讲的还是有些抽象下面看一个图就能很好理解同样是源于极客时间欧创新的DDD实战课 简单概括一下 通过事件风暴我理解就是头脑风暴不过我们一般都是先通过个人理解然后再和相关核心同学进行沟通得到实体和值对象将这些实体和值对象聚合为“投保聚合”和“客户聚合”其中“投保单”和“客户”是两者的聚合根找出与聚合根“投保单”和“客户”关联的所有紧密依赖的实体和值对象在聚合内根据聚合根、实体和值对象的依赖关系画出对象的引用和依赖模型。 服务services 服务提供的操作是它提供给使用它的客户端并突出领域对象的关系。 所有的service只负责协调并委派业务逻辑给领域对象进行处理其本身并未真正实现业务逻辑绝大部分的业务逻辑都由领域对象承载和实现了。 service可与多种组件进行交互这些组件包括其他的service、领域对象和repository 或 dao。 服务又细分为领域服务和应用服务。 领域服务Domain Service 接下来看看领域服务和应用服务两个核心概念。 领域中的一些概念如果是名词适合建模为对象的一般归类到实体对象或值对象。 如果是动词比如一些操作、一些动作代表的是一种行为如果是和实体或值对象密切相关的也可以合并到某个实体或者值对象中。 但是有些操作不属于实体或者值对象本身或会涉及到多个领域对象并且需要协调这些领域对象共同完成这个操作或动作这时就需要创建领域服务来提供这些操作。 当一些逻辑不属于某个实体时可以把这些逻辑单独拿出来放到领域服务中。可以使用领域服务的情况 执行一个显著的业务操作对领域对象进行转换以多个领域对象作为输入参数进行计算结果产生一个值对象 领域服务有两个特征 1操作代表了一个领域概念且不是实体或者值对象的一个自然的部分2被执行的操作涉及领域中的其他对象操作是无状态的。领域服务还有一个好处可以避免领域逻辑泄露到t应用层。 因为如果没有领域服务那么应用层会直接调用领域对象完成本该是属于领域服务该做的操作。 此外如果实体操作过多或者过大为了避免臃肿也可以使用领域服务来解决。 但是不能把所有的东西都搬到领域服务里过度使用可能会导致产生的太多的贫血对象。 理想的情况是没有领域服务如果领域服务使用不恰当慢慢又演化回了以前逻辑都在 service 层的局面 应用服务Application Service 应用层作为展现层与领域层的桥梁是用来表达用例和用户故事的主要手段。 应用层通过应用服务接口来暴露系统的全部功能。 在应用服务的实现中它负责编排和转发它将要实现的功能委托给一个或多个领域对象来实现它本身只负责处理业务用例的执行顺序以及结果的拼装。 通过这样一种方式它隐藏了领域层的复杂性及其内部实现机制。 应用层相对来说是较“薄”的一层除了定义应用服务之外在该层我们可以进行安全认证权限校验持久化事务控制或者向其他系统发生基于事件的消息通知另外还可以用于创建邮件以发送给客户等。 领域服务和应用服务的不同 领域服务和应用服务是不同的领域服务是领域模型的一部分用来处理业务逻辑而应用服务不是。应用服务是领域服务的直接客户负责处理事务、安全等操作它将领域模型变成对外界可用的软件系统。跨多个实体的业务逻辑通过领域服务来实现跨多个聚合的业务逻辑通过应用服务来实现。 跨多个实体的业务逻辑通过领域服务来实现跨多个聚合的业务逻辑通过应用服务来实现。 比如有的业务场景需要同一个聚合的 A 和 B 两个实体来共同完成我们就可以将这段业务逻辑用领域服务来实现而有的业务逻辑需要聚合 C 和聚合 D 中的两个服务共同完成这时你就可以用应用服务来组合这两个服务。 领域事件 领域事件是一个领域模型中极其重要的部分用来表示领域中发生的事件。 领域事件 事件发布 事件存储 事件分发 事件处理。 程序事件通常分为系统事件、应用事件和领域事件。领域事件的触发点在领域模型中。 它的作用是将领域对象从对repository或service的依赖中解脱出来避免让领域对象对这些设施产生直接依赖。 它的做法就是当领域对象的业务方法需要依赖到这些对象时,就发出一个事件这个事件会被相应的对象监听到并做出处理。譬如跨限界上下文时使用关键应用事件触发事件传递。 从尼恩的视角简单来说领域事件是对 repository或service 的异步解耦。 在尼恩写的深度文章《京东一面20种异步你知道几种 含协程》中就有EventBus 这样的事件总线完成模块之间的异步解耦也有 RocketMQ这样的分布式消息组件完成进程级别的异步解耦。而领域事件 是设计维度的解耦。EventBus 、RocketMQ是实现层面的异步解耦当然是先有设计后有实现。 在DDD中通过领域事件一个领域模型可以忽略不相关的领域活动同时明确领域专家要跟踪或希望被通知的事情或与其他模型对象中的状态更改相关联。 下面简单说明领域事件 事件发布构建一个事件需要唯一标识然后发布事件存储发布事件前需要存储因为接收后的事建也会存储可用于重试或对账等事件分发服务内直接发布给订阅者服务外需要借助消息中间件比如KafkaRabbitMQ等事件处理先将事件存储然后再处理。 比如下订单后给用户增长积分与赠送优惠券的需求。如果使用瀑布流的方式写代码。一个个逻辑调用那么不同用户赠送的东西不同逻辑就会变得又臭又长。 这里的比较好的方式是用户下订单成功后发布领域事件积分聚合与优惠券聚合监听订单发布的领域事件进行处理。 资源仓储/资源库Repository 仓储资源库是用来管理实体的集合。 仓储介于领域模型和数据模型之间主要用于聚合的持久化和检索。 仓储里面存放的对象一定是聚合原因是domain是以聚合的概念来划分边界的聚合作为一个整体概念要么一起被取出来要么一起被删除。 它隔离了领域模型和数据模型以便我们关注于领域模型而不需要考虑如何进行持久化。 我们将暂时不使用的领域对象从内存中持久化存储到磁盘中。 当日后需要再次使用这个领域对象时根据 key 值到数据库查找到这条记录然后将其恢复成领域对象应用程序就可以继续使用它了这就是领域对象持久化存储的设计思想。 工厂Factory 工厂用来封装创建一个复杂对象尤其是聚合时所需的知识作用是将创建对象的细节隐藏起来。 客户传递给工厂一些简单的参数然后工厂可以在内部创建出一个复杂的领域对象然后返回给客户。 工厂Factory不是必须的只有当创建实体和值对象复杂时建议使用工厂模式。 领域驱动设计一般分为两个阶段 领域驱动设计划分了战略设计和战术设计也提供了诸多模式和工具但却没有一个统一过程去规范这两个阶段需要执行的活动、交付的工件以及阶段里程碑甚至没有清晰定义这两个阶段如何衔接、它们之间执行的工作流到底是怎么样的。 除了把领域驱动设计 分为战略设计和战术设计的方法之外《解构-领域驱动设计》提出的 DDDRUP 方法。 DDDRUP 给出了更细致的步骤、步骤与步骤之间的衔接以及明确的阶段里程碑最重要的是 DDDRUP 可以串联 DDD 的所有概念和模式非常便于初学者做知识梳理和上手实践。DDDRUP 各步骤与战略战术设计的关系见下表。 对于《解构-领域驱动设计》DDDRUP 方法打击可以去看那个书籍。 这里尼恩还是使用略设计和战术设计方式进行 领域驱动设计的介绍。 战略设计侧重于高层次、宏观上去划分和集成限界上下文而战术设计则关注更具体使用建模工具来细化上下文。 1战略阶段。 在战略设计中讲求的是子域和限界上下文的划分以及各个限界上下文之间的上下游关系更偏向于软件架构帮助我们从一个宏观的角度观察和审视软件系统。 分解问题域 通常先进行需求调研收集领域知识用例设计引入限界上下文Bounded Context和上下文映射Context Map对问题域进行合理的分解识别出核心领域Core Domain与子领域SubDomain并确定领域的边界以及它们之间的关系维持模型的完整性。 架构方面 通过分层架构来隔离关注点尤其是将领域实现独立出来能够更利于领域模型的单一性与稳定性引入六边形架构可以清晰地表达领域与技术基础设施的边界CQRS 模式则分离了查询场景和命令场景针对不同场景选择使用同步或异步操作来提高架构的低延迟性与高并发能力。在对传统MVC架构系统进行改造时一般分为接口层、应用层、领域层、基础设施层。 2战术阶段。 在战术层面它主要应对的是领域的复杂性。 领域驱动设计用以表示模型的主要要素包括如下 实体Entity。一个由它的标识定义的对象叫做实体。通常实体具备唯一id能够被持久化具有业务逻辑对应现实世界业务对象。值对象Value Object。描述事物的对象更准确的说一个没有标识符描述一个领域方面的对象。聚合及聚合根(aggregate、aggregate root)。聚合是用来定义领域对象所有权和边界的领域模式。聚合定义了一组具有内聚关系的相关对象的集合每个聚合都有一个根对象聚合根实体。领域事件Domain Event。领域事件的触发点在领域模型中。它的作用避免让领域对象对repository或service设施产生直接依赖。资源库Repository。仓储资源库是用来管理实体的集合。。工厂Factory。当创建实体和值对象复杂时建议使用工厂模式。服务services。服务提供的操作是它提供给使用它的客户端并突出领域对象的关系。其细分为领域服务和应用服务。领域服务Domain Service。领域服务封装了一些域概念这些概念并不是自然建模的。领域服务是无状态的须以非常干净简洁的代码实现。应用服务Application Service。应用程序服务构成应用程序或服务层。 领域驱动设计流程 通常战略阶段和战术阶段要求领域专家和开发团队紧密配合沟通协调完成示意图如下 领域驱动的流程特点 领域驱动指的是以领域作为解决问题切入点 面对业务需求先提炼出领域概念并构建领域模型来表达业务问题 而构建过程中我们应该尽可能避免牵扯技术方案或技术细节。 而编码实现更像是对领域模型的代码翻译代码变量名、方法名、类名等中要求能够表达领域概念让人见码明义。 特点之一思维模式转变 实践 DDD 以前我最常使用的是数据驱动设计。 它的核心思路针对业务需求进行数据建模根据业务需求提炼出类然后通过 ORM 把类映射为表结构并根据读写性能要求使用范式优化表与表之间的关联关系。 数据驱动是从技术的维度解决业务问题得出的数据模型是对业务需求的直接翻译并没有蕴含稳定的领域知识/规则。 一旦需求发生变化数据模型就得发生变化对应的库表的设计也需要进行调整。 这种设计思维导致变化从需求穿透到了数据层中间并没有稳定的不易变的层级进行阻隔最终导致系统响应变化的能力很差。 特点之二协同方式转变 过去由产品同学提出业务需求研发同学根据业务需求的 产品原型 进行技术方案设计并编程实现。 这种协同方式的弊端在于无法形成能够消除认知差异的模型。 产品同学从业务角度提出用户需求这些需求可能是易变的、定制化的 而研发同学在缺少行业经验的情况下往往会选择直译即根据需求直接转换为数据模型。 而研发同学从技术实现角度设计技术方案其中涉及很多的技术细节产品同学无法从中判断是否与自己提出的业务诉求和产品规划相一致最终形成认知差异。 且认知差异会随着迭代不断被放大最后系统变成一个大泥球。 DDD 通过解锁新角色**”领域专家以及模型驱动设计**有效地降低产品和研发的认知差异。 领域专家是具有丰富行业经验和领域知识储备的人他们能够在易变的、定制化的需求中提炼出清晰的边界稳定的、可复用的领域概念和业务规则并携手产品和研发共同构建出领域模型。 领域模型是对业务需求的知识表达形式它不涉及具体的技术细节但能够指导研发同学进行编程实现因此消除了产品和研发在需求认知上的鸿沟。 而模型驱动设计则要求领域模型能够关联业务需求和编码实现模型的变更意味着需求变更和代码变更协作围绕模型为中心。 特点之三精炼循环 精炼循环指的是在统一语言提炼领域概念明确边界构建模型绑定实现过程中这些环节相互影响和反馈在不断的迭代试错-调整以最终沉淀出稳定的、深层次的模型的过程。 比如我们在提炼领域概念的时候会觉得统一语言定义不合理/有歧义此时我们就会调整统一语言的定义并重新进行提炼领域概念。 通过精炼循环我们逐步形成稳定的领域模型。在 DDD 中让领域专家来主导概念提炼、边界划分等宏观设计原因就在于领域专家的经验和行业洞见来源于过去已经迭代的无数个精炼循环因此由这些宏观设计推导出来的领域模型往往都是非常稳定的。 精炼循环的核心是循环它避免知识只朝单一方向流动最终因各环节上的认知差异最终导致模型无法在产品、领域专家和研发中达成一致、模型与实现割裂。 阶段一战略阶段 战略阶段的工作引入限界上下文Bounded Context和上下文映射Context Map对问题域进行合理的分解。 战略阶段的参与人员产品、领域专家和开发同学。这些人组成 DDD 战略设计会。 领域专家是谁懂得该领域业务知识的人领域专家一般不是技术人员通常技术人员对新软件项目的领域知识是不了解的。 领域专家通常是软件设计者或者产品经理也可能是销售、技术支持等收集业务需求的人。 技术人员与领域专家之间需要使用领域通用语言Ubiquitous Language进行业务沟通领域通用语言可以是 UML、需求文档等提前规定好的双方都能理解的语言。 在战略设计中讲求的是子域和限界上下文的划分以及各个限界上下文之间的上下游关系更偏向于软件架构帮助我们从一个宏观的角度观察和审视软件系统。 首先领域可以拆分为多个子领域。 一个领域相当于一个问题域领域拆分为子域的过程就是大问题拆分为小问题的过程。 子域还可根据需要进一步拆分为子子域拆到一定程度后有些子子域的领域边界就可能变成限界上下文的边界了。 每个领域模型都有它对应的限界上下文团队在限界上下文内用通用语言交流。 领域内所有限界上下文的领域模型构成整个领域的领域模型。 DDD 战略设计会建立领域模型领域模型可以用于指导微服务的设计和拆分。 事件风暴是建立领域模型的主要方法它是一个从发散到收敛的过程。 它通常采用用例分析、场景分析和用户旅程分析尽可能全面不遗漏地分解业务领域并梳理领域对象之间的关系这是一个发散的过程。事件风暴过程会产生很多的实体、命令、事件等领域对象我们将这些领域对象从不同的维度进行聚类形成如聚合、限界上下文等边界建立领域模型这就是一个收敛的过程。 领域通用语言和限界上下文Bounded Context 通用语言就是能够简单、清晰、准确描述业务涵义和规则的语言。限界上下文用来封装通用语言和领域对象提供上下文环境保证在领域之内的一些术语、业务相关对象等通用语言有一个确切的含义没有二义性。 通用语言 Ubiquitous Language 通用语言是团队统一的语言不管你在团队中承担什么角色在同一个领域的软件生命周期里都使用统一的语言进行交流。 那么通用语言的价值也就很明了它可以解决交流障碍这个问题使领域专家和开发人员能够协同合作从而确保业务需求的正确表达。 什么是通用语言 尼恩给大家说个大白话就是 产品经理、项目经理、架构师、开发人员、测试等项目所有人员对同一个业务词汇有统一的定义不要一词各表、鸡同鸭讲。 所以通用语言最终结果就是需求沟通没有歧义同一个词有大家都明确了的定义。 大家不要感觉这个是一件很容易做到的事情其实很难。 在实际开发过程中 产品经理以为开发懂了实际上最终的效果不是他们想要的很大程度上就是没有统一通用语言。 在事件风暴过程中通过团队交流达成共识的能够简单、清晰、准确描述业务涵义和规则的语言就是通用语言。也就是说通用语言是团队统一的语言不管你在团队中承担什么角色在同一个领域的软件生命周期里都使用统一的语言进行交流。 通用语言包含术语和用例场景并且能够直接反映在代码中。 通用语言中的名词可以给领域对象命名如商品、订单等对应实体对象而动词则表示一个动作或事件如商品已下单、订单已付款等对应领域事件或者命令。 通用语言贯穿 DDD 的整个设计过程。作为项目团队沟通和协商形成的统一语言基于它你就能够开发出可读性更好的代码将业务需求准确转化为代码设计。 下面这张图描述了从事件风暴建立通用语言到领域对象设计和代码落地的完整过程。 在事件风暴的过程中领域专家会和设计、开发人员一起建立领域模型在领域建模的过程中会形成通用的业务术语和用户故事。 事件风暴也是一个项目团队统一语言的过程。 通过用户故事分析会形成一个个的领域对象这些领域对象对应领域模型的业务对象每一个业务对象和领域对象都有通用的名词术语并且一一映射。 微服务代码来源于领域模型每个代码对象跟 领域对象一一对应。 设计过程中我们可以用一些表格来记录事件风暴和微服务设计过程中产生的领域对象及其属性。 比如领域对象在 DDD 分层架构中的位置、属性、依赖关系以及与代码对象的映射关系等。 下面是一个微服务设计实例的部分数据表格中的这些名词术语就是项目团队在事件风暴过程中达成一致、可用于团队内部交流的通用语言。 在这个表格里面我们可以看到DDD 分析过程中所有的领域对象以及它们的属性都被记录下来了除了 DDD 的领域对象我们还记录了在微服务设计过程中领域对象所对应的代码对象并将它们一一映射。 限界上下文 Bounded Context 通用语言也有它的上下文环境为了避免同样的概念或语义在不同的上下文环境中产生歧义DDD 在战略设计上提出了“限界上下文”这个概念用来确定语义所在的领域边界。 限界上下文是一个显式的语义和语境上的边界领域模型便存在于边界之内。 限界上下文包含两部分限界Bounded和上下文Context。限界就是领域的边界, 而上下文就是语义环境, 通过限界上下文让所有交流的人讨论的范围在同一个领域边界内。 边界内通用语言中的所有术语和词组都有特定的含义。 把限界上下文拆解开看限界就是领域的边界而上下文则是语义环境。 通过领域的限界上下文我们就可以在统一的领域边界内用统一的语言进行交流。 总之限界上下文用来封装通用语言和领域对象提供上下文环境保证在领域之内的一些术语、业务相关对象等概念有一个确切的含义。 领域和子域 Domain 、SubDomain 什么是领域 汉语词典领域是从事一种专门活动或事业的范围、部类或部门。” 百度百科领域具体指一种特定的范围或区域。” 两个解释有一个共同点范围。领域就是用来确定范围的范围即边界这也是 DDD 在设计中不断强调边界的原因。 在研究和解决业务问题时DDD 会按照一定的规则将业务领域进行细分当领域细分到一定的程度后DDD 会将问题范围限定在特定的边界内在这个边界内建立领域模型进而用代码实现该领域模型解决相应的业务问题。简言之DDD 的领域就是这个边界内要解决的业务问题域。 业务领域分析的过程是由浅入深从粗到细致不断迭代不断深化的过程。 在不断迭代的过程中DDD 会按照一定的规则将业务领域不断细分 领域可以进一步划分为子领域。 把划分出来的多个子领域称为子域每个子域对应一个更小的问题域或更小的业务范围。 领域的核心思想就是分治思想就是将问题域逐级细分来降低业务理解和系统实现的复杂度。 最终当领域细分到一定的程度后DDD 会将问题范围限定在特定的边界内在这个边界内建立领域模型进而用代码实现该领域模型解决相应的业务问题。 子领域的功能分类策略问题空间应该分为哪些子领域需要团队对目标系统整体进行探索并根据功能分类策略进行分解。 业务职能当目标系统运用于企业的生产和管理时与目标系统业务有关的职能部门往往会影响目标系统的子领域划分并形成一种简单的映射关系。这是康威定律的一种运用。业务产品当目标系统为客户提供诸多具有业务价值的产品时可以按照产品的内容与方向进行子领域划分。业务环节对贯穿目标系统的核心业务流程进行阶段划分然后按照划分出来的每个环节确定子领域。这也是我们最常用的策略业务概念捕捉目标系统中一目了然的业务概念将其作为子领域。 通过领域细分逐步缩小服务需要解决的问题域构建合适的领域模型。 举个简单的例子 对于保险领域我们做第一次解耦可以把保险细分为承保、收付、再保以及理赔等子域 而承保子域还可以继续解耦细分为投保、保全寿险、批改财险等子子域。 核心域Core Domain、通用域和支撑域 子域可以根据重要程度和功能属性划分为如下 核心域决定产品和公司核心竞争力的子域它是业务成功的主要因素和公司的核心竞争力。通用域没有太多个性化的诉求同时被多个子域使用的通用功能的子域。支撑域但既不包含决定产品和公司核心竞争力的功能也不包含通用功能的子域。 核心域、支撑域和通用域的主要目标识别出资源投入的优先级。通过领域划分区分不同子域在项目内的不同功能属性和重要性从而项目可对不同子域采取不同的资源投入和建设策略其关注度也会不一样。 公司在 IT 系统建设过程中由于预算和资源有限对不同类型的子域应有不同的关注度和资源投入策略好钢要用在刀刃上。 很多公司的业务表面看上去相似但商业模式和战略方向是存在很大差异的因此公司的关注点会不一样在划分核心域、通用域和支撑域时其结果也会出现非常大的差异。 比如同样都是电商平台的淘宝、天猫、京东和苏宁易购他们的商业模式是不同的。 淘宝是 C2C 网站个人卖家对个人买家; 而天猫、京东和苏宁易购则是 B2C 网站是公司卖家对个人买家。 即便是苏宁易购与京东都是 B2C 的模式苏宁易购是典型的传统线下卖场转型成为电商京东则是直营加部分平台模式。 因此在公司建立领域模型时我们就要结合公司战略重点和商业模式重点关注核心域。 注意通用语言贯穿 DDD 的整个设计过程。作为项目团队沟通和协商形成的统一语言在说某通用语言时必须要限定在某个上下文内以确保每个上下文含义在它特定的边界内都有唯一的含义。 上下文映射图Context Mapping - 上下文 Context集成 上下文映射图的英文是 Context Map 其实这个翻译挺难理解的上下文映射图其实就是不同上下文是如何进行交流的关系。 尼恩提示这里的用词雷死人不要命。 eric evans埃里克埃文斯搞得那么玄乎其实就是 不同 Context 上下文之间的依赖关系。 三种Context集成方式 RPC 方式消息队列或者发布 - 订阅机制RESTful 方式 上下文映射 依赖的种类 合作关系 合作关系存在于两个团队之间。 每个团队各自负责一个限界上下文。两个团队通过互相依赖的一整套目标联合起来形成合作关系。一损俱损一荣俱荣。 由于相互之间的联系非常紧密他们经常会对同步日程安排和相互关联的工作。他们还必须使用持续集成对保持集成工作协调一致。 共享内核 两个或者多个团队之间共享着一个小规模但却通用的模型。 团队必须就要共享的模型元素达成一致。有可能他们当中只有一个团队会维护构建及测试共享模型的代码。 客户 - 供应商 两个限界上下文中一方是供应商处于上游一方是客户方处于下游。支配这种关系的是供应商因为它必须提供客户需要的东西。客户需要与供应商共同制订规划来满足各种预期但最终却还是由供应商来决定客户获得的是什么以及何时获得。 跟随者 上游团队没有任何动机去满足下游团队的具体需求。由于各种原因下游团队也无法投入资源去翻译上游模型的通用语言来适应自己的特定需求因此只能顺应上游的模型。例如当一个团队需要与一个非常庞大复杂的模型集成而且这个模型已经非常成熟时团队往往会成为它的跟随者。 防腐层 ACL 这是最具防御性的上下文映射关系下游团队在其通用语言模型和位于它上游的通用语言模型之间创建了一个翻译层。 防腐层隔离了下游模型与上游模型并完成了两者之间的翻译。 引入防腐层的目的是为了隔离耦合。防腐层往往位于下游通过它隔离上游上下文发生的变化。 所以这也是一种集成方式。 尼恩提示防腐层 就更加 文绉绉了其实就是 适配层。 TMD写到这里尼恩想吐。 开放主机服务 OHS 开放主机服务会定义一套协议或者接口让限界上下文可以被当做一组服务访问。 开放主机服务定义公开服务的协议亦称为“服务契约”包括通信方式、传递消息的格式协议让限界上下文可以被当做一组服务访问。开放主机服务也可以视为一种承诺保证开放的服务不会轻易做出变化。 对于进程内的开放主机服务称为本地服务对应 DDD 中的应用服务。 对于进程间的开放主机服务成为远程服务。根据选择的分布式通信技术的不同又可以定义出类型不同的远程服务 面向服务行为比如基于 RPC称为提供者Provider面向服务资源比如基于 REST称为资源Resource面向事件比如基于消息中间件称为订阅者Subscriber面向视图模型比如基于 MVC称为控制器Controller 该协议是开放的所有需要与限界上下文进行集成的客户端都可以相对轻松地使用它。 通过应用程序编程接口提供的服务都有详细的文档用起来也很舒服。 尼恩提示 u表示 upstream 上游d 表示down stream下游。 阶段二战术阶段 战术设计也称为战术建模从技术视角出发以领域模型为输入通过限界上下文作为服务划分的边界进行微服务拆分 在每个微服务中进行领域分层实现领域服务从而实现领域模型对于代码映射目的最终实现DDD的落地实。 包括实体、值对象、聚合、聚合根、资源库、工厂、领域服务、领域事件、应用服务等代码逻辑的设计和实现。 总体设计思路面向对象。 实体和值对象 在 DDD 中实体和值对象是很基础的领域对象。 实体一般对应业务对象它具有业务属性和业务行为而值对象主要是属性集合对实体的状态和特征进行描述。但实体和值对象都只是个体化的对象它们的行为表现出来的是个体的能力。 实体 对象 具备id标识可以通过id进行相等性比较实体在聚合内唯一但是状态可变它依附于聚合根它的生命周期由聚合根管理实体一般都会持久化跟数据持久化对象存在多种对应关系一对一一对多多对一1对0实体可以引用聚合中的聚合根实体值对象。 值对象描述了领域中的一件东西这个东西是不可变的无生命周期用完即失效值对象之间通过属性值判断相等性他的核心是值是一组概念完整的属性集合用于描述实体的特征和状态值对象尽量只引用值对象。 举个栗子 人员实体原本包括姓名、年龄、性别以及人员所在的省、市、县和街道等属性。 这样显示地址相关的属性就很零碎了对不对 现在我们可以将 “省、市、县和街道等属性” 拿出来构成一个“地址属性集合”这个集合就是值对象了。 聚合和聚合根 领域模型内的实体和值对象就好比个体而聚合能让实体和值对象协同工作的组织就是聚合它用来确保这些领域对象在实现共同的业务逻辑时能保证数据的一致性。 如果把聚合比作组织聚合根则是组织的负责人聚合根也叫做根实体它不仅仅是实体还是实体的管理者。 聚合 需要做到 高内聚、低耦合它是领域模型中最底层的边界可以作为拆分微服务的最小单位。 当然一个微服务可以包含多个聚合。或者说 多个 聚合 拆分 到一个微服务。 但是在对性能有极致要求的场景中聚合可以独立作为一个微服务以满足版本的高频发布和极致的弹性伸缩能力。 一个微服务可以包含多个聚合聚合之间的边界是微服务内天然的逻辑边界。 有了这个逻辑边界在微服务架构演进时就可以以聚合为单位进行拆分和组合了微服务的架构演进也就不再是一件难事了。 怎样设计聚合 采用事件风暴用例分析、场景分析和用户旅程分析等方法列出所有业务行为和事件梳理出这些行为过程中的实体和值对象领域对象。从众多实体中选出作为对象管理者的根实体也叫聚合根。根据业务单一职责和高内聚原则找出与聚合根关联的所有的紧密依赖的实体和值对象。在聚合内根据 聚合根、实体、值对象 的依赖关系画出对象的引用和依赖模型。多个聚合根据业务语义和上下文一起划分到同一个限界上下文中。 DDD 领域建模通常采用事件风暴它通常采用用例分析、场景分析和用户旅程分析等方法通过头脑风暴列出所有可能的业务行为和事件然后找出产生这些行为的领域对象并梳理领域对象之间的关系找出聚合根找出与聚合根业务紧密关联的实体和值对象再将聚合根、实体和值对象组合构建聚合。 下面我们以保险的投保业务场景为例看一下聚合的构建过程主要都包括哪些步骤 聚合的一些设计原则 在一致性边界内建模真正的不变条件聚合内的实体和值对象按照统一的业务规则运行实现数据对象的一致性。设计小聚合降低由于业务过大导致的聚合重构的可能性。通过唯一标志引用其他聚合聚合之间通过关联外部聚合根 id 的方式引用。在边界之外使用最终一致性在一次事务中最多只能更改一个聚合的状态。若一次业务操作导致多个聚合状态的修改。可以采用领域事件异步修改相关的聚合。通过应用层实现跨聚合的服务调用为实现微服务内聚合之间的解耦以及未来以聚合为单位的微服务组合和拆分应避免跨聚合的领域服务调用和跨聚合的数据库表关联。 领域事件Domain Event 领域事件是解耦微服务的关键也是领域模型中非常重要的一部分用来表示领域中发生的事件。一个领域事将导致进一步的业务操作在实现业务解耦的同时, 还有助于形成完整的业务闭环。 如何识别领域事件 ① 在做用户旅程或者场景分析时我们要捕捉业务、需求人员或领域专家口中的关键词“如果发生……则……”、“当做完……的时候请通知……”、“发生……时则……” 等。在这些场景中如果发生某种事件后会触发进一步的操作那么这个事件很可能就是领域事件。 ② 领域事件可以切断领域模型之间的强依赖关系事件发布完成之后发布方不必关系订阅方事件处理是否成功这样可以实现领域模型之间的解耦维护领域模型的独立性和数据的一致性。在领域模型映射到微服务系统架构时领域事件可以解耦微服务微服务之间的数据不必要求强一致性而是基于事件的最终一致性。 领域事件的使用场景 微服务内的领域事件 当领域事件发生在微服务内的聚合之间领域事件发生后完成事件实体构建和事件数据持久化发布方聚合将事件发布到事件总线订阅方接收事件数据完成后续业务操作。 微服务内大部分事件的集成都发生在同一个进程内进程自身可以很好地控制事务因此不一定需要引入消息中间件。 但一个事件如果同时更新多个聚合按照 DDD “一次事务只更新一个聚合” 的原则你就要考虑是否引入事件总线。但微服务内的事件总线可能会增加开发的复杂度因此你需要结合应用复杂度和收益进行综合考虑。 微服务内应用服务可以通过跨聚合的服务编排和组合以服务调用的方式完成跨聚合的访问这种方式通常应用于实时性和数据一致性要求高的场景。这个过程会用到分布式事务以保证发布方和订阅方的数据同时更新成功。 微服务之间的领域事件 跨微服务的领域事件会在不同的限界上下文或领域模型之间实现业务协作其主要目的是实现微服务解耦减轻微服务之间实时服务访问的压力。 领域事件发生在微服务之间的场景比较多事件处理的机制也更加复杂。跨微服务的事件可以推动业务流程或者数据在不同的子域或微服务间直接流转。 跨微服务的事件机制要总体考虑事件构建、发布和订阅、事件数据持久化、消息中间件甚至事件数据持久化时还可能需要考虑引入分布式事务机制等。 微服务之间的访问也可以采用应用服务直接调用的方式实现数据和服务的实时访问弊端就是跨微服务的数据同时变更需要引入分布式事务以确保数据的一致性。分布式事务机制会影响系统性能增加微服务之间的耦合所以我们还是要尽量避免使用分布式事务。 领域事件相关案例 介绍一个保险承保业务过程中有关领域事件的案例一个保单的生成经历了很多子域、业务状态变更和跨微服务业务数据的传递。这个过程会产生很多的领域事件这些领域事件促成了保险业务数据、对象在不同的微服务和子域之间的流转和角色转换。 事件起点客户购买保险 - 业务人员完成保单录入 - 生成投保单 - 启动缴费动作。 领域事件总体架构 领域事件的执行需要一系列的组件和技术来支撑。 我们来看一下这个领域事件总体技术架构图领域事件处理包括事件构建和发布、事件数据持久化、事件总线、消息中间件、事件接收和处理等。 领域服务(Domain Service)和应用服务(Application Service) 战略层语境 领域服务通常指相对聚焦的底层支撑域/通用域服务应用服务通常指面向业务场景负责功能组装的服务 战术层语境 领域服务指领域建模工具集中所指的“领域服务” 处于领域层应用服务指面向场景的技术实现组装处于应用层 思考在战术层语境应用和领域仅仅是技术和业务的分离吗能否参考战略层语境 开发框架设计 总体设计思路 分层CQRSEDA 分层架构 在《实现领域驱动设计》一书中DDD 分层架构有一个重要的原则每层只能与位于其下方的层发生耦合。分层架构可以简单分为两种即严格分层架构和松散分层架构。 在严格分层架构中某层只能与位于其直接下方的层发生耦合而在松散分层架构中则允许某层与它的任意下方层发生耦合。 关于分层架构的优点Martin Fowler在《Patterns of Enterprise Application Architecture》一书中给出了答案 开发人员可以只关注整个结构中的某一层。可以很容易的用新的实现来替换原有层次的实现。可以降低层与层之间的依赖。有利于标准化。利于各层逻辑的复用。 “金无足赤人无完人”分层架构也不可避免具有一些缺陷 降低了系统的性能。这是显然的因为增加了中间层不过可以通过缓存机制来改善。可能会导致级联的修改。这种修改尤其体现在自上而下的方向不过可以通过依赖倒置来改善。 六边形架构 六边形架构是 Alistair Cockburn 在2005年提出解决了传统的分层架构所带来的问题实际上它也是一种分层架构只不过不是上下或左右而是变成了内部和外部。 六边形架构又名“端口-适配器架构” 六边形架构又称为端口-适配器架构这个名字更容器理解。 六边形架构将系统分为内部内部六边形和外部内部代表了应用的业务逻辑外部代表应用的驱动逻辑、基础设施或其他应用。内部通过端口和外部系统通信端口代表了一定协议以API呈现。 一个端口可能对应多个外部系统不同的外部系统需要使用不同的适配器适配器负责对协议进行转换。 这样就使得应用程序能够以一致的方式被用户、程序、自动化测试、批处理脚本所驱动并且可以在与实际运行的设备和数据库相隔离的情况下开发和测试。 CQRS架构 CQRS架构命令查询职责分离 CQRS本身只是一个读写分离的架构思想全称是Command Query Responsibility Segregation即命令查询职责分离表示在架构层面将一个系统分为写入命令和查询两部分 一个命令(如写入)表示一种意图表示命令系统做什么修改命令的执行结果通常不需要返回一个查询表示向系统查询数据并返回。 CQRS架构中另外一个重要的概念就是事件 事件表示命令操作领域中的聚合根然后聚合根的状态发生变化后产生的事件。 洋葱架构 洋葱架构针对六边形架构更进⼀步把内层的业务逻辑进一步细分 细分为了DDD概念的应⽤服务层、领域服务层和领域模型层。 特点 1围绕独⽴的领域模型构建应⽤ 2内层定义接⼝外层实现接⼝ 3依赖的方向指向圆心注意洋葱架构提倡不破坏耦合方向的依赖都是合理的外层可以依赖直接内层也可以依赖更里面的层 4所有的应用代码可以独立于基础设施编译和运⾏ 领域驱动落地框架 1 COLA框架 cola框架是阿里大佬张建飞Frank 基于DDD构建的平台应用框架。 “让COLA真正成为应用架构的最佳实践帮助广大的业务技术同学脱离酱缸代码的泥潭” csdn地址https://blog.csdn.net/significantfrank/article/details/110934799 2 leave-sample 中台架构与实现 DDD和微服务清晰地提供了从战略设计到战术设计以及代码落地。 leave-sample地址https://gitee.com/serpmelon/leave-sample 3 dddbook 阿里技术专家详解DDD系列例子精炼项目代码结构与rdfa相似极具参考价值。 dddbook地址https://developer.aliyun.com/article/719251 4 Xtoon xtoon-boot是基于领域驱动设计DDD并支持SaaS平台的单体应用开发脚手架。 重点研究如何应用。xtoon-boot提供了完整落地方案和企业级手脚架 gitee地址https://gitee.com/xtoon/xtoon-boot github地址https://github.com/xtoon/xtoon-boot 5 DDD Lite DDD 领域驱动设计微服务简化版简洁、高效、值得重点研究。 gitee地址https://gitee.com/litao851025/geekhalo-ddd 快速入门https://segmentfault.com/a/1190000018464713 快速构建新闻系统https://segmentfault.com/a/1190000018254111 6 ruoyi_cloud 若依快速开发平台以该项目建立对阳光智采和rdfa的技术框架基准线。 gitee地址https://gitee.com/y_project/RuoYi-Cloud 7 Axon Framework Axon Framework 是用来帮助开发人员构建基于命令查询责任分类(Command Query Responsibility Segregation: CQRS)设计模式的可伸缩、可扩展和可维护应用程序的框架。你只需要把工作重心放在业务逻辑的设计上。通过一些 Annotation Axon 使得你的代码和测试分离。 https://www.oschina.net/p/axon https://www.jianshu.com/p/15484ed1fbde DDD实战 这里尼恩还是使用略设计和战术设计方式进行 领域驱动设计的介绍。 step1学生管理系统的问题空间 学生管理系统Student Management System下文简称 SMS作为讲解示例以下为其问题空间的描述。 学校需要构建一个学生管理系统Student Management System, SMS。 通过这个管理系统学生可以进行选课查询成绩查询绩点。 老师则可以通过这个系统录入授课课程的成绩。录入的分数会由系统自动换算为绩点规则如下若分数 90绩点为4.090 分数 80绩点为3.080 分数 70绩点为2.070 分数 60绩点为1.0成绩 60则没有绩点并邮件通知教务员由教务员联系学生商榷重修事宜。 成绩录入后的一周内若出现录入成绩错误的情况老师可提交修改申请由教务员审核后即可完成修改。审核完成后系统会通过邮件告知老师审核结果。一周后成绩将锁定不予修改。成绩锁定后次日系统会自动计算各年级、各班的学生的总绩点总绩点由各门课程的学分与其绩点进行加权平均后所得。 而教务员则可以通过该系统发布可以选修的课程。同时教务员能够查看到各年级各班的学生的总绩点排名。 step2价值需求分析形成统一语言 根据业务需求形成统一语言有助于团队对事物的认知达成一致。 统一语言可以通过词汇表的形式展示其中词汇表最好还要包含术语对应的英文描述便于研发同学在代码层面表达统一语言。 示例-SMS 的统一语言词汇表如下。 step3 价值需求分析 价值需求分析主要做的三个工作是 识别利益相关者。利益相关者指的是与目标系统存在利益关系的人、团队或组织, 可以简单理解为目标系统的用户或与目标系统有直接交互的人、团队或组织。明确系统愿景。阐明目标系统要做什么以及为何要做。确定系统范围。确定系统问题空间的边界明确系统什么该做什么不该做。结合目标系统当前状态和未来状态进行判断。当前状态指的是系统的可用资源包括业务资源、人力资源资金资源等而未来的状态则由业务目标、组织的战略规划和产品规划共同构成。 并非任何系统都 DDDDDD 的核心是解决领域复杂性 若系统逻辑简单功能不多引入 DDD 则会得不偿失。 而在进行价值需求分析后我们便能判断是否需要通过 DDD 驱动系统的设计 因为是SMS 是演示系统所以这里不做价值需求分析 step4业务需求分析 使用业务流程、业务场景、业务服务和业务规则来表示业务需求。 业务流程表示的是一个完整的、端对端的服务过程。 业务场景按阶段性的业务目标划分业务流程就可以获得业务场景。在示例-SMS 中老师修改成绩就分为了老师“提交申请单”以及教务员“同意申请单”两个场景。 业务服务角色主动向目标系统发起服务请求完成一次完整的功能交互以实现业务目标。 业务服务是业务需求分析最核心也是最基础的单元。 角色可以是用户、策略定时任务或者其他系统完整则强调的是业务服务的执行序列的所有步骤都应该是连续且不可中断的。 而业务流程和业务场景是为了更好地分析出业务服务在示例-SMS 中的“同意申请单”场景中包含了两个业务服务教务员“同意申请单”和系统“邮件通知”教务员。 业务规则指对业务服务约束的描述用于控制业务服务的对外行为。 业务规则是业务服务正确性的基础。 常见的业务规则有 a) 意如“若… , 就….” 的需求描述比如示例-SMS 中可提炼出“若成绩录入时间间隔超过一周不予修改” b) 具有事务性的操作。 子领域 通过业务流程、业务场景和业务服务的梳理基本可以分析出业务需求所需要的业务服务。 然而在业务服务的分析过程中发现业务服务粒度太细而问题空间又太大我们需要找一个更细粒度的问题空间或者说更粗粒度的业务服务单元而这个更粒度的业务单元就是子领域。 子领域的作用 划分问题空间作为业务服务分类的边界用于分辨问题空间的核心问题和次要问题。 划分子领域的过程存在很多经验因素一个对该行业领域知识了如指掌的领域专家可以在完成价值需求分析后结合自身的领域经验能够选择合适的聚类策略并给出稳定的子领域列表。 但没有领域经验也没有关系因为根据知识消化循环思路再经历多个迭代后收敛出来的子领域划分也会逐渐合理逼急领域专家凭经验得出的子领域划分只是可能需要的时间要长一些。 识别限界上下文及其映射 示例SMS 的限界上下文可划分为 成绩上下文课程上下文审批上下文权限上下文邮件上下文 上下文及其映射是啥呢 咱们都是开发按照开发的概念来理解吧。 假设一个界限上下文对应一个微服务。 那么上面就是 5个微服务微服务之间是 rpc 调用。这种服务的发布和调用就是 上下文的映射。 发布 rest 接口的一方在DDD叫做开放主机服务OHS 调用rest 接口的一方在DDD 叫做防腐层ACL如下图所示 限界上下文 Context A 通过控制器Controller为其他上下文暴露 REST 服务而在 Context A内部呢则调用的自己的应用层的应用服务Application Service然后再调用领域层的领域模型Domain Model。 倘若限界上下文 B 需要访问限界上下文A 的rest 服务由基础设施层的客户端 如 Feign Client完成这个客户端就是上下文映射模式的防腐层ACL。而在 Context B内部呢实际不会直接调用Feign则通过放置在领域层的接口Interface去完成接口Interface去 调用由基础设施层的客户端 如 Feign Client完成真正的访问逻辑实现。 SMS 上下文映射图如下所示。 step5领域分析模型 这一步提炼出领域对象 通过名词建模动词建模和归纳抽象后可提炼出以下领域对象成绩Result、绩点gpa、总成绩total result、总绩点total gpa、学年school year、学期semester、课程course、学分credit、申请单application receipt邮件mail排名rank申请单状态application receipt status 这些领域对象之间的关系如下图所示。 step6领域设计模型 进行领域对象的聚合 聚合设计 step7服务设计 这一步包括应用服务和领域服务 注意不要把领域服务和领域对象搞混淆 下面只罗列非查询类的服务设计。 step8领域实现建模 注意这里领域实现建模关注的不是如何进行代码实现 不是编码而是领域的测试设计 而是如何验证代码实现的正确性保证实现的高质量。 领域模型与测试金字塔之间的关系 领域模型中的服务包括了应用服务、领域服务、领域行为和端口。 其中通过 Provider面向服务行为/rpc、Resource面向服务资源/rest、Subscriber面向事件、Controller面向视图模型对外进行暴露的我们称为远程服务。 面向服务行为比如基于 RPC称为提供者Provider面向服务资源比如基于 REST称为资源Resource面向事件比如基于消息中间件称为订阅者Subscriber面向视图模型比如基于 MVC的page称为控制器Controller 领域模型中的服务与测试金字塔的关系如下图所示。 测试驱动开发 领域实现建模提倡的是测试驱动开发的编程思想即要求开发者在进行逻辑实现前优先进行测试用例的编写站在调用者角度而非实现者角度去思考接口。 在上述测试金字塔中开发者需要关注的是单元测试不依赖任何外部资源的测试就是单元测试。 在领域设计建模阶段我们对业务服务/应用服务进行分解定义出了领域行为和领域服务。 对于领域行为由于其不依赖外部资源因此我们可以直接编写单元测试 而对于领域服务其可能会通过端口访问外部资源此时我们需要对端口进行 mock以隔离外部资源对领域逻辑验证的干扰。 特别地单元测试一定要覆盖所有对业务规则的验证这是保证领域行为和领域服务正确性的基础。 单元测试编码规范 测试类的命名应与被测试类保持一致为“被测类名称Test 后缀”。测试方法表达业务或业务规则为目的。测试方法体遵循 Given-When-Then 模式。Given: 为要测试的方法提供准备包括创建被测试对象为调用方法准备输入参数实参等When: 调用被测试的方法遵循单一职责原则在一个测试方法的 When 部分应该只有一条语句对被测方法进行调用Then: 对被测方法调用后的结果进行预期验证。 step9分层架构 代码架构分层是经典 DDD 四层用户接口层应用层领域层和基础设施层。 需要注意的的地方是 用户接口层根据通信方式的不同区分开了 Provider面向服务行为/RPC、Subscriber面向事件、Controller面向视图模型资源 、Task面向策略/定时任务。基础设施层单独划分了 infranstructure-impl 模块。 为了保证领域层的纯洁性DDD 通过依赖倒置把访问外部系统数据库第三方系统的服务的实现都下放到了基础设施层而 infranstructure-impl 模块 则是对这些实现进行了归集。 这样做的好处有两个第一依赖关系明确infransturcture-impl — domainapplication, interface、application、domain — infranstructure第二拆分服务更便捷。当我们需要部分领域独立拆分出来的时候在实现层面就只需要关注 infransturcture-impl 模块 即可。Infranstructure-impl 模块依赖应用层的原因是应用层可能会抽象出防腐层接口需要 infranstruct-impl 为其提供实现。 Step10.代码骨架 用户接口层 用户接口层的核心职能协议转换和适配、鉴权、参数校验和异常处理。 ├── controller //面向视图模型资源 │ ├── ResultController.java │ ├── assembler // 装配器将VO转换为DTO │ │ └── ResultAssembler.java │ └── vo // VO(View Object)对象 │ ├── EnterResultRequest.java │ └── ResponseVO.java ├── provider // 面向服务行为,比如dubbo RPC ├── subscriber // 面向事件 比如 rocketmq的客户端程序 └── task // 面向策略 比如 xxl-job的调度任务└── TotalResultTask.java应用层 应用层的核心职能编排领域服务、事务管理、发布应用事件。 ├── assembler // 装配器将DTO转换为DO │ ├── ResultAssembler.java │ └── TotalResultAssembler.java ├── dto // DTO(Data Transfer Object)对象 │ ├── cmd // 命令相关的DTO对象 │ │ ├── ComputeTotalResultCmd.java │ │ ├── EnterResultCmd.java │ │ └── ModifyResultCmd.java │ ├── event // 应用事件相关的DTO对象, subscriber负责接收 │ └── qry // 查询相关的DTO对象 └── service // 应用服务├── ResultApplicationService.java├── event // 应用事件用于发布└── adapter // 防腐层适配器接口领域层 代码组织以聚合为基本单元。 ├── result // 成绩聚合 │ ├── entity // 成绩聚合内的实体 │ │ └── Result.java │ ├── service // 领域服务 │ │ ├── ResultDomainService.java │ │ ├── event // 领域事件 │ │ ├── adapter // 防腐层适配器接口 │ │ ├── factory // 工厂 │ │ └── repository // 资源库 │ │ └── ResultRepository.java │ └── valueobject // 成绩聚合的值对象 │ ├── GPA.java │ ├── ResultUK.java │ ├── SchoolYear.java │ └── Semester.java └── totalresult // 总成绩聚合├── ... 这段有点长其代码结构与成绩聚合一致因此省略 ...基础设施实现层 该层主要提供领域层接口资源库、防腐层接口和应用层接口防腐层接口的实现。 代码组织基本以聚合为基本单元。对于应用层的防腐层接口则直接以 application 作为包名组织。 ├── application // 应用层相关实现 │ └── adapter // 防腐层适配器接口实现 │ ├── facade // 外观接口 │ └── translator // 转换器DO - DTO ├── result // 成绩聚合相关实现 │ ├── adapter │ │ ├── facade │ │ └── translator │ └── repository // 成绩聚合资源库接口实现 │ └── ResultRepositoryImpl.java └── totalresult // 总成绩聚合相关实现├── adapter│ ├── CourseAdapterImpl.java│ ├── facade│ └── translator└── repository└── TotalResultRepositoryImpl.javaDDD 与微服务的关系 微服务拆解指的是把一个单体服务拆分为粒度“足够小”的多个服务而这里的“足够小”是一个主观的没有任何标准的定义。 尽管如此我们对“微”这个词还是有一些基本要求的足够内聚足够独立足够完备这才使得拆分出来的微服务收益大于投入试想如果一个微服务提供的业务功能会牵扯到与其他众多微服务的协作那岂不是芭比 Q 了。 而上述我们对微服务的基本要求实际上与限界上下文的特征最小完备自我履行稳定空间独立进化不谋而合因此我们可以把限界上下文映射为微服务。 我在日常实践中都是将限界上下文和微服务的关系进行一一对应的但这不是绝对的 限界上下文是站在领域角度给出的逻辑边界而微服务的设计往往还要考虑物理边界以及实际的质量需求性能可用性安全性等比如当我们采用的是 CQRS 架构领域模型会被分为命令模型和查询模型虽然它们同属一个限界上下文但是它们往往是物理隔离的。 一个BC代表一个微服务吗 限界上下文划分规则 一般来说先考虑团队规模来决定最终需要划分到多细粒度的BC如果团队规模过小而BC过细则对后期的运维、部署、上线都会造成很大的负担 在确定好粒度后可以对语义相关性、功能相关性-业务方向、功能相关性-非业务方向进行划分 按照以上的规则划分之后就得到了多个BC啦 BC与业务的关系 通过对业务的划分比如订单系统订单是一个子域库存是一个子域 其中商品再不同的子域中所表示的意义也不同比如在订单上下文中的商品表示商品的单价、折扣等等而在库存的上下文中商品表示商品的库存量、成本、存放位置等。 BC与技术的关系 多个子域之间必须需要在应用层进行聚合而聚合的过程中就引出了技术方案 比如订单到库存到支付他们应该采用同步方式 这几个子域调用通知都应该是异步那么可能就需要消息中间件或其它技术方案 一个BC代表一个微服务吗 概念 微服务一般是指将高度相关功能的一个开发部署单元有自己的技术自治性、技术选型、弹性扩缩容、发布上下频率等说白了就是各自维护一个业务然后多个业务组成一个系统多个业务之间各自管理 关系这里的BC其实就是一个领域或一个模块或一个业务如果两个领域相关性很高一个微服务就可以包含多个BC或者如果一个领域访问量非常大则需要部署在一个微服务中以提高性能 因此限界上下文只能作为微服务拆分的指导而拆分过程中需要考虑质量需求架构设计等技术因素。 最后浓缩一下DDD的四重边界 根据下图所示我们通过四重边界来进行架构设计 分而治之DDD通过规划四重边界把领域知识做了合理的固化和分层。 业务有核心领域和支持域、业务域中又拆分成多个限界上下文BC一个BC中又根据领域知识核心与否进行分层领域层中按照多个业务子域的强相关性进行聚合成一个子域 【第一重边界】确定项目的愿景与目标确定问题空间确定核心子领域、通用子领域多个子领域可以复用、支撑子领域额外功能如数据统计、导出报表 【第二重边界】解决方案空间里的限界上下文就是一道进程隔离层面的物理边界 【第三重边界】每个限界上下文内使用分层架构划分为接口层、领域层、应用层、基础设施层之间的最小隔离 【第四重边界】领域层里为了保证各个领域的完整性和一致性引入聚合的设计作为隔离领域模型的最小单元 附录什么是POCO和DTO、值对象Value Object? DTO就是数据传输对象Data Transfer ObjectPOCO就是简单CLR对象Plain Old CLR Object这个是C#中的概念公共语言运行时 (CLR)概念来源于Java中的POJO值对象Value Object是领域驱动设计Domain-Driven DesignDDD中的概念是一个包含数据逻辑的完整的领域模型。 那么这三者是什么关系呢Vladimir Khorikov专门发表了一篇文章来解释这个问题。 首先他给它们进行了更加准确的定义 DTO是表示数据的对象但是其中不包含任何业务逻辑Value Object (值对象) 是一个完整的领域模型不仅包含数据还有逻辑 从值对象还引申出实体对象Entity实体对象和值对象的概念比较接近 不同的是实体对象一般会基于标识ID进行区分POCO概念来源于POJOPOJO概念由Martin Fowler提出使用POJO名称是为了避免和EJB混淆起来, 是和EJB这类重量级Java对象的相互对立概念。POJO简单直接有一些属性及其getter setter方法的类没有业务逻辑有时可以作为VO(value -object)或DTO(Data Transform Object)来使用。当然,如果你有一个简单的运算属性也是可以的但POJO不允许有业务方法也不能携带有connection之类的方法。POJO是可以理解为轻量级的JavaBeans轻量级到什么地步呢也即这种POJO类不继承于任何对象或者说POJO 直接继承于Object同理C#中POCO类似Java的POJO是继承自C#中 System.ComponentModel命名空间中的Component对象的独立对象。可以说理解了Java的POJO的概念也即理解了C#中POCO概念。而C#中 POCO代表了可以用于领域模型的尽可能简单的对象而POCO是可以包含逻辑的。 根据以上定义我们得到这样几个结论 POCO是DTO和值对象的超集DTO不能等同于值对象DTO和值对象都不能有标识Id而POCO可以有标识Id实体对象 是DDD里边的概念可以基于标识ID进行区分也属于 POCO 参考文献 《解耦-领域驱动设计》《领域驱动设计软件核心复杂性应对之道》《实现领域驱动设计》https://www.cnblogs.com/daxnet/archive/2010/07/07/1772584.html https://enterprisecraftsmanship.com/posts/dto-vs-value-object-vs-poco/ https://blog.csdn.net/u012605629/article/details/125246753 https://blog.csdn.net/wwd0501/article/details/95062535/ https://blog.csdn.net/u012605629/article/details/125246753 https://huaweicloud.csdn.net/633119ded3efff3090b52091.html https://zhuanlan.zhihu.com/p/489070825 https://blog.csdn.net/hbuxiaofei/article/details/119796158 推荐阅读 《响应式圣经10W字实现Spring响应式编程自由》 《全链路异步让你的 SpringCloud 性能优化10倍》 《Linux命令大全2W多字一次实现Linux自由》 《阿里一面你做过哪些代码优化来一个人人可以用的极品案例》 《网易二面CPU狂飙900%该怎么处理》 《阿里二面千万级、亿级数据如何性能优化 教科书级 答案来了》 《峰值21WQps、亿级DAU小游戏《羊了个羊》是怎么架构的》 《场景题假设10W人突访你的系统如何做到不 雪崩》 《2个大厂 100亿级 超大流量 红包 架构方案》 《Nginx面试题史上最全 持续更新》 《K8S面试题史上最全 持续更新》 《操作系统面试题史上最全、持续更新》 《Docker面试题史上最全 持续更新》 《Springcloud gateway 底层原理、核心实战 (史上最全)》 《Flux、Mono、Reactor 实战史上最全》 《sentinel 史上最全》 《Nacos (史上最全)》 《TCP协议详解 (史上最全)》 《分库分表 Sharding-JDBC 底层原理、核心实战史上最全》 《clickhouse 超底层原理 高可用实操 史上最全》 《nacos高可用图解秒懂史上最全》 《队列之王 Disruptor 原理、架构、源码 一文穿透》 《环形队列、 条带环形队列 Striped-RingBuffer 史上最全》 《一文搞定SpringBoot、SLF4j、Log4j、Logback、Netty之间混乱关系史上最全》 《单例模式史上最全》 《红黑树 图解 秒懂 史上最全》 《分布式事务 秒懂》 《缓存之王Caffeine 源码、架构、原理史上最全10W字 超级长文》 《缓存之王Caffeine 的使用史上最全》 《Java Agent 探针、字节码增强 ByteBuddy史上最全》 《Docker原理图解秒懂史上最全》 《Redis分布式锁图解 - 秒懂 - 史上最全》 《Zookeeper 分布式锁 - 图解 - 秒懂》 《Zookeeper Curator 事件监听 - 10分钟看懂》 《Netty 粘包 拆包 | 史上最全解读》 《Netty 100万级高并发服务器配置》 《Springcloud 高并发 配置 一文全懂》
http://www.w-s-a.com/news/620613/

相关文章:

  • 网站建设周期规划北京网站设计必看刻
  • 如何做自己的在线作品网站深圳网站设计公司的
  • 网站开发外包公司wordpress最简单模板
  • 湖南省建设人力资源网站wordpress主机pfthost
  • 淮安软件园哪家做网站各网站特点
  • 网站长尾关键词排名软件重庆荣昌网站建设
  • 建个商城网站多少钱茂名专业网站建设
  • 开通公司网站免费的网站app下载
  • 跨境电商网站模板wordpress壁纸
  • 国内做网站网站代理电子商务网站建设与维护概述
  • 如何做地方网站推广沈阳网势科技有限公司
  • 哈尔滨网站优化技术涵江网站建设
  • 做网站搞笑口号wordpress全屏动画
  • 怎么可以建网站小程序代理项目
  • 怎样做软件网站哪个网站用帝国cms做的
  • 网站开发编程的工作方法wordpress dux-plus
  • 廊坊电子商务网站建设公司网站进不去qq空间
  • 南宁网站推广费用创意网页设计素材模板
  • 深圳技术支持 骏域网站建设wordpress 酒主题
  • 东莞网站建设+旅游网站改版数据来源表改怎么做
  • 手机端做的优秀的网站设计企业做网站大概多少钱
  • 优化网站使用体验手机网站解析域名
  • 网站制作 商务做网站的软件名字全拼
  • 阿里巴巴网官方网站温州网站建设设计
  • 传奇购买域名做网站国外网站设计 网址
  • 西安凤城二路网站建设seo网站是什么
  • 网站后台如何更换在线qq咨询代码在线种子资源网
  • 东莞网站优化制作免费中文wordpress主题下载
  • 东莞建筑设计院排名网络优化论文
  • 做牙工作网站郑州前端开发培训机构