DDD解決什麼問題/為什麼要用DDD
你可以把答案寫到評論區
DDD整體流程(來源ThoughtWorks)
v1
v2
最大的區別在於第一步的事件風暴與換成了戰略設計的識別核心域。個人覺得對於新業務不夠熟悉的情況下不適合提前討論核心域,因為沒有掌握足夠資訊全靠猜測,前期過多討論浪費時間。
個人理解的DDD流程
- 前期
- 價值、痛點、需求
- 事件風暴
- 識別事件
- 命令
- 領域名詞
- 業務建模/戰略設計(開發與業務的建模)
- 劃分子域/核心域
- 限界上下文
- 建立業務模型
- 模型設計/戰術設計(開發之間的建模)
- 模型設計
- api設計
- 分層架構
- 資料庫設計
- 程式碼編寫
- 釋出
上一層的輸出是下一層的輸入。
DDD戰術設計相關概念
概念 | 說明 |
---|---|
限界上下文 | 一個微服務存在多個限界上下文,簡單可以理解成模組、包、名稱空間。比如上下文A存在category聚合,上下文B也可能存在category聚合。都叫category但含義不同 |
聚合 | 也是一種邊界,將實體和值物件劃分為聚合並圍繞著聚合定義邊界 |
聚合根 | 聚合裡面的主實體,可由多個實體和值物件構成,一個聚合只有一個聚合根 |
實體 | 聚合根是實體,實體不一定是聚合根,實體是多張表模型的抽象(可等價po也可由多個po組成) |
值物件 | 沒有ID(唯一標誌)的物件 |
領域物件 | 包括:聚合、聚合根、實體、值物件、領域事件。在領域層domain裡的物件都叫領域物件 |
DO | Domain Object,同上,領域物件。所處位置:領域層 |
DTO | Data Transfer Object,資料傳輸物件。存在意義:上下游服務的流通物件。所處位置:介面層 |
PB | Proto Buffer,存在意義:GRPC上下游服務的流通物件,在我們這裡等價DTO。所處位置:介面層 |
PO | Persistence Object,持久化物件。存在意義:等價資料表模型。所處位置:基礎設施層 |
VO | View Object,檢視物件。存在意義:領域物件的一種,主要用於查詢。所處位置:領域層 |
Value Object | 值物件,所處位置:領域層 |
BO | Business Object,業務物件。存在意義:給兩個及兩個以上的領域物件進行組裝的容器。所處位置:應用層 |
DDD四層結構
層次 | 職責 |
---|---|
介面層/展示層 | 負責向使用者展現資訊以及解釋使用者命令 |
介面層/展示層 | 負責向使用者展現資訊以及解釋使用者命令 |
應用層 | 很薄的一層,用來編排一或多個領域服務,返回領域物件或業務物件給介面層 |
領域層 | 最核心的一層,負責業務邏輯編寫,對於資料庫持久化之類的操作委託給基礎設施層實現 |
基礎設施層 | 業務物件持久化實現;呼叫下游服務 |
DDD 各層流通物件(訊息中心現狀)
DDD程式碼目錄
- server 介面層
- application 應用層
- domain 領域層
- infra 基礎設施層
一個微服務存在多個限界上下文,domain層的下層目錄是指限界上下文的劃分。其他infra/persistent、server、application層的下層目錄是按模組分包。按模組+限界上下的設計是為了解決每個上下文四層目錄設計的落地成本的折中方案。
按模組分包的好處是解決了領域名詞衝突。比如上下文A存在category聚合和上下文B可能存在category聚合的同名衝突。
粒度劃分:
領域>子域>限界上下文>聚合>聚合根/實體/值物件
Go GRPC DDD框架
Go GRPC DDD框架詳細目錄說明
|-- application //應用層
| |-- directories //模組(技術視角),這裡指開發商名錄模組
| | |-- assembler //應用層物件轉換器
| | | |-- category.go
| | | `-- developer_dir.go
| | `-- developer_dir_app.go //開發商名錄app
| |-- example //模組(技術視角),example模組
| | `-- example_app.go
| |-- module3 //模組(技術視角),示例模組3
| | `-- xxxx_app.go
| |-- module4 //模組(技術視角),示例模組4
| | `-- xxxx_app.go
| `-- module5 //模組(技術視角),示例模組5
| `-- xxxx_app.go
|-- build.yaml
|-- domain //領域層
| |-- directories //限界上下文(領域視角),這裡指開發商名錄限界上下文
| | |-- aggregate3 //聚合,示例aggregate3
| | |-- category //聚合,分類
| | | |-- category_repo.go //分類聚合倉儲介面
| | | |-- category_service.go //分類聚合服務
| | | |-- entity //實體
| | | |-- event //事件
| | | `-- vo //表單物件
| | `-- developer_dir //聚合,開發商名錄聚合
| | |-- developer_dir_repo.go
| | |-- developer_dir_service.go
| | |-- entity
| | |-- event
| | `-- vo
| |-- example //限界上下文(領域視角),示例上下文
| | |-- aggregate1
| | |-- aggregate2
| | `-- example
| | |-- entity
| | |-- event
| | |-- example_repo.go
| | |-- example_service.go
| | `-- vo
| |-- ctx1 //限界上下文(領域視角),示例上下文1
| | `-- aggregate1
| | |-- aggregate1_repo.go
| | |-- aggregate1_service.go
| | |-- entity
| | |-- event
| | `-- vo
| |-- ctx2
| | |-- aggregate1
| | `-- aggregate2
|-- go.mod
|-- go.sum
|-- golangci.yml
|-- infra //基礎設施層
| |-- common
| | |-- authorize.go
| | |-- jwt.go
| | |-- page_info.go
| | |-- queue
| | | |-- consumer.go
| | | |-- etcd.go
| | | |-- message.go
| | | |-- mns_client.go
| | | |-- queue_producer.go
| | | |-- server.go
| | | |-- setup.go
| | | `-- topic_producer.go
| | |-- rpc_client
| | | `-- rpc_client.go
| | `-- tree.go
| |-- di
| | |-- developer_dir.go
| | `-- example.go
| |-- persistent //持久化層
| | |-- directories //模組
| | | |-- assembler //物件轉換器
| | | `-- repsitory //倉儲實現
| | |-- example
| | | `-- repsitory
| | |-- module3
| | | `-- repsitory
| | `-- module4
| | `-- repsitory
| |-- pkg
| | |-- businesscode
| | | `-- businesscode.go
| | |-- constant
| | | `-- common.go
| | |-- errcode
| | | |-- common_errcode.go
| | | `-- custom_errcode.go
| | `-- utils
| | |-- common.go
| | `-- db_insert_build.go
| |-- po //持久化物件,資料表模型
| | |-- oa_developer_dir.go
| | |-- oa_developer_dir_category.go
| | |-- oa_developer_dir_suppliers.go
| | `-- oa_developer_dir_user_down.go
| |-- remote //遠端呼叫
| | `-- micro_basic_service
| | `-- id_generation_service.go
| `-- startup
| |-- config.go
| |-- mq_register.go
| |-- register.go
| `-- vars.go
|-- main.go
|-- micro-msgcenter-mng-service
|-- proto //pb協議
| |-- micro_msgcenter_mng_service_proto
| | `-- micro-msgcenter-mng-service
| | |-- directories //模組
| | |-- example //模組
| | `-- module3
| |-- swagger_json.go
|
|-- server //介面層
| |-- directories //模組
| | `-- developer_dir.go
| |-- example
| | `-- example.go
| |-- module3
| `-- module4
|-- xxx.yaml
`-- vendor
PHP BFF目錄結構
proto目錄
BFF按限界上下文劃分模組、proto也按限界上下文分包
案例講解
根據戰略設計劃分出來的訊息中心業務模型
下文對產品上下文的實現做分析。
戰術設計-產品上下文的模型設計
api設計
BFF
介面名稱 | 模組(限界上下文) | 控制器(聚合) | 行為(事件) | url |
---|---|---|---|---|
獲取產品應用 | product | product | get-applications | micro-msgcenter-mng-api.cc/product/... |
獲取應用場景 | product | app | get-scenes | micro-msgcenter-mng-api.cc/product/... |
新增&修改應用場景 | product | app | set-scenes | micro-msgcenter-mng-api.cc/product/... |
GRPC
grpc介面名稱 | 包名(微服務名下劃線.限界上下文) | rpc服務名(聚合Service) | rpc方法(事件/行為) | 服務路徑 |
---|---|---|---|---|
獲取產品應用 | micro_msgcenter_mng_service.product | ProductService | GetApplications | micro_msgcenter_mng_service.product.ProductService/GetApplications |
獲取應用場景 | micro_msgcenter_mng_service.app | AppService | GetScenes | micro_msgcenter_mng_service.app.AppService/GetScenes |
新增&修改應用場景 | micro_msgcenter_mng_service.app | AppService | SetScenes | micro_msgcenter_mng_service.app.AppService/SetScenes |
GRPC-Gateway
介面名稱 | 模組(限界上下文) | 聚合 | 行為(事件) | url |
---|---|---|---|---|
獲取產品應用 | product | product | get-applications | micro-msgcenter-mng-service/product/product/get-applications |
獲取應用場景 | product | app | get-scenes | micro-msgcenter-mng-service/product/app/get-scenes |
新增&修改應用場景 | product | app | set-scenes | micro-msgcenter-mng-service/product/app/set-scenes |
設計之初不太建議限界上下文名稱和聚合名稱同名;
介面路徑按照:“限界上下文/聚合/行為”定義。
BFF 獲取產品應用 APIDOC定義
/**
* @api {get} /product/product/get-applications 獲取產品應用
* @apiDescription 獲取產品應用
* @apiSampleRequest http://micro-msgcenter-mng-api.cc/product/product/get-applications
* @apiVersion 2.0.0
* @apiName get-applications
* @apiGroup /product/product
* @apiSuccess {Boolean} success 返回狀態
* @apiSuccess {String} message 返回訊息內容
* @apiSuccess {Object} data 結果集
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
"success": true,
"message": "",
"error_code": "",
"data": [
{
"product_code": "product1",
"product_name": "產品1",
"app": [
{
"app_id": "1",
"app_name": "app1",
},
{
"app_id": "2",
"app_name": "app2",
}
]
}
]
}
*/
可以看到,這個介面需要產品聚合和應用聚合組合出來的資料。
由於組合出來的物件無法歸屬到產品實體和應用實體的任何一個,是兩個領域物件的組合體,需要有一個物件來裝載,這個物件就是BO(業務物件)。
類比微服務,微服務只負責本微服務內的領域,聚合只負責本聚合的實體。
DDD的核心就是邊界劃分清晰。
下圖是應用層的服務編排
應用和場景的聚合設計
使用了golang繼承的方式實現實體定義,減少結構體繁瑣的定義。
與傳統表模型不同的是,DDD實體是表模型的抽象,一個實體可能由多個表模型組合而成,當然這多個表模型要歸屬一個聚合下的。本例中,應用和場景組成了一個實體,應用是聚合根(主實體),場景是實體,聚合根是與外部物件溝通的代表,非主實體的業務操作需要通過聚合根來完成,如想要修改或檢視應用場景,要經過應用聚合來操作:
獲取場景:AppService.GetScenes(ctx, appId)
// app = *entity.App
設定場景:AppService.SetScenes(ctx, app)
而不是弄個場景服務來操作
獲取場景:ScenesService.GetScenes(ctx, sceneId)
// scene= *entity.Scene
設定場景:ScenesService.SetScenes(ctx, scene)
聚合理念參考文章:深入理解DDD的聚合模式
DDD 各層流通物件(推薦版)
問題
事務
其他
從DDD可以學習那些思維模型
分層思維
“計算機領域的任何問題都可以通過增加一個間接的中間層來解決”
![在這裡插入圖片描述]
歸類思維
對於眾多問題,如何減少解決問題數量的數量,可以歸類處理。
如:
- 如何賺一個億
- 如何保持身體健康
- 如何提高表達能力
- 如何實現財富自由
- 如何找到女朋友
- 如何擁有幸福的家庭
- 如何高效工作
- 如何高效溝通
- 如何帶小孩
- 怎麼提高自己的寫作技巧
- 什麼樣的運動最健康
- 跑步怎麼跑
問題域歸類
也可以更加抽象,抽象層次越高,視野越寬廣,需要考慮的問題越多。
邊界思維
舉例:公司組織架構分部門/小組
本作品採用《CC 協議》,轉載必須註明作者和本文連結