传统分层
MVC
远古时代的 web 项⽬:
当前后端分离成为主流共识:
MVC -> DLC
业务逻辑越来越复杂,拆成单独的 logic:
业务代码越来越多了,看不懂怎么办
贫⾎模式 | 充⾎模式 |
---|---|
业务逻辑都在 logic 层内 | 要让 domain object 即 entity 有更多逻辑 |
Struct、Class 上没有任何逻辑,或只有少量逻辑 | 通过聚合来组合 entity 的逻辑 |
贫⾎模式代码示例
业务逻辑被埋没在存储业务中:
使⽤充⾎模式改造后
定义 Customer Repo:
相关的 Repo 实现都较简单:
主流程根据不同的⽤例选择执⾏不同的对象函数,⽽不是⽤数据建模,将所有处理集中在同⼀个函数⾥
SOLID 中的 DIP(Dependency Inversion Principle)
通过 DIP,我们可以做到依赖⽅向与程序控制⽅向相反
整洁架构 style
整洁架构
Uncle Bob 提出的整洁架构:
Uncle Bob 同时也是⾯向对象编程中的SOLID 原则的提出者
整洁架构的核⼼观点
- 不与框架绑定:业务不应该与某个 web 框架绑定,应该做到想换就换。
- 可测试:业务逻辑应该在没有 UI、database 环境、web server 等所有外部环境的前提下做到可测试。
- 不与 UI 绑定:不与具体的 UI 库绑定,项⽬应该做到随意切换外部 UI,⽐如可以将 web UI 替换为 console UI,同时⽆需业务逻辑做修改。
- 不与数据库绑定:可以把 Oracle 换成 SQL Server,也可以换成Mongo,换成 BigTable,换成 CouchDB。业务不依赖具体存储⽅式。
- 不依赖任何外部代理:你的业务应该对外部环境⼀⽆所知。
不与框架绑定
- 业务代码⼊⼝不应与任何协议绑定
- 框架代码(如 gin.Context)不要⼊侵到业务层
可测试
在没有 UI、database 环境、web server 等所有外部环境的前提下做到可测试。
- Sql driver mock for Golang
- Go monkeypatching
- Redis client Mock
- httptest
- gomock
- Testify
- How to get a mock Etcd for unit test?
不与 UI 绑定
我们做的基本上都是前后分离的系统,不太容易与 UI 发⽣绑定:
不与数据库绑定
不与数据库绑定:可以把 Oracle 换成 SQL Server,也可以换成Mongo,换成 BigTable,换成 CouchDB。业务不依赖具体存储⽅式。
需要借助 DDD 中的 Repo 设计⽅式:
不依赖任何外部代理
没有外部 agent 也能启动,做到⽐较难。
⽐如你是不是经常会碰到:
- 没有配置下发的 agent 的环节,本系统跑不起来。
- 没有 service mesh 模块,本系统跑不起来。
- 没有 metrics 采集模块,本系统跑不起来。
DDD style
DDD 社区就是名词多
- Entity
- Value Object
- Repository
- Aggregate
- Aggregate Root
ports && adapters
六边形架构,也被称为端⼝与适配器架构,所有外部类型都有适配器与之对应,外部通过 API 与内部交互。——《实现领域驱动设计-ch4》
Ports = interface
Adapters = instances
DDD 中的战术设计-Value Object(Value Type)
Value Type 是不可变(immutable),可⽐较(comparable)的值
例如:
数字: 1,2,3;
字符串:“⼈⺠⽇报”,“⽕星探测”
结构体:var addr = address {“100010”, “xi er qi”, “33.133”, “21.3243”}
DDD 中的战术设计-Entity
- Entity 的关键是其有 ID 作为唯⼀标识
- Value type 则没有 ID
- 有 ID 意味着 entity 是可变(mutable)的,会随着时间更新
DDD 中的战术设计-Aggregate
• Aggregate 和 entity 设计上类似,也有 ID,也是可变(mutable)的
• 聚合可以⽤ entity + value object 构成,如右图
• 每⼀个聚合对应⼀个 Repo interfac
• 聚合需要对聚合内的数据⼀致性负责,可以认为聚合是数据⼀致性的边界
• 聚合之外的⼀致性采⽤最终⼀致性保证
Aggregate:
|
Repo:
- ⼀个聚合可以只有⼀个 entity
- 也可以有多个 entity 和 value object
DDD 中的战术设计-Aggregate Root
• 聚合根也是聚合
• 与普通聚合唯⼀差别:聚合根对外暴露,要关联某个领域内的对象,⼀定是通过聚合根的 id 来进⾏关联的。
• 在聚合内的⼀些 entity ⼀般不对外暴露,但随着时间的推移也可能变成聚合根。
DDD 中的战术设计-Repo Pattern
DDD 中的战术设计
插件化架构
其它知识
依赖注⼊⼯具
main 模块是上帝模块,需要负责初始化所有内部类
⼀些可以参考的 API 设计指南
Paypal 的 API 设计指南
Paypal 的 API 设计指南(中⽂)
⽹友的 API 设计指南
微软的 API 设计指南
微软的 API 设计指南(中⽂)
Google 的 API 设计指南
References
The Clean Architecture
使⽤ Go 实践 clean architecture
整洁架构相关的代码
Design a DDD-oriented microservice
wire简介
整洁架构重新思考
Bounded-context 和 microservice 怎么对应
贫⾎模式还是充⾎模式
Go 与 DDD 中的战术设计
DDD 代码模板
DDD 代码模板
⼦领域和 bounded context 的关系
设计微服务的 domain mode
Netflix 的六边形架构实践
将贫⾎项⽬重构为 DDD 模式
将贫⾎项⽬重构为 DDD 模式