一文读懂领域驱动设计DDD -- 知识铺
领域驱动设计(DDD)概述与实践
前言领域驱动设计(DDD)
是一种软件设计方法论,它通过将复杂业务领域分解为更小的子域,实现业务规则和知识的高效管理。本文将通过一个虚拟公司’RabbitTech’的业务场景,介绍DDD的基本概念及其落地实践。
领域与子域领域是业务规则和知识的集合
如财务、人力等。面对复杂的领域,我们可以采用分治法,将其拆分为更易管理的子域。
领域驱动设计的重要性DDD提供了一种系统化的方法论
帮助我们在战略和战术层面上规划和设计软件系统。它不仅关注业务需求,也涉及技术实现。
领域模型的构建领域模型是业务系统的核心
具有以下特点:
- 抽象性:反映领域内用户需求的本质。
- 业务导向:与技术实现无关。
- 集中性:确保业务逻辑的集中管理。
- 转化性:帮助开发人员将领域知识转化为软件构造。
- 交流性:促进领域专家、设计人员和开发人员之间的知识共享。
- 复杂性:建立正确的领域模型需要跨团队的沟通和努力。
- 可视化:通过图、代码或文字描述等方式表达。
- 核心性:是软件中最有价值和竞争力的部分。
领域通用语言(UBIQUITOUS LANGUAGE)
领域通用语言是团队成员在所有交流形式中使用的一致语言,基于领域模型构建,促进了软件专家与领域专家之间的有效沟通。
虚拟业务场景以’RabbitTech’公司为例
该公司主营知识付费产品’RabbitAdvisors’。本文将基于此场景,介绍DDD的实践应用。
结语DDD作为一种设计方法论
虽然不是万能的,但它提供了一套有效的框架和工具,帮助我们更好地理解和应对复杂的业务领域。
RabbitAdvisors 架构设计与团队分工方案
一、架构设计
1. 技术选型
- 微服务架构:采用微服务架构,确保系统的高可用性和可扩展性。
- 注册中心:使用 Nacos 作为服务注册与发现。
- 消息队列:使用 RocketMQ 处理异步消息传递和解耦。
- 数据库:使用关系型数据库存储结构化数据,如 MySQL。
2. 微服务划分
- 用户服务:处理用户注册、登录、订阅管理等。
- 内容服务:管理专栏内容的创建、编辑、发布等。-
- 支付服务:处理支付事务,与第三方支付平台对接。-
- 佣金服务:计算和处理作者的佣金分成。
3. 服务间通信-
RESTful API:服务间通过 RESTful API 进行同步通信。- 事件驱动:使用消息队列实现服务间的异步通信。
4. 安全性-
认证授权:采用 OAuth2.0 和 JWT 进行用户认证和授权。- 数据加密:敏感数据进行加密存储和传输。
5. 监控与日志-
监控系统:集成 Prometheus 和 Grafana 进行系统监控。- 日志管理:使用 ELK Stack 收集和分析日志。
二、团队分工
1. 后端开发团队-
服务开发:负责微服务的设计与开发。- API 设计:设计 RESTful API 和事件模型。
2. 前端开发团队-
用户界面:开发用户交互界面。- 前端逻辑:实现前端业务逻辑。
3. 测试团队-
单元测试:编写和执行单元测试。- 集成测试:确保服务间正确集成。
4. 运维团队-
部署:负责应用的部署和持续集成。- 监控:监控应用性能和健康状态。
5. 数据库团队-
数据库设计:设计数据库模型和索引优化。- 数据迁移:处理数据迁移和备份。
三、开发流程
- 需求分析:与业务团队合作,明确需求。2.
- 设计阶段:进行系统设计和 API 设计。3.
- 开发阶段:按照分工进行编码实现。4.
- 测试阶段:进行单元测试和集成测试。5.
- 部署上线:部署到生产环境并监控。
四、参考代码与资源- 后端微服务1:smart-classroom-misc- 后端微服务2:smart-classroom-subscription- 前端项目:smart-classroom-front- 在线演示:RabbitAdvisors 在线演示
请根据本文的架构设计和团队分工方案,与实际项目进行对比,评估和优化你的设计思路。 在软件开发领域,MVC模式是一种常见的设计模式,它将应用程序分为三个主要部分:Model、View和Controller。下面,我们将详细探讨MVC模式的各个组成部分及其功能。
Model(模型)Model层负责数据和业务逻辑的管理。
它与数据库紧密相连,通常包含了数据访问对象(DAO)和数据传输对象(DTO)。Model层的主要职责是维护数据的一致性和完整性。
View(视图)View层主要负责用户界面的展示。
它将Model层的数据以用户可理解的形式呈现出来。View层不包含业务逻辑,而是通过与Controller层的交互来获取数据。
Controller(控制器)Controller层是Model和View之间的桥梁。
它接收用户的输入,调用Model层的业务逻辑处理请求,并最终将结果传递给View层进行展示。Controller层通常由Service组件辅助实现,以实现更复杂的业务逻辑。
总结MVC模式通过分离关注点
提高了应用程序的可维护性和可扩展性。在没有领域驱动设计(DDD)的情况下,MVC模式为软件开发提供了一种清晰的结构化方法。
MVC与DDD的对比虽然MVC模式在软件开发中非常流行
但领域驱动设计(DDD)提供了一种更专注于业务领域的设计方法。DDD强调业务逻辑的建模和领域专家的参与,而MVC模式则更侧重于技术层面的分离。
结论理解MVC模式的工作原理对于软件开发者来说至关重要。
它不仅有助于构建结构清晰的应用程序,而且也是学习更高级设计模式,如DDD的基础。
软件开发方法论的演变
随着业务逻辑的日益复杂化,传统的软件开发模式,如MVC,开始显现出其局限性。以下是MVC模式面临的主要问题:
- 业务语言缺失:MVC模式主要关注软件架构,而忽略了业务语言的整合,导致难以直接与业务对话。
- 数据与行为分离:MVC模式将数据与行为分开处理,通过数据库和业务服务实现,这可能导致需求实现的不连贯。3.
- 边界划分不明确:缺乏顶层设计中明确的边界划分规范,依赖技术负责人的经验进行划分,易造成团队协作中的职责不清。 面对这些挑战,领域驱动设计(DDD)应运而生,提供了一种新的解决方案。
DDD概述
定义领域驱动设计(Domain-Driven Design,简称DDD)
是一种模型驱动的设计方法。它通过构建领域模型来捕捉和表达领域知识,从而构建出易于维护的软件系统。
模型的三个关键作用
- 反映软件结构:模型直接映射到软件实现的结构。
- 形成统一语言:基于模型形成团队的共同语言。
- 传递知识:模型作为精炼的知识,用于传递和共享。
DDD的价值
DDD带来以下几方面的价值:
统一语言- 形成团队内对事物的统一描述,提升沟通效率。
面向业务建模- 领域模型与数据模型分离,降低业务与技术的耦合度。
边界清晰的设计方法- 通过需求识别和分类,指导团队成员分工协作。
业务领域的知识沉淀- 模型与业务世界保持一致,促进业务知识的传递和沉淀。
DDD的基本概念
DDD涉及众多概念,对于初学者来说可能感到困惑。以下是一些基本概念的简要说明:
统一语言- 团队成员在特定上下文中形成对事物的统一描述,形成统一的概念模型。
实例说明以“RabbitAdvisors”商业模式中的“
用户可以选择自己感兴趣的专栏进行付费订阅”为例,进行建模。
结语DDD作为一种应对复杂业务逻辑的软件开发方法
通过统一语言、面向业务建模、清晰的边界划分和业务知识的沉淀,为软件团队提供了一种更加高效和灵活的工作方式。 在领域驱动设计(Domain-Driven Design, DDD)中,统一语言是沟通业务和技术团队的关键。以下是对统一语言形成过程的梳理和应用场景的描述:
统一语言的形成
- 定义领域模型概念:
- 用户(User):指所有在“RabbitAdvisors”注册过的人。
- 订阅的专栏(Subscription):指用户付费过的专栏。
- 阐述领域模型逻辑: - 用户可以订阅多个专栏。
- 明确限界上下文: - 订阅行为发生在特定的业务上下文中。 通过这些基础词汇,我们可以构建一个没有歧义的业务和技术交流平台。统一语言的确立是一个逐步使用和接受的过程。
统一语言的应用
- 描述需求: - 用户可以查阅自己订阅过的专栏,以及其中的教学内容。
- 编写测试用例: - 当用户已购买某个专栏,访问时无需再次付费。 统一语言不仅简化了沟通,还有助于确保需求的准确理解和实施。
战略设计与战术设计
DDD进一步细分为战略设计和战术设计,它们各自包含不同的关注点和实施策略:
- 战略设计:关注整体业务目标和长期规划。- 战术设计:关注具体的实现细节和短期目标。 通过战略和战术设计的结合,可以确保业务和技术的协同发展,同时保持设计的灵活性和可扩展性。
结语
统一语言是DDD实践中不可或缺的一部分,它进了团队成员之间的有效沟通,为构建高质量的软件系统奠定了基础。
战略设计概述
战略设计是对一个组织所涉及的整个业务领域的全面分析和规划。它包括对领域概念、业务规则和边界的确定,并考虑未来发展趋势和潜在变化。
领域定义领域指的是组织业务的总体范围,包括人员、规则和流程。它面向业务而非技术或数据库。
示例以“RabbitTech”的“RabbitAdvisors”为例,其领域是知识付费。
子域细分子域是领域内的独立业务领域,具有自己的概念、规则和流程。
核心域核心域是公司和产品核心竞争力的体现,直接产生业务价值。
示例“RabbitAdvisors”中的订阅域。
通用域通用域提供多个子域使用的通用功能,间接产生业务价值。
示例“RabbitAdvisors”中的权限域、登录域。
支撑域支撑域支撑其他领域业务,具有企业特性但不通用。
示例“RabbitAdvisors”中的专栏域、评论域。
限界上下文限界上下文是业务边界的划分,支持完整业务流程,是微服务拆分的依据。
示例“专栏订阅上下文”包含订单域和订阅域。
战术设计概述
战术设计基于战略设计,针对具体问题提供解决方案。它关注具体情境和场景,满足业务需求。
实体定义实体是具有唯一标识和生命周期的业务对象,代表现实世界概念。
代码形态- 失血模型:仅含数据定义。- 贫血模型:含部分业务逻辑。- 充血模型:含全部业务逻辑。- 胀血模型:含非业务逻辑。
建议建议采用贫血模型,实体与领域服务共同构成领域模型。
值对象定义值对象通过属性值识别,无标识符,不可修改,描述领域特定方面。
业务形态值对象用于描述实体特征,简化沟通。
代码形态- 单一属性值对象:如字符串、整型。- 多属性值对象:设计为class,无ID。
聚合和聚合根聚合是内聚性的表现,包含有相同生命周期的实体和值对象。聚合根是对外暴露的根实体。
领域、子域、限界上下文与聚合的关系- 领域、子域、限界上下文属于战略设计。- 聚合属于战术设计,范围小于前三者。
结构图领域、子域、限界上下文、聚合的结构关系图应展示其层级和范围大小。
领域驱动设计(DDD)核心概念与实践
领域驱动设计(Domain-Driven Design,简称DDD)是一种软件开发方法论,旨在通过领域建模来创建软件。以下是DDD中的一些核心概念和实践方法。
工厂模式(Factory Pattern)
工厂模式是一种设计模式,它将对象的创建过程封装起来,以便于管理和使用。在DDD中,工厂模式的使用动机如下:
- 将创建复杂对象和聚合的职责分配给一个单独的对象。- 工厂本身不承担领域模型中的职责,但属于领域设计的一部分。- 提供一个创建对象的接口,封装创建对象的复杂操作过程。
资源库模式(Repository Pattern)
资源库模式用于封装数据访问逻辑,提供数据的持久化和查询功能。它将数据访问细节与领域模型分离,使得领域模型更加独立和可测试。资源库模式的特点包括:
- 提供统一的接口,便于领域模型与不同数据存储方式交互。- 支持查询操作,便于在领域层中进行数据查询。
领域服务(Domain Service)
领域服务用于处理那些不属于任何对象的领域行为。当识别出这样的行为时,推荐将其声明为服务。例如,在“RabbitAdvisors”中,订阅行为涉及到多个实体的操作,因此适合作为领域服务。
领域事件(Domain Event)
领域事件是领域中值得注意的事件,通常意味着领域对象状态的改变。它们在系统中起到传递消息、触发其他动作的作用,是解耦领域模型的重要手段。例如,在“RabbitAdvisors”中,用户订阅专栏后,会产生“专栏订阅成功”的事件。
领域建模(Domain Modeling)
领域建模是DDD的核心,目的是捕捉业务知识,形成统一语言,沉淀领域模型。领域建模的产出物包括:
- 领域模型- 用例图- 数据模型- 状态图- 活动图- 序列图- 架构模型
事件风暴建模(Event Storming)
事件风暴是一种互动式建模工作坊,通过捕捉事件和领域概念来完成建模。它由Alberto Brandolini在2012年创造。
事件风暴语法
事件风暴通过事件、命令与策略之间的响应关系来组织逻辑,定义了一套彩色贴纸的语法:
- 浅黄色:角色(Actor)- 蓝色:命令(Command)- 粉色:业务规则(Policy)- 紫色:系统(System)- 橙色:事件(Event)- 绿色:阅读模型(Read Model)- 红色:热点问题(HotSpot) 以上是DDD中的核心概念和实践方法的简要介绍,希望对您的理解和应用有所帮助。
事件风暴概述
事件风暴是一种业务建模技术,旨在通过一系列特定的语法,清晰地描述业务逻辑和关键流转过程,从而方便不同角色间传递领域知识。
事件风暴的语法元素
- 行动者(Actors):系统的使用者,可以是人或系统。- 命令(Command):行动者发起的行为,通常作为事件的起因。- 事件(Event):已发生且重要的事情,业务关注点。- 系统(System):不需要了解细节的三方系统,视为一个整体。- 阅读模型(Read Model):支撑决策的信息,与界面布局相关。- 策略(Policy):对事件的响应,可触发新的命令。- 热点问题(HotSpot):业务中的痛点、瓶颈或模糊点。
事件风暴操作流程
准备工作
- 准备物料:彩色贴纸、笔纸,确保有足够的空间。2. 邀请人员:包括程序员、设计师、测试人员等,以及能提供业务背景的用户或业务代表。
开场介绍
- 由主持人介绍事件风暴的目的、好处及彩色贴纸的使用方法。- 明确事件风暴的范围和目标,例如讨论“RabbitAdvisors”用户订阅专栏的业务流程。
事件风暴的业务沟通方式
第一步:梳理事件(使用橙色贴纸)
- 事件是业务关注的事实,需要保持时间顺序。- 主持人引导参与者通过提问找到事件的前因后果,包括正常流程(Happy path)和异常流程(Unhappy path)。
举例
- 假设第一个事件是“专栏已订阅”。- 通过提问,引导参与者发现并补充相关事件,如“订单已支付”和“订单创建失败”。
注意事项
- 确保事件的完整性,包括边界条件和异常情况。- 当新事件发现速度减慢时,进入梳理业务规则阶段。
事件风暴的实施要点
- 保持参与者的专注和交流。- 通过提问和讨论,提炼和总结事件风暴的成果。 通以上步骤,事件风暴能够帮助团队深入理解业务流程,发现并解决问题,促进知识的共享和传递。 在进行事件风暴时,我们遵循以下步骤来确保业务逻辑的清晰和准确:
第二步:业务规则(粉色贴纸)业务规则是业务流程中的核心。主持人需要提出以下问题来明确规则:
- 事件是否一定成功?如果不一定,成功的条件是什么?- 事件是否会引发其他事件? 例如,针对“订单已创建”的事件,我们可以这样分析:- 订单创建的前提条件是专栏可订阅且用户未订阅过。- 订单创建后,会触发支付流程。
第三步:行动者、命令、阅读模型和系统(浅黄色、蓝色、绿色、紫色贴纸)主持人通过提问来引导参与者识别以下要素:
- 事件是由命令还是规则触发的?- 执行动作的是人还是系统?- 在执行动作前,用户需要了解哪些信息? 通过这些问题,我们可以逐步确定Actor, Command, Read Model和System。
第四步:热点问题(红色贴纸)记录业务中的痛点、瓶颈和不明确的地方。这些问题需要在事件风暴结束后进一步讨论,而不是在风暴过程中解决。
第五步:故事串讲选择一名成员按照事件的时间顺序讲述业务流程。听众在听到不一致的地方时可以提出问题,然后大家共同讨论,调整事件和逻辑,以达成共识。
注意:在进行事件风暴时,要确保内容的条理性与结构性,以便于理解和沟通。
产出架构在完成事件风暴后,架构师应根据业务流程和逻辑,制定相应的架构设计。这一步骤涉及创建领域模型、用例图、状态图、活动图和序列图等关键架构文档。
事件风暴概述事件风暴是一种高效的事件建模技术,它通过集体头脑风暴识别领域事件,并探索事件之间的响应关系。此方法具有以下特点:
- 快速识别:迅速发现领域中的事件。- 集体参与:需要团队成员的共同参与。- 事件关联:以事件的响应为核心,探索事件间的联系。
事件风暴的优势- 直观性:通过可视化的方式呈现事件和流程。- 参与性:鼓励团队成员的积极参与和讨论。
事件风暴的局限性- 资源密集:需要较多人员参与,可能涉及较长的流程。- 逻辑收敛:成功的关键在于能否有效收敛逻辑,避免发散。
结构化内容1. 领域模型:定义系统中的实体及其关系。2. 用例图:展示系统功能和用户交互。3. 状态图:描述对象状态的变迁。4. 活动图:展示业务流程的步骤。5. 序列图:展示对象间的交互顺序。
通过上述步骤,可以确保架构设计既全面又具有针对性。
事件风暴法与四色建模法
事件风暴法
概念与问题事件风暴法是一种发散思维方法,鼓励参与者自由表达想法,但这也带来了信息的噪声。在收敛阶段,通过逻辑主线合并概念,过滤无效信息。然而,由于依赖主持人的经验和判断,这种方法存在一定的随意性,导致结果可能因人而异。
改进思考鉴于事件流的质量取决于收敛逻辑,提出直接从收敛逻辑出发,通过引导和分析来获取事件流。
四色建模法
起源与发展四色建模法起源于Peter Coad的四原型法,后结合徐昊的事件建模,形成了一种强分析法。它通过结合业务逻辑,以业务的内在逻辑发现事件,从而获得具有业务含义的模型。
核心逻辑四色建模法的三个关键逻辑:1. 现金收入:表示承担义务,需要履约证据。2. 现金支出:表明拥有权利,需要检查履约。3. 目标与实际对比:设立目标,追踪实际执行结果。
业务视角四色建模法以业务模式为核心,通过收入流和成本结构识别事件,强调业务的稳定性和内在逻辑。
四色建模法语法
对象原型与颜色四色建模法中的四种对象原型,每种对应一种颜色,具体如下:- 收入流:与现金收入相关,识别义务履约事件。- 成本结构:与现金支出相关,识别权利检查事件。- 目标设定:设立业务目标,用于对比实际执行。- 实际执行:追踪业务实际执行情况,发现与目标的偏差。
应用与优势四色建模法的应用使得模型更贴近业务实际,便于统一语言和控制变化传播。它的优势在于直接从业务视角出发,以业务逻辑为主导,从而更有效地进行领域建模。
结论四色建模法通过结合业务逻辑,提供了一种更为系统和结构化的建模方法,有助于提高模型的质量和实用性。
四色建模法操作流程
四色建模法概述
四色建模法是一种结构化分析方法,用于构建财务和业务流程的模型。它通过识别和关联关键凭证,明确权利与责任,从而提高模型的准确性和逻辑性。以下是使用四色建模法建模的步骤:
步骤1:确定关键现金往来和凭证
首先,识别与现金直接相关的交易,如购买和分成。例如,在’RabbitAdvisors’专栏中,关键现金往来包括读者购买专栏和作者获得分成。
步骤2:追踪关键数据项的来源
对于每个关键数据项,如时间点和金额,需要确定其来源。来源可能包括用户输入、前序凭证提供或算法计算。
步骤3:分析权利与责任
考虑现金凭证所代表的权利与责任,并确定需要哪些凭证来证明这些权利与责任。
步骤4:确保数据项的顺畅获取
无论凭证类型如何,必须清晰列出关键数据项,并确保数据项的获取流程顺畅。
步骤5:关联现金往来不大的KPI指标
如果某些凭证与现金往来关联不大,应寻找关键KPI指标,并构造验收凭证来表示它。
步骤6:构建相互关联的凭证流
通过获取相互关联的凭证,形成事件流,为模型细化打下基础。
步骤7:确定参与角色
围绕每个凭证,识别参与其中的角色,并思考可能扮演这些角色的参与方。
步骤8:为模型添加补充说明
通过描述对象,为模型提供更详细的补充说明。
‘RabbitAdvisors’专栏建模示例
以’RabbitAdvisors’专栏为例,我们首先识别了两种现金往来:读者购买专栏和作者分成。以下是构建模型的示例:
- 读者购买专栏:记录购买时间和金额,追踪资金流向。2. 作者分成:根据分成规则,记录分成时间和金额。 在构建模型时,我们确保每个步骤都严格遵循四色建模法的要求,以提高模型的逻辑性和实用性。 在处理现金往来的凭证时,我们通常需要通过一系列步骤来追溯其来源。以下是对上述情况的重新整理和分析:
- 凭证构建:首先,我们构造了两个凭证来表示现金往来。每个凭证都包含关键数据项,如时间点和金额。
- 关键数据项分析:以读者购买专栏的凭证为例,其关键数据项包括: - 时间点:交易发生的具体时间。 - 金额:交易的货币价值。
- 追溯前序凭证:为了确保金额的准确性,我们需要追溯到前序凭证: - 支付凭证:读者支付的金额来源于订单,因此支付凭证的金额应与订单金额一致。 - 订单凭证:订单金额则来源于专栏报价,所以订单金额应与专栏报价相等。
- 报价来源:专栏报价是由编辑输入的,这构成了订单金额的前序凭证。
- 追溯总结:通过上述步骤,我们完成了从支付凭证到专栏报价的完整追溯链,确保了每一笔交易的金额都有明确的来源和依据。 以上步骤构成了一个清晰的追溯流程,有助于我们理解和管理现金往来的凭证。 在处理支付流程之后,我们需要确保Payment系统具备相应的履约凭证。履约凭证是证明用户已经支付并有权享受服务的关键。对于我们的业务场景,用户购买的是阅读专栏的权利,因此我们需要一个凭证来证明用户可以从特定时间开始阅读专栏内容。
履约凭证:订阅凭证(Subscription)订阅凭证是用户支付后获得的凭证,它包含以下关键数据项:
- 开始时间(start_time):用户可以开始阅读专栏内容的时间点。这个时间必须在支付完成后,确保用户在完成支付后才能访问专栏,即使只提前一毫秒也是不允许的。
业务逻辑1. 用户完成支付后,系统生成订阅凭证。2. 订阅凭证记录用户的开始时间,确保该时间晚于支付完成时间。3. 用户凭借订阅凭证,在开始时间之后,可以阅读专栏内容。
重要性- 订阅凭证确保了用户在支付后能够获得相应的服务。- 它防止了用户在未支付的情况下提前访问专栏内容。- 保证了业务流程的公平性和合理性。
通过引入订阅凭证,我们不仅提高了用户体验,也加强了业务流程的安全性和合理性。 专栏作者分成的计算流程可以概括为以下几个步骤:
- 确定账期:首先,需要确定分成的账期,这通常是基于合同约定的时间段,如每月或每季度。
- 收集销售凭证:在确定的账期内,收集所有读者购买专栏的凭证,这些凭证是计算分成的基础数据。
- 计算总销售金额:将账期内所有销售凭证的金额汇总,得到总销售金额。
- 确定分成比例:分成比例通常在专栏撰写合同中规定,这个比例决定了作者能够获得的分成金额。
- 计算分成金额:根据总销售金额和分成比例,计算出作者应得的分成金额。
- 确定上次分成付款日:如果存在之前的分成支付凭证,最晚的支付日期即为上次分成付款日;如果没有,则默认为合同签约日。
- 生成佣金支付单:将上述信息汇总,生成佣金支付单,作为前序凭证,为作者分成的支付提供依据。 在处理过程中,需要注意的难点是确保数据项的准确性和来源的明确性,特别是分成比例、账期日和上次分成付款日这几个关键数据项。
业务流程概述
在RabbitAdvisors专栏业务中,我们构建了一条清晰的凭证链,它贯穿了整个业务流程,从作者签约到读者付费,再到作者分成,这一系列动作构成了我们业务的核心——业务的脊梁。
1. 签约作者首先,我们与作者签约,确立合作关系,这是业务的起点。
2. 专栏定价随后,对专栏进行定价,这是吸引读者付费的关键步骤。
3. 读者付费读者对感兴趣的专栏进行付费,这是业务收入的直接来源。
4. 作者分成最后,根据约定的比例,对作者进行分成,确保作者的利益。
角色与参与者的识别
在这一过程中,我们需要识别与凭证相关的角色,并寻找合适的参与者来扮演这些角色,以及确定与凭证相关的标的物。
- 角色识别:识别业务流程中的关键角色,例如作者、读者、平台等。- 参与者寻找:基于角色的需求,寻找合适的参与者,例如签约知名作者,吸引忠实读者等。- 标的物关联:明确每个角色与业务流程中的标的物之间的关系,如作者的作品、读者的付费等。 通过上述步骤,我们可以确保业务流程的顺畅进行,并有效地捕捉和管理业务的收入流。
获得领域模型
按照关键数据项间的关联,将模型连在一起,稍加润色,就能得到领域模型了:
验证业务脊梁与领域模型的有效性
在业务脊梁和领域模型初步建立后,下一步是验证它们的有效性,确保业务流程的顺畅。首先,将所有凭证转化为实际单据;其次,通过角色扮演的方式,让业务团队模拟业务运作。 验证过程中,除了常规流程外,重点模拟业务异常情况,检验业务脊梁能否准确捕捉数据,以支持权责证明。例如,专栏定价的修改、读者不满等问题。还包括作者对分成结果的质疑等。 四色建模法的独特之处在于,它认为软件系统应加速和优化业务流程,模型应支撑业务运营,而非仅服务于软件构建,因此更容易被业务方接受。 如果在验证中发现问题,应返回前一步骤进行调整。
四色建模法概述
四色建模法基于业务的基本逻辑,即收入流和成本结构,来构建业务模型,并从中提炼领域模型。它能够从业务角度出发,更容易说服业务方接受模型作为统一语言。
落地实践
接下来,我们将介绍“RabbitAdvisors”的落地实践。
方案设计
领域模型
根据四色建模法,我们已获得领域模型,并对其进行了整理,如下所示: 注:以上图示应根据实际模型进行替换。
读者、编辑以及作者都可以看做是用户,但是三者在系统中所从事的动作,拥有的能力差异巨大,用户的共性仅体现在登录等极少数场景,因此本文建模更倾向于将“读者”、“编辑”以及“作者”视为三个独立的模型。下文中将不再出现“用户”。
子域划分
针对上面的领域模型进行子域划分,子域划分时尽量要体现出聚合根,因此子域往往只包含一到两个领域模型,子域中的实体会以值对象的方式聚合其他子域的实体。子域划分如下图:
根据子域对于业务重要性的不同,可以分为核心域、支撑域和通用域:
核心域包括:订单域、订阅域
支撑域:专栏域、专栏报价域、金融域、签约域、佣金域
通用域:读者域、小二域、作者域
为了行文简洁,还有一些其他必须的支撑域和通用域没有画出来,比如消息通知域、公告域、评论域等。
限界上下文划分
限界上下文中划分的一个技巧就是考虑一个完整的业务流程,保证这个业务流程所涉及的领域都在一个限界上下文中,例如“专栏订阅上下文”中包含了用户订阅这个业务流程的关键领域对象。
限界上下文是指导微服务系统拆分的依据,上图中根据限界上下文的划分,需要拆分成5个微服务系统,分别是 专栏订阅系统、专栏信息系统、签约分佣系统、金融系统以及用户信息系统。
到这里,顶层的架构设计已经完成了,也回答了文章开头的提问:
假如你是“RabbitTech”的CTO,你将会怎样来对“RabbitAdvisors”进行架构设计,又如何给团队分工呢?
关于架构设计,领域建模部分前面已经给出,应用架构将会在“代码实施”一节介绍。 给团队分工可以根据限界上下文,比如这里由五个小组分别负责每个限界上下文的内容。
本节下文就是系分中的内容啦,这也是软件工程师们日常最熟悉的内容了。下面我们重点围绕“专栏订阅系统”来做具体实施。
用例图
用例图可以清晰的表示出系统的功能,例如针对专栏订阅系统,用例图如下:
状态机
逻辑清晰的状态机是刻画实体生命周期的关键,例如针对专栏订阅系统,状态机如下:
活动图
活动图也叫流程图,是用来展示具体的业务流程,可以描述清楚业务具体的处理逻辑,一般流程图采用泳道图的形式表达。例如订阅和取消订阅的流程图如下:
时序图
时序图描述了完成某个业务流程,系统中各个对象之间的交互过程和消息传递序列,时序图可以帮助厘清系统间的依赖和调用。例如订阅的时序图如下:
ER图
ER图是描述的数据库建模,可用于直接指导数据库建表。对于习惯面向数据库模型编程的工程师来说,可能画ER图是需求研发的第一步,但是在领域驱动设计中,ER图只是仓储实施细节,只会在领域建模完成后才进行ER设计。 例如专栏订阅域中ER图如下:
代码实施
终于到了编码的环节了。
应用架构
上文在“限界上下文划分”一节中建议分成5个微服务。为了尽可能演示出分布式业务系统,但同时又不至于让示例工程太复杂,在演示工程中就只分为两个应用,本文重点演示“专栏订阅”中的实践,将其他限界上下文统一放到了“smart-classroom-misc”系统中。
应用模块分层
在每个微服务应用分层有多种方法,一个核心原则就是“高内聚,低耦合”,本文参考COLA架构并结合实际工作经验,总结了如下的应用分层:
基本思想
-
为了保证核心业务逻辑的稳定,领域层应作为最纯粹、最少对外依赖的层次,只包含业务知识和业务规则,不该过多关心技术细节的实现(如存储、消息等)。
-
分层以领域层为核心来分层建设。
模块职责
测试套件(testsuite):
测试模块,所有单测和集成测试写在该模块。它通过直接和间接依赖,可以访问到每个模块的代码,也即所有模块对测试层都是可见的。如果项目中有类似AliGenerator的代码自动生成工具,也应该放在该模块中。主程序(main):
应用的启动入口,包含启动相关的配置。接口门面层(facade):
该模块是对外发布的API包,对外暴露的SOA/RPC接口放在这里,外部应用使用我们的服务会依赖该包。一个应用可能会按照用途不同,设计多个facade模块。接口门面实现层(facade-impl):
实现了用户界面层(facade)中的接口,主要负责向用户显示信息和解释用户指令。处理输入的解析、验证、转换,还有输出的序列化。如果应用有多个facade模块,可以使用一个facade-impl来实现,也可以是多个。控制器层(controller):
可选。如果应用直接对外提供http服务,那么http的接口入口就在该层中,例如spring-boot中的Controller都放在这一层。
控制器层虽然依赖于facade-impl层,但是只能调用facade中声明的方法,这样可以防止业务逻辑从facade-impl层泄露到controller层中。
一般说来,http服务由专门的网关应用对外提供,微服务系统中这一层不需要。应用层(application):
定义要完成的业务功能,由该层负责协调和编排领域层的原子方法。因此应用层主要负责: -
事务控制
-
查询仓储(注意:只能查询仓储,不能写仓储,写仓储是领域层的能力)
-
领域事件(domain event)的触发和监听
-
操作日志
-
安全认证
注意一点,应用层虚线依赖仓储层和基础设施层,应用层要调用仓储查询或者使用中间件都应该使用领域层声明的接口,不能够直接使用仓储层和基础设施层的实现,例如应用层中不能直接使用Mapper,而应该使用Repository接口。虚线依赖是因为仓储层和基础设施层必须挂到主程序这个根上,整个项目模块才能被组织起来。
领域层(domain):
表达业务概念、业务状态、及业务规则。一个聚合(aggregate)一个package。领域层负责以下内容:
- 实体(entity)
- 值对象(value object)
- 领域服务(domain service)
- 领域事件(domain event)
- 仓储(repository)接口定义,读写仓储
- 依赖的外部服务(anti-corruption layer)的接口定义
- 工厂(factory)
结合团队以及兄弟团队的实践,建议实体采用贫血模式,实体和领域服务共同构成领域模型。
仓储层(repository):
仓储层负责数据查询及持久化,仓储层本质上也属于一种基础设施,但是仓储层作为系统中重要的一环,因此从基础设施层中独立开来。DO对象只存在于仓储层,通过内部定义的Converter转为领域对象后供上层使用。
基础设施层(infrastructure):
倒置依赖领域层,负责RPC服务以及中间件服务的调用和具体实现。经典DDD分层中依赖的外部服务的防腐层(anti-corruption layer)就在这里。
工具层(utility):
和业务无关的工具方法都放在这里,比如Utils类,全局通用Exception定义等。utility会被整个项目其他模块直接或间接依赖,必须保证其无业务语义。
编码规范
这里给出的编码规范是我基于团队实践总结出来的,不同团队可以有自己的规范,这里供参考:
POJO规范【强制】
POJO(Plain Ordinary Java Object)简单的Java对象,区别于Spring Bean。 POJO的对象后缀做如下约定。
DO 数据库模型
XXDO代表的是数据库模型,其中的字段就是数据库表字段的平铺格式。
Model 领域模型
XXModel代表某个领域模型,这是我们的逻辑核心。
DTO 外部传输对象
XXDTO 是对外的数据传输对象,一般DTO是在facade层定义的,在facade-impl层完成转换。
Info 内部传输对象
XXInfo是在application层和domain层之间的数据传输对象,和DTO的功能类似,区别在于DTO对外,Info对内。
VO 值对象
XXVO 代表了值对象,VO是Value Object简写,通过值对象可以接收其他系统facade传递过来的DTO。注意VO不是View Object(视图对象),在项目中没有视图对象,因为是前后端分离,没有类似于jsp这种视图,另外DTO已经完全承载了和前端或者其他服务交互的职能,不再需要视图对象。
Query 查询对象
XXQuery 是仓储接口接收的查询参数封装的对象,一般仓储查询包含3个及以上的参数就应该封装。 XXQuery和仓储接口都是定义在领域层,在仓储层进行实现的。
Request 请求对象
XXRequest是facade层中定义的查询对象,一般一个facade接口包含3个或者以上的参数就应该使用Request进行封装。
Converter 转换器
各种类型的对象涉及到大量的转换,Converter结尾的类就是转换器,XXAA2BBConverter就代表了XX这个实体的AA对象类型转成BB对象类型。转换器放在对应的分层中,例如 Model2DOConverter和DO2ModelConverter就放在仓储层,Model2DTOConverter就放在 facade-impl层中。
注意:Converter建议逐个字段手写转换,不建议使用BeanUtils.copyProperties 或者 MapStruct对象转换工具,理由是使用了这类工具后,字段发生变化没法在编译阶段感知到,容易导致生产事故,所以推荐转换就使用笨方法,逐个字段转换。
Bean规范【强制】
应用采用了多层的结构,如果没有统一的命名规约,势必容易造成名字冲突的情况,这里对于Spring Bean做如下命名约定:
Controller 控制器
对外提供http服务的控制器,只存在于控制器层。
Facade 门面服务
通过门面的方式对外提供的服务接口一般直接以Facade结尾,可以细化为 XXWriteFacade: 操作服务 和 XXReadFacade: 查询服务。Facade对应的实现命名为 XXFacadeImpl。
FacadeImpl 门面服务实现
Facade门面服务对应的实现,XXFacadeImpl位于接口门面实现层(facade-impl)。 一般会将 FacadeImpl 发布成RPC服务。
AppService 应用层服务
应用层的服务以AppService结尾,可继续细化为 XXWriteAppService: 操作服务 和 XXReadAppService: 查询服务。
DomainService 领域服务
领域服务均以DomainService结尾。
Repository 仓储接口
仓储接口定义在领域层,其实现在仓储层,实现文件命名为 XXRepositoryImpl。
RepositoryImpl 仓储接口实现
仓储接口实现在仓储层,这是倒置依赖的体现。
Mapper 数据库查询接口
这是MyBatis的查询接口,是应用最底层的数据库操作文件,只允许在仓储层调用。每个XXMapper都会继承其基类XXBaseMapper,在基类中定义了基本的增删改查方法。
Client 中间件服务依赖接口
通过定义Client接口来消费中间件服务,Client定义在领域层,其对应的实现为 ClientImpl。这是防腐的设计思想。
ClientImpl 中间件服务依赖实现
这是对领域层中声明的依赖的实现,ClientImpl都是在基础设施层。
FacadeClient 外部RPC服务依赖接口
通过定义FacadeClient接口来消费RPC服务,FacadeClient定义在领域层,其对应的实现为 FacadeClientImpl。这是防腐层的设计思想。
FacadeClientImpl 外部RPC服务依赖实现
这是对领域层中声明的依赖的实现,FacadeClientImpl都是在基础设施层。
Configuration 配置
XXConfiguration是应用中的配置类,各层负责自己层的配置,例如DataSourceConfiguration关于数据源的配置,放到仓储层;WebConfiguration负责http的json配置,就放到控制器层。
各种类型的对象及Bean所处位置如下图所示:
方法命名【建议】
-
Service/Repository/Client/DAO方法命名规约
-
获取单个对象的方法用query作前缀。
-
获取多个对象列表的方法用list作前缀,复数结尾,如:listObjects。
-
获取多个对象分页的方法用page作前缀,复数结尾,如:pageObjects。
-
获取统计值的方法用count作前缀。
-
插入的方法用insert作前缀。
-
删除的方法用delete作前缀。
-
修改的方法用update作前缀。
更多编码规范
项目中更多的编码规范请参考《阿里巴巴开发规约》。
编码规范多说两句
上面约定的模块分层和编码规范可能会给你一种比较冗长的感觉,光数据从数据库传输到http层都需要经过多个Bean以及多次POJO的转换,是不是没有必要这么麻烦呢? 其实不然,这一套应用模块分层是面向团队协作设计的,如果仅仅是做个人小项目完全没有必要使用这一套分层框架,MVC模式就够了。这一套设计尽量贴合了领域驱动设计,每一层都有自己特定的职责,这样可以应对复杂的项目,用规范约束了编码风格,在一定程度上统一了团队的研发姿势,可以做到在团队中各司其职,严格的规范保证了团队代码质量的下限。
Saber工具
Saber工具位于testsuits中,它的作用是根据数据库表的元信息,自动生成Mapper和DO文件,省去逐字段手写的麻烦。如果增减字段,也可以重跑Saber工具就实现了字段和数据库一致。 Saber工具类似于Aligenerator。
操作方法 -
在SaberConfig文件中修改一些配置,主要包括表前缀,包名,jdbc信息等。
-
在x文件夹下,写一个XXCommand的类,该类包含main函数,可以执行。在该类中指定表名,分页查询字段,枚举映射关系,忽略字段等配置。
-
运行上面写的XXCommand类,这时候就能在项目下看到自动生成的XXBaseMapper.xml, XXMapper.xml, XXBaseMapper.java, XXMapper.java, XXDO.java 五个文件。
进阶用法
如果你希望根据数据库表字段信息生成前端文件或者数仓SQL文件,你可以自定义一个BlaBlaArtwork,写好对应的模板文件 TemplateBlaBla.vm,将BlaBlaArtwork注册到SaberHelper中即可。 这个时候再运行上面的XXCommand就能够得到BlaBlaArtwork中定义的产物了。
项目演示
后端微服务1:https://github.com/eyebluecn/smart-classroom-misc
后端微服务2:https://github.com/eyebluecn/smart-classroom-subscription
前端项目:https://github.com/eyebluecn/smart-classroom-front
在线演示地址:https://classroom.eyeblue.cn/
演示项目中可以用读者和小编两种身份登录,小编可以创建专栏,读者可以订阅专栏和查看已订阅的专栏。
演示项目分为两个后端应用,smart-class-subscription是“专栏订阅系统”,完全按照微服务应用建设。smart-class-subscription就是所有其他微服务的合集,同时还包含一个控制层,用于提供http服务。后端应用技术栈为:SpringBoot, MyBatis, MySQL, Dubbo, Nacos, RocketMQ, Maven。
前端项目技术栈为:React, TypeScript, ReactRouter, Antd, Less, Vite。
后记
我认为领域驱动设计是一种软件工程的思想,它不是一套模板,它的思想精髓值得软件工程师以及架构师们领会,即:
- 直接面向业务进行领域建模,将业务知识沉淀到领域模型中。
- 业务知识的沉淀不是一蹴而就,应该反复提炼,持续演进;为了让演进提炼的过程高效顺畅,团队使用统一语言来沟通、描述需求和设计方案。
- 高内聚、低耦合是应对软件复杂度的不二法则。领域、子域、限界上下文、聚合都是为这条宗旨服务的工具。
领域驱动设计也不是银弹,在软件开发过程中没有必要完全DDD一把梭,对于一些不复杂的项目,使用MVC模式开发反而更简单高效。同时业务本身的复杂度不是依靠某种软件设计思想或者设计范式就能规避的,DDD只是架构师们在架构设计过程中的一种指导思想,它本质上是一种工具。深刻理解业务,洞察问题本质才是一个架构师最核心的能力体现。当然,如果本文能够带你了解甚至是在工作中实践领域驱动设计,那本文的目的也就达到了。
推荐阅读:
https://zhuanlan.zhihu.com/p/352756483
参考引用
- Eric Evans《领域驱动设计》
- Vaughn Vernon《实现领域驱动设计》
- 徐昊《如何落地业务建模》
- Erich Gamma《设计模式:可复用面向对象软件的基础》
- EventStorming
- 迄今为止最完整的DDD实践
- 聊一聊,我对DDD的关键理解
- 事件风暴与领域建模在阿里的实践
- 阿里技术专家详解 DDD 系列 第一讲- Domain Primitive
- 限界上下文:冲破微服务设计困局的利器-阿里云开发者社区
- 复杂度应对之道 - COLA应用架构cola 扩展点张建飞(Frank)的博客-CSDN博客
- 从壹开始微服务 [ DDD ] 之三 ║ 简单说说:领域、子域、限界上下文 - 老张的哲学 - 博客园
- 领域驱动设计详解:是什么、为什么、怎么做?
- 领域驱动设计(DDD)-基础思想
- 阿里巴巴开发规约
- 原文作者:知识铺
- 原文链接:https://index.zshipu.com/geek001/post/20240730/%E4%B8%80%E6%96%87%E8%AF%BB%E6%87%82%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E8%AE%BE%E8%AE%A1DDD--%E7%9F%A5%E8%AF%86%E9%93%BA/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 免责声明:本页面内容均来源于站内编辑发布,部分信息来源互联网,并不意味着本站赞同其观点或者证实其内容的真实性,如涉及版权等问题,请立即联系客服进行更改或删除,保证您的合法权益。转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。也可以邮件至 sblig@126.com