接續上一章節的成果。
我們現在要來設計 Aggregates 的細節了。
在實作之前,我們先來介紹 DDD 三個重要的物件規則:Entity、Aggregate 和 Value Object:
接著,我們要來看看這些 Aggregates 內部要如何設計,先把我們前一章的所有 Aggregates 列出來,寫下他們的 Aggregate root 作為 Entity,然後把 Aggregate 內所有 Entity 的關係寫下來。
接著按照:
不同 Aggregate 之間不應該直接引用對方的實體,而是應該通過引用對方的聚合根 ID 來進行交互,這樣可以避免強耦合和循環依賴。
我們把 Entity 的關係改為依賴 ID (Value Object)。
我想要在 Todo Item Aggregate 裡面多加一個 Value Object: Status
。這個 Todo Item 在不同狀態下會有不同的顏色,範例如下:
{
"todoItemStatus": {
"status": "todo",
"color": "#FFFFFF"
}
}
結果會變成這樣。
User
{
"id": { "value": "00000000-0000-0000-0000-000000000000" },
"firstName": "Tiffany",
"lastName": "Doe",
"email": "user@gmail.com",
"password": "p@ssw0rd!", // Need hash
"createdDateTime": "2024-01-01T00:00:00.0000000Z",
"updatedDateTime": "2024-01-01T00:00:00.0000000Z"
}
Todo List
{
"id": { "value": "00000000-0000-0000-0000-000000000000" },
"name": "Default",
"description": "Default",
"status": "active", // active, removed
"userId": { "value": "00000000-0000-0000-0000-000000000000" },
"itemIds": [
{ "value": "00000000-0000-0000-0000-000000000000" }
],
"createdDateTime": "2024-01-01T00:00:00.0000000Z",
"updatedDateTime": "2024-01-01T00:00:00.0000000Z"
}
Todo Item
{
"id": { "value": "00000000-0000-0000-0000-000000000000" },
"content": "Do homework!",
"todoItemStatus": {
"status": "todo", // todo, finished, removed
"color": "#FFFFFF"
},
"listId": { "value": "00000000-0000-0000-0000-000000000000" },
"createdDateTime": "2024-01-01T00:00:00.0000000Z",
"updatedDateTime": "2024-01-01T00:00:00.0000000Z"
}
到目前為止,所有的設計都圍繞著開發需求 (Domain)。在這之前的所有設計都不應該被任何 Database 相關的議題所干涉,這才是 Domain-Driven 的正確方式。
現在,所有的邏輯與需求都被設計好了,我們可以來討論 Database 該如何配合這個結果。
我們先把資料攤平到 Table 上,加上一些 Constraints,就會發現這個模型相當直觀。
唯一要注意的是 TodoItemStatus
這個 Value Object,因為它沒有自己的生命週期,也就是說它的生命週期跟著 Entity/Aggregate,所以我們選擇將它攤平到 Table 內。接著是 SQL 語法:
CREATE TABLE [dbo].[tb_user] (
[id] UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
[first_name] NVARCHAR(100) NOT NULL,
[last_name] NVARCHAR(100) NOT NULL,
[email] NVARCHAR(255) NOT NULL UNIQUE,
[password_hash] NVARCHAR(255) NOT NULL,
[created_date_time] DATETIME2 NOT NULL,
[updated_date_time] DATETIME2 NOT NULL,
CONSTRAINT UQ_User_Email UNIQUE (email)
);
CREATE TABLE [dbo].[tb_todo_list] (
[id] UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
[name] NVARCHAR(100) NOT NULL,
[description] NVARCHAR(255) NOT NULL,
[status] NVARCHAR(50) NOT NULL CHECK (status IN ('active', 'removed')),
[user_id] UNIQUEIDENTIFIER NOT NULL,
[created_date_time] DATETIME2 NOT NULL,
[updated_date_time] DATETIME2 NOT NULL,
FOREIGN KEY (user_id) REFERENCES [dbo].[tb_user](id)
);
CREATE TABLE [dbo].[tb_todo_item] (
[id] UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
[content] NVARCHAR(255) NOT NULL,
[status] NVARCHAR(50) NOT NULL CHECK (status IN ('todo', 'finished', 'removed')),
[color] NVARCHAR(7) NOT NULL, -- 例如 HEX 顏色代碼
[list_id] UNIQUEIDENTIFIER NOT NULL,
[created_date_time] DATETIME2 NOT NULL,
[updated_date_time] DATETIME2 NOT NULL,
FOREIGN KEY (list_id) REFERENCES [dbo].[tb_todo_list](id)
);
到這裡,資料結構的設計大致上完成了,剩下一些資料流的設計我們等實戰部分再來解釋。
另外,這個 Database 的設計只是初步的概念,等到實作會有些微的不同。
想拋出一個問題給大家思考:在做 Aggregates 設計時,Aggregate 是允許被別的 Aggregates 聚合的,那麼我們是否可以將 Todo Item 變成 Todo List Aggregate 中的一個 Entity?如果可以,最終結果會是怎樣?這樣設計又有什麼好處與壞處?