领域驱动设计知识分享
·2347 words·5 mins
Table of Contents
领域驱动设计是什么
wiki释义: 领域驱动设计(英语:Domain-driven design,缩写 DDD,后续简称DDD)是一种通过将实现连接到持续进化的模型[1]来满足复杂需求的软件开发方法。领域驱动设计的前提是:
- 把项目的主要重点放在核心领域(core domain)和域逻辑
- 把复杂的设计放在有界域(bounded context)的模型上
- 发起一个创造性的合作之间的技术和域界专家以迭代地完善的概念模式,解决特定领域的问题
领域驱动设计是一种由域模型来驱动着系统设计的思想,不是通过存储数据词典(DB表字段、ES Mapper字段等等)来驱动系统设计。领域模型是对业务模型的抽象,DDD是把业务模型翻译成系统架构设计的一种方式。
领域模型驱动设计开发
- 模型和设计的核心相互影响
- 模型是团队所有成员所使用的交流语言的中枢
- 模型是浓缩的知识
为什么使用DDD
领域驱动设计提到一些比较好的柔性设计。我觉得是十分合理且值得尝试的。如:
- 接口需要意图明确。
- 函数应该无边际效应等等。
DDD会从整体到局部去指定一些原则去分析业务,让开发同学对业务会有更深刻的认识。 同时,随着业务的迭代,DDD的一些理论知识也能起到很好的指导作用。比如,通过context map可能从宏观角度更好的看到系统的各个领域,从不同子域的划分,可能让我们更清晰的清楚不同领域的重要程度。各个部分显得有条不紊。
DDD核心概念
- Entity 模型【关心id】
- Value Object 值对象 【不关心id,只关心值】
- Service 服务 【不属于entity的一些行为,比如转账】
- Aggregate 聚合 【汽车包括发动机和轮子,那么汽车,发动机,轮子就组成了一个聚合】
- Aggregate Root 聚合根 【在一个聚合中,有一个对象是入口,比如上面的汽车。如果离开汽车,轮子的存在将毫无意义。没人会关心一个废弃的轮子】
柔性设计
- intention-revealing interface 意图明确的领域模型 【类或者函数的职责应该清晰,功能明确,使用方不用去了解过图的内部实现细节】
- side-effect-free function 边际效应无关的函数。 函数应该避免更改对象的状态,减少副作用。【函数应该是只返回结果而不产生明显副作用的操作,所以应该避免在多个函数中更改入参状态的操作】
- assertion 断言 【通过assertion去验证你的代码逻辑,同时也可以帮助其他人更好的学习使用你的函数】
- conceptual contour 概念轮廓 【把设计元素(操作,接口,类和聚合)分解成为内聚的单元】【单一职责】
- standalone class 独立的类 【尽可能把复杂逻辑提炼在一个独立的类中】【类与类之间往往存在很多依赖, module和aggregate的目的都是为了限制相互依赖的关系网】
- closure of operation 闭合操作 【在定义操作时, 让它的返回类型与其参数类型相同,eg: 任意两个实数相加还是实数】
战略设计
- Bounded Context 限界上下文
- Continuous Integration 持续集成。 指把一个上下文的所有工作足够频繁的合并到一起,并使他们经常保持一致,以便当模型发生分裂时, 可以迅速发现并纠正问题。
- Context Map 上下文思维导图。宏观的去看到所有上下文。
- 保持模型的完整性【不同上下文之间的关系】
- Shared Kernel 共享内核【一般是共享核心域,或者一些通用子域】
- Customer/Supplier Development Team 客户/供应商 【同一管理者管理】
- Conformist 跟随者【非同一管理者,维护比较麻烦的上下游关系】
- Anticorruption layer 防腐层 【设计一些通用的新模型去适配不同的旧系统的接口】
- Separate Way 分离【有些时候,不一定要把所有东西集成在一个系统中,分离出去可能收益更高,更利于维护】
- Open Host Service 开放主机【通过制定一些协议,开放一些公共的接口供其他系统访问】
- Published Language 【基于一种公共语言实现的模型。如jdbc。可以连接不同的数据库,不同的数据库连接都需要对jdbc这种公共语言做适配】
- 精炼【子领域】
- Core Domain 核心域
- Generic Subdomain 通用域
- Domain Vision Statement 领域总览说明
- highlight core 强调核心
- Cohesive Mechanism 内聚机制 【把概念上的复杂机制分离倒一个单独的轻量级框架中,可以理解为粒度更高的封装】
- Segregated Core 隔离核心
- Abstract Core 抽象核心【从宏观上分析核心域】
- 大比例结构【重构旧系统原则】【有点抽象】
- Evolving order 定义优先级
- System Metaphor 系统隐喻【通过metaphor分析系统的各个部分,使对系统各部分有个更清晰的认识】
- Responsibility Layer 职责层
- Knowledge level 知识级别 【定义一些规则】
- pluggable component framework 可插拔式的组件框架【优点不用说了。缺点:1.难以使用,需要高精度的接口和模型设计;2.选择有限。如果core domain有所变化,可插拔组件可能就不好工作了】
- 保持模型的完整性【不同上下文之间的关系】
落地实践
我们可以看到上面三种不同的不同的理解。好像都有道理。但是
- 从领域驱动设计这本书作者的行文逻辑来看,我觉得第一种和第二种可以算是合适的。而第三种实际上是不太合适的。
- 论证2。落地领域驱动的书中,第四章开头也有提及:核心域位于限界上下文中(原文)
错误版本
合理版本
领域驱动设计结构
工作规范
宏观
基本规范
- DDD的重要概念必须写在README上,确保所有开发人员理解。
- README必须包含项目的Context Map。且需要定期更新。【第一版由我来提供】
- 不同的限界上下文需要在项目层级上有所体现。
- 项目中的文档放入到项目中,或者一个统一的地方维护。
业务规范
- 非核心域的逻辑尽可能在非核心域中,避免核心域中的非核心逻辑过重。符合聚焦核心原则。
微观
- 类&函数规范
- 类应该职责分明,避免不属于该类的行为出现在该类中。
- 函数应该是只返回结果而不产生明显副作用的操作,所以应该避免在多个函数中更改入参状态的操作
- dto到po的转换逻辑最好写在dto上, po到vo的转换逻辑最好写在vo上【dto数据传输对象,一般作为项目的内部的数据输入对象。 vo数据显示对象,一般作为项目的数据输出对象, po数据持久化对象】
- 涉及到客户端联调的枚举尽量定义成含义明确的字符串
- 接口规范,尽可能的遵循restful原则。
- 查询接口用get请求,参数暂时不使用路径传参。
- 修改接口用post请求
- 增加接口用put请求
- 删除接口用delete请求
- Mybatis-plus规范
- 使用lambdaQuery而不是普通query。利于后续的可维护性。
参考文档
https://juejin.cn/post/7238845423379906621 (理解比较合适的博客)