摘要
本文主要介紹了DDD(領域驅動設計)在系統設計中的實踐應用,包括其在編碼規范、分層架構設計等方面的具體要求和建議。重點強調了應用層的命名規范,如避免使用模糊的Handler、Processor等命名,推薦使用動詞加業務動作的清晰命名方式;區分命令和查詢服務的命名規則;以及Repository層和防腐層的設計原則。此外,還探討了DDD的價值和在實際系統中的應用思考。
1. DDD領域模型驅動Coding規范
1.1. 統一語言規范
與統一語言(英文)一致的代碼命名,保證代碼可讀性和可溝通性、降低團隊溝通成本和團的其他成員理解成本。
1.2. Domian層
1.2.1. Domain對象拒絕Getter、Setter、Constructor等注解?
在 DDD(領域驅動設計)中,Domain 對象(如 Entity、Value Object、Aggregate Root)確實應當盡量避免使用 Getter
、Setter
、Constructor
等 Lombok 或 IDE 自動生成的注解,這是出于“建模思想”與“封裝業務規則”的考慮。以下是詳細解釋與建議。
- 🧠 破壞封裝與建模思想:DDD 強調 通過代碼表達領域模型的意圖和業務規則。直接暴露 getter/setter,會讓你的領域對象退化為一個貧血模型(Anemic Domain Model),只是一個數據容器,而不是業務的承載體。
// ? 錯誤寫法:getter/setter + public 字段完全暴露
@Getter
@Setter
public class Order {private Long id;private String status;
}
?這樣寫沒有任何領域語義,Order 的狀態可以隨意被外部修改,違背領域封裝原則。
- 🛡? Setter 允許任意修改內部狀態,打破一致性:領域對象的核心職責是:保障業務數據的完整性和一致性。Setter 讓外部可以繞過業務規則,隨意設置對象屬性:
order.setStatus("已支付"); // 沒有校驗是否可以從“已取消”變成“已支付”
而正確的做法應是:
public void markAsPaid() {if (!canPay()) {throw new IllegalStateException("當前狀態不可支付");}this.status = Status.PAID;
}
- 🏗? Constructor 注解(如 @AllArgsConstructor)缺乏表達力
@AllArgsConstructor
自動生成構造函數,但它不能表達“構建一個合法對象的業務意圖”,也難以做校驗。例如,構建一個 LoanApplication
時可能需要校驗利率、貸款人信息、期限等,而這些必須在構造過程強校驗。應該這樣:
public LoanApplication(Applicant applicant, BigDecimal amount, Term term) {if (amount.compareTo(BigDecimal.ZERO) <= 0) {throw new IllegalArgumentException("貸款金額必須大于0");}this.applicant = applicant;this.amount = amount;this.term = term;
}
正確做法:用行為方法代替 Setter,用工廠方法代替構造器,示例:DDD 風格的 Order 聚合根
public class Order {private final OrderId id;private final List<OrderItem> items = new ArrayList<>();private OrderStatus status = OrderStatus.CREATED;// 構造器設為 protected 或 private,僅通過工廠創建protected Order(OrderId id) {this.id = id;}public static Order create(OrderId id) {return new Order(id);}public void addItem(Product product, int quantity) {if (status != OrderStatus.CREATED) {throw new IllegalStateException("只能在創建狀態添加商品");}this.items.add(new OrderItem(product, quantity));}public void markAsPaid() {if (status != OrderStatus.CREATED) {throw new IllegalStateException("不能重復支付");}this.status = OrderStatus.PAID;}public OrderId getId() {return id;}public List<OrderItem> getItems() {// 可返回不可變副本return Collections.unmodifiableList(items);}
}
Domian對象code實踐建議
項目 | DDD 建議 |
| ? 只在讀取聚合標識、只讀字段時局部使用 |
| ? 禁止在領域對象中使用 |
| ? 不建議使用 |
| ? 避免(除非 ORM 必須) |
| ? 應包含業務校驗邏輯 |
| ? 可用于構造復雜值對象 |
1.2.2. Domain僅包含領域模型定義的對象,且用plain object。
Domain層主要包含領域模型(Domain Model),比如:
- 實體(Entity):有唯一標識的業務對象,如“訂單”、“用戶”。
- 值對象(Value Object):無唯一標識,僅通過屬性值定義的對象,如“地址”、“金額”。
- 聚合根(Aggregate Root):實體的集合邊界,保證數據一致性。
- 領域服務(Domain Service):當業務邏輯不適合放在某個實體上時,用領域服務封裝。
- 領域事件(Domain Event):業務狀態變化的事件。
不包含技術層相關的類(比如 DAO、DTO、Controller、ServiceImpl等)。
Domain對象都用plain object
- Plain Object 指的是簡單的、純粹的業務對象,即不依賴特定框架的特殊基類、注解或技術代碼。
- 這意味著領域模型類盡量只包含業務屬性和行為,不引入持久化、網絡、序列化等技術代碼。
- 例如,領域模型不要直接繼承 JPA Entity,或帶大量數據庫注解;避免和框架耦合。
- 保持領域模型的純粹性,方便單元測試和業務復用。
// 領域實體示例(Plain Object)
public class Order {private String orderId;private List<OrderItem> items;public void addItem(OrderItem item) {// 業務規則校驗items.add(item);}// 省略Getter/Setter,聚焦行為
}
這里的 Order
是一個“純領域對象”,沒有任何持久化注解,也沒有依賴框架特性。
- 在實際項目中,領域對象和數據庫映射對象通常會做分離,通過Repository 層進行轉換。
- 這樣既保持了領域層的純粹,也滿足了持久化需求。
1.2.3. Domain層不依賴spring的AOP和lOC等三方包
Domain層應該保持“純凈”,不依賴 Spring、MyBatis、Hibernate、Lombok 等任何三方框架,尤其不能依賴 Spring 的 IoC、AOP 等容器機制。
DDD 要求領域模型:
- 表達業務含義清晰(面向業務而非技術)
- 可以脫離框架獨立測試或演算(保持“領域獨立性”)
- 保持長生命周期可演進(與基礎框架解耦)
所以:@Component
、@Autowired
、@Transactional
、@Service
等 Spring 注解 → ? 不應該出現在 Domain 層@Getter
、@Setter
、@Entity
、@Table
等 Lombok / ORM 注解 → ? 不應該污染領域模型
如果你的 Domain層用了一堆Spring注解,要小心:
- 說明你的領域模型很可能耦合了基礎設施層邏輯(違背 DDD 分層)
- 可能導致業務邏輯難以復用或測試
層 | 說明 | 是否可用框架注解 |
| 純領域邏輯模型、實體、聚合、值對象、領域服務 | ? 不依賴任何框架 |
| 數據持久化、消息中間件、緩存等實現細節 | ? 用 Spring 管理 |
| 調用編排、流程協調、事務管理等 | ? 用 Spring |
| Controller、API 接口層 | ? 用 Spring MVC 注解等 |
1.2.3.1. ? 不推薦(污染領域模型)
@Entity
@Getter
@Setter
public class LoanApplication {@Idprivate Long id;@Autowiredprivate CreditService creditService; // 依賴外部服務public boolean canApprove() {return creditService.checkCredit(id); // 不可測、強耦合}
}
1.2.3.2. ? 推薦(純凈的領域對象)
public class LoanApplication {private Long id;private int score;public LoanApplication(Long id, int score) {this.id = id;this.score = score;}public boolean canApprove() {return score >= 700;}
}
- 外部服務(如
CreditService
)由DomainService
或ApplicationService
在外部注入,傳參給領域對象,不在對象中注入依賴。
1.2.3.3. 🧪 好處:
- ? 易于單元測試:構造純對象即可測試,不依賴 Spring 環境。
- ? 解耦框架:更容易遷移、更少“技術污染”。
- ? 聚焦業務:領域對象只關心業務含義,職責清晰。
1.2.4. Domain對象行為拒絕setter、update、modify、save、delete等無明確業務含義的方法?
這是 DDD(領域驅動設計)中對 Domain 對象(即領域模型) 的一種強烈編碼規范:領域對象的方法必須具備明確的業務含義。這些方法通常只表示技術操作,而沒有任何具體的業務語義,違背了 DDD 中“領域模型體現業務行為”的基本理念。
錯誤方式(技術性方法) | 正確方式(業務性方法) |
|
|
|
|
|
|
|
|
1.2.4.1. 領域模型是業務專家的語言映射
- setter/update/save 等是 面向 ORM 和數據庫 的語言。
- 而
approve()
、reject()
、cancel()
等方法是 業務專家能聽懂的術語,符合“統一語言”的要求。
1.2.4.2. 降低貧血模型(Anemic Model)風險
- 如果實體只有 getter/setter,就淪為了數據容器(貧血模型),邏輯散落在 service 中,失去了封裝。
- 加入業務行為,才能形成真正的充血模型,邏輯內聚,模型可維護、可擴展。
1.2.4.3. 那領域模型中應該怎么定義方法?
按照“行為驅動模型”的思路:
public class LoanApplication {private LoanStatus status;public void approve() {if (this.status != LoanStatus.PENDING) {throw new IllegalStateException("Loan cannot be approved in current state");}this.status = LoanStatus.APPROVED;}public void reject(String reason) {this.status = LoanStatus.REJECTED;// 記錄拒絕原因,可能還要寫審計日志}
}
方法名 | 是否允許 | 說明 |
| ? | 除非是純值對象,如 DTO、配置類 |
| ? | 太籠統,建議改為具體業務操作 |
| ? | 有明確業務語義 |
| ? | 計算、驗證行為是業務的一部分 |
| ?(只讀轉換) | 可接受,但可以考慮放到 assembler 或 factory |
1.2.4.4. ? Domain對象行為總結
編碼項 | DDD 推薦 |
是否寫 setter | ? 盡量避免 |
方法是否用 update/save/delete 命名 | ? 避免 |
方法是否要有業務含義 | ? 強烈建議 |
領域對象職責 | 封裝業務狀態 + 表達業務行為 |
對象類型推薦 | 值對象不可變、實體對象充血 |
1.2.5. 值對象命名不用加上標識技術語言的Enum。
在很多項目中,我們習慣于寫:
public enum LoanStatusEnum {PENDING, APPROVED, REJECTED;
}
但在 DDD 中,更推薦寫為:
public enum LoanStatus {PENDING, APPROVED, REJECTED;
}
1.2.5.1. 領域語言優先:表達業務語義,而非技術語義
DDD 強調“領域語言”,即代碼的命名要貼合業務、可讀性強,而不是暴露技術細節。
LoanStatus
是一個業務概念,用戶、產品經理、風控人員都能理解。LoanStatusEnum
是面向程序員的命名方式,暴露了技術實現細節(用的是 enum)。
💡 DDD 建議隱藏實現細節、突出業務意圖。值對象本身就是“一個不可變、具備自我完整性的業務值”,不管你是用 enum
、class
、record
實現的,業務只需要知道這是 LoanStatus
,而不是“枚舉”。
1.2.5.2. 值對象不僅限于 enum
很多值對象是用 class 定義的:
public class Amount {private BigDecimal value;private String currency;
}
如果你對 enum 加上 Enum
后綴,那你是不是也要對上面的 class 叫 AmountClass
?顯然沒有這個習慣。所以統一叫“LoanStatus”這種業務術語,風格更一致、更干凈。
1.2.5.3. ? 什么情況下不推薦簡化命名?
以下場景你可能仍然需要保留Enum
后綴(但這不再是純粹 DDD 語境了):
- 與其他類型沖突,如
LoanStatus
既是實體字段又是類名時。 - 和第三方庫集成時,需要區分類型。
- 較低層的工具包(非 DDD),用于統一標識枚舉。
維度 | 建議 |
命名方式 | 推薦使用業務語言命名,不加 |
示例 |
|
原因 | 保持領域語言一致性、隱藏實現細節、業務表達自然 |
例外 | 與類型沖突、集成第三方工具時可保留 |
1.3. application層
1.3.1. application層拒絕XXXHandler、XXXProcessor、XXXContext等含義不明確的命名?
1.3.1.1. ? 為什么要避免這種模糊命名?
命名 | 問題 |
| “處理訂單”具體是創建、取消、派送、結算還是別的?看不出來 |
| “處理貸款”是審批?風控?放款?也看不出來 |
| 是用戶上下文對象?Session?請求參數?環境變量?不清晰 |
| 管理什么?責任不清 |
這些命名是技術導向的,不利于業務溝通和代碼可維護性。
1.3.1.2. ? 推薦的命名方式:動詞 + 業務動作或職責清晰的命名
命名應能直接反映業務操作的意圖,建議使用如下格式:
建議命名 | 職責 |
| 提交貸款申請 |
| 取消訂單 |
| 審批貸款申請 |
| 發起資金轉賬 |
| 生成報表 |
這些命名都表達了 清晰的業務行為,更符合 DDD 中“應用層協調領域服務”的職責。
1.3.1.3. ? 如何重構?
原名 | 重命名建議 |
|
|
|
|
|
|
1.3.1.4. 🧱 補充說明:不同于領域層、基礎設施層
層級 | 命名推薦 | 不推薦 |
Domain 層 |
|
|
Application 層 |
/ |
|
Infrastructure 層 |
| 可容忍 |
命名類別 | 推薦所在層 | 說明 |
| ? Infrastructure 層 或 Application 層中實現類 | 用于技術處理(如消息消費、HTTP 請求處理等) |
| ? Infrastructure 層 或 Application 層中實現類 | 用于組合多個行為、任務編排 |
| ? 可用于跨調用傳遞上下文對象(如流程上下文),但不作為核心業務對象 | 放在 Application 層或跨層共享模塊 |
1.3.1.5. ? 總結
命名原則 | 說明 |
? 避免 | 業務語義不明確 |
? 使用 | 符合統一語言 |
? 命名體現職責和行為 | 方便業務溝通、代碼自解釋 |
? 不建議應用層泛化職責(如一個類什么都管) | 導致職責混亂、難以維護 |
1.3.2. 區分命令和查詢,命令推薦KXXCommandService,查詢推薦XXXQueryService?
你提到的這個命名方式和區分 命令(Command) 與 查詢(Query) 的設計,是現代 DDD(領域驅動設計)中非常推薦的一種 CQRS(Command Query Responsibility Segregation,命令查詢職責分離) 實踐。
1.3.2.1. ? 命名規則推薦
類型 | 命名規范示例 | 說明 |
命令類 |
| 代表狀態變更操作(有副作用) |
查詢類 |
| 代表數據讀取操作(無副作用) |
1.3.2.2. 🧠 為什么這么命名?
CQRS 的核心思想是:將“讀操作”和“寫操作”分離成兩個服務接口,職責清晰,便于演進、擴展和性能優化。
1.3.2.3. XXXCommandService
- 只包含“寫”操作:新增、更新、刪除、觸發業務行為等
- 會調用 領域服務 / 聚合根
- 會有事務控制
- 會影響系統狀態
public interface UserCommandService {void registerUser(RegisterUserCommand command);void updateUser(UpdateUserCommand command);void disableUser(String userId);
}
1.3.2.4. XXXQueryService
- 只包含“讀”操作:查詢詳情、列表、分頁等
- 不包含任何副作用
- 可返回 DTO/VO
- 可對接讀庫(或搜索引擎緩存等)
public interface UserQueryService {UserDetailDTO getUserById(String userId);List<UserDTO> listUsers(UserQuery query);
}
1.3.2.5. ? 優點總結
優點 | 說明 |
職責清晰 | 讀寫邏輯分離,不會混淆 |
可單獨優化 | 查詢可以走緩存、ES、分庫;命令可以做冪等性、事務保障 |
更易測試 | Query 無副作用;Command 只測試狀態變更 |
支持復雜業務擴展 | 比如后續支持 Event Sourcing、審計日志、寫擴展性等 |
1.3.2.6. 🚫 反面示例(混用):
public class UserService {public void createUser(...) {...} // 寫public User getUserById(...) {...} // 讀
}
這種 Service 混合讀寫職責,后續很容易導致復雜度上升、耦合增加,不易演進。
1.3.2.7. 👇 實戰建議
- 應用服務層(Application Service)就應該按照 Command / Query 分開設計
- Controller 層調用時清晰地知道是讀請求還是寫請求
- 命名約定保持一致:
UserCommandService / UserCommandAppService
UserQueryService / UserQueryAppService
- 不需要為了“統一”而把 Command/Query 合并回一個 Service
1.4. infrastructure層
1.4.1. Repositoryl的入參和出參除了原始改據類型,只能包含領域對象?
Repository 的職責是訪問“領域模型”的持久化存儲,其輸入輸出應圍繞“領域對象”展開,而不是直接處理 DTO(數據傳輸對象)或 PO(數據庫實體對象)。
內容 | 說明 |
? 只能包含領域對象(Domain Object) | Repository 是領域層的一部分,它的作用是將領域對象保存/加載到持久化介質中,所以它操作的對象應該是領域對象(如實體、值對象) |
? 避免 PO(Persistence Object) | PO 是數據庫結構的映射,屬于基礎設施(infrastructure)層,而 Repository 是領域層的一部分,它不應直接操作數據庫結構的對象 |
? 避免 DTO(Data Transfer Object) | DTO 是服務層或接口層的數據格式,通常用于與外部系統或前端交互,不屬于領域模型,因此不能作為 Repository 的輸入輸出 |
1.4.1.1. ? 錯誤理解示例(違反規范):
// 錯誤:傳入和返回的是 DTO 或 PO,而不是領域對象
UserDTO findById(Long id);
void save(UserPO userPo);
1.4.1.2. ? 正確設計示例:
// 正確:傳入和返回的都是領域對象(Entity 或 ValueObject)
User findById(UserId id); // 返回領域實體
void save(User user); // 傳入領域實體
1.4.1.3. 🎯 為什么這樣設計?
原因 | 說明 |
分層清晰 | 明確職責邊界,Repository 專注于領域模型的持久化,DTO/PO 屬于別的層 |
降低耦合 | 避免領域模型對數據庫結構或外部接口耦合,增強模型穩定性和可演進性 |
保持統一語言 | 領域對象使用的是統一語言建模,符合業務語義,PO/DTO 通常是技術導向結構 |
1.4.2. Repository對外交互拒絕DTO、PO?
“Repository 對外交互拒絕 DTO、PO”,可以從 架構職責分層、解耦性、建模一致性 等多個角度來理解。
概念 | 說明 |
Repository | 是 DDD 中領域層的一部分,負責對領域對象(Entity、Value Object)的持久化操作,如存儲、加載 |
DTO(Data Transfer Object) | 用于服務層、應用層與外部系統(如接口調用、RPC、Web)之間的數據傳輸對象,不包含業務邏輯 |
PO(Persistence Object) | 通常是 ORM 框架(如 JPA、MyBatis)映射的數據庫實體,緊耦合于數據庫結構 |
1.4.2.1. ? 錯誤設計(違反規范)
// 錯誤:直接傳 PO、DTO
public interface UserRepository {void save(UserPO userPo); // 錯:使用 POUserDTO findById(Long id); // 錯:返回 DTO
}
1.4.2.2. ? 正確設計(遵守規范)
public interface UserRepository {void save(User user); // 入參是領域對象User findById(UserId id); // 返回領域對象
}
1.4.2.3. 📌 為什么要拒絕 DTO 和 PO?
原因 | 說明 |
? 職責單一 | Repository 是領域層的一部分,職責是“存取領域模型”,不是處理數據庫結構或 API 數據格式。 |
? 分層解耦 | DTO 是接口層/應用層對象,PO 是基礎設施層對象,而 Repository 是領域層對象 —— 應層層隔離,不應交叉 |
? 保持建模一致性 | 領域對象才具備業務語義,DTO 和 PO 都只是結構化數據,不具備行為和語義 |
? 便于演進 | 若數據庫字段或接口結構變化,只需修改 PO/DTO,不影響領域模型與 Repository 交互邏輯 |
1.4.2.4. 📌 那 Repository 和數據庫是怎么交互的?
通過“轉換器(Assembler/Converter)”在基礎設施層完成對象轉換:
+---------------------+| Domain Repository | ← 輸入輸出:User(領域對象)+---------------------+↑+-------------|------------------+| Infrastructure 層 || UserRepositoryImpl.java || UserPO ? User 轉換器 |+-------------------------------+↓+-----------------+| 數據庫(PO) |+-----------------+
示例:
@Override
public void save(User user) {
UserPO userPO = UserPOAssembler.toPO(user);
userMapper.insert(userPO);
}
1.4.3. 對外接口訪問的防腐層,統一命名為XXXAdaptor?
對外接口訪問的防腐層,統一命名為 XXXAdaptor
。
1.4.3.1. 什么是“防腐層”Anti-Corruption Layer(ACL)
在DDD中,防腐層的作用是:
- 保護領域模型不被外部系統污染或侵蝕
- 實現外部系統模型 → 自己系統領域模型的隔離和轉換
- 防止外部系統設計不佳、耦合度高、變化頻繁影響你的系統
🧱 舉個例子:你需要調用第三方風控系統,它返回的接口數據結構是ThirdPartyRiskResponse
,但你不希望這個結構在你的領域模型里出現。這時你應該:
- 定義一個
RiskEngineAdaptor
接口/實現 - 將外部數據結構
ThirdPartyRiskResponse
轉換為你自己的領域模型RiskResult
1.4.3.2. 為什么命名為 XXXAdaptor
?
統一命名為 XXXAdaptor
(或 Adapter)是為了:
- 一眼識別出它是 適配外部系統的類
- 它的作用是“適配 + 轉換 + 解耦 + 防腐”
- XXX 是被適配的系統名,如
RiskEngineAdaptor
,CreditPlatformAdaptor
,OpenApiAdaptor
1.4.3.3. Adaptor
和領域的邊界關系
+------------------------+| 你的領域模型 || (干凈、高內聚) |+------------------------+↑| ← 防腐轉換(Adaptor)↓+------------------------+| 外部系統(如三方接口) || 數據格式不一致,模型低質 |+------------------------+
1.4.3.4. 🧩 示例說明:
外部系統返回結構
@Data
public class ThirdPartyRiskResponse {private String code;private String message;private Map<String, String> data;
}
Adaptor 接口定義
public interface RiskEngineAdaptor {RiskResult query(RiskRequest request);
}
Adaptor 實現類(防腐層)
@Component
public class RiskEngineAdaptorImpl implements RiskEngineAdaptor {@Overridepublic RiskResult query(RiskRequest request) {ThirdPartyRiskResponse response = thirdPartyClient.call(request);return RiskResultAssembler.toDomain(response);}
}
轉換器(Assembler)
public class RiskResultAssembler {public static RiskResult toDomain(ThirdPartyRiskResponse response) {// 適配字段、格式、含義return new RiskResult(response.getCode(), response.getData().get("score"));}
}
1.4.3.5. 🚫 如果沒有防腐層會怎樣?
如果直接在 Service 中使用 ThirdPartyRiskResponse
:
- 你的領域模型、服務層會大量出現外部結構 → 強耦合
- 外部系統改了字段,你系統大范圍受影響
- 業務含義模糊,代碼可讀性差
- 不利于測試、演進、重構
Adaptor 就是你的系統與外部世界之間的“防護墻”,統一命名為 XXXAdaptor
是為了職責清晰、結構分明、易于管理和維護。
1.4.4. 禁止外部接口對象向上層透傳?
“禁止外部接口對象向上層透傳”的核心目的是:不讓外部結構入侵系統內部,保持業務領域的純潔性和獨立性。外部接口返回的對象(如三方 API、RPC、數據庫 PO、Web 請求參數 DTO 等)不能直接透傳到系統內部,尤其是不能傳入領域層或直接暴露給上層。
1.4.4.1. 📦 透傳的反例(錯誤示范)
假設你調用一個外部授信平臺,它返回一個 CreditResponseDTO
,你直接在服務層或控制器里透傳這個對象:
// ? 錯誤做法:把外部系統返回對象直接透傳到上層接口
public CreditResponseDTO checkCredit(String userId) {return creditPlatformClient.query(userId);
}
問題:
CreditResponseDTO
是外部定義的結構,字段命名、含義不一定穩定- 一旦外部結構發生變動,你的整個服務層、接口層都需要改
- 你的業務邏輯會被迫使用外部系統的定義,嚴重耦合
1.4.4.2. ? 正確做法:引入 轉換層(Assembler) 和 防腐層(Adaptor)
// 對外暴露領域對象或自定義 VO,而非外部結構
public CreditResult checkCredit(String userId) {CreditResponseDTO responseDTO = creditPlatformClient.query(userId);return CreditAssembler.toCreditResult(responseDTO); // 轉換為內部對象
}
CreditResponseDTO
只在Adaptor
或Assembler
層使用CreditResult
是你自己定義的領域對象或 VO,用于業務邏輯或接口輸出- 這樣無論外部系統怎么變,只需改 Adapter/Assembler,不影響核心業務
1.4.4.3. 🎯 目的總結
原則 | 說明 |
防腐 | 外部系統不穩定,不可信,要設“隔離層”防污染 |
解耦 | 內部系統演化應與外部系統解耦 |
可維護 | 變化控制在邊界,便于測試和演進 |
語義清晰 | 自定義對象語義明確,更符合業務語言 |
1.4.4.4. 🧩 實戰建議
類型 | 示例 | 是否允許透傳? | 正確做法 |
第三方接口返回對象 |
| ? 禁止透傳 | 轉換為 |
數據庫查詢的 PO |
| ? 禁止透傳 | 轉換為 |
前端提交的請求體 DTO |
| ? 禁止透傳 | 轉換為 |
領域模型 |
| ? 允許傳遞 | 按照聚合設計使用 |
1.5. 事件層
1.5.1. 事件命名為事件+Event,且事件命名為動詞過去時 ?
1.5.1.1. 為什么事件命名要加Event
后綴?
- 明確類型:
Event
后綴能清晰表示這是一個“事件對象”,區別于命令(Command)、DTO、實體(Entity)等。 - 增強可讀性:看到類名帶
Event
,一目了然該對象是用于描述某個事件發生。 - 方便維護:在代碼庫中快速定位事件相關代碼,便于事件管理和監控。
示例:
UserRegisteredEvent
OrderCancelledEvent
PaymentSucceededEvent
1.5.1.2. 為什么事件名用動詞過去式?
- 表示已發生的事實:事件描述的是“某件事已經發生了”,所以用過去時更符合語義。
- 符合事件驅動語義:事件是對“發生事實”的記錄或通知,而不是命令或請求。
- 區分命令和事件:
- 命令(Command)通常用動詞原形或祈使句(如:
CreateOrderCommand
) - 事件(Event)用動詞過去式,表明動作已完成(如:
OrderCreatedEvent
)
- 命令(Command)通常用動詞原形或祈使句(如:
1.5.1.3. 結合起來的示例
類型 | 命名示例 | 語義說明 |
命令 |
| 請求創建訂單(動作指令) |
事件 |
| 訂單已被創建(已發生的事實) |
事件 |
| 支付成功事件(動作完成的結果) |
1.5.1.4. 總結
規范點 | 理由 |
事件名后綴 | 明確事件類型,方便區分和維護 |
動詞過去式命名 | 事件是“已經發生的事實”,語義準確 |