系統設計——DDD領域模型驅動實踐

摘要

本文主要介紹了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)確實應當盡量避免使用 GetterSetterConstructor 等 Lombok 或 IDE 自動生成的注解,這是出于“建模思想”與“封裝業務規則”的考慮。以下是詳細解釋與建議。

  1. 🧠 破壞封裝與建模思想:DDD 強調 通過代碼表達領域模型的意圖和業務規則。直接暴露 getter/setter,會讓你的領域對象退化為一個貧血模型(Anemic Domain Model),只是一個數據容器,而不是業務的承載體。
// ? 錯誤寫法:getter/setter + public 字段完全暴露
@Getter
@Setter
public class Order {private Long id;private String status;
}

?這樣寫沒有任何領域語義,Order 的狀態可以隨意被外部修改,違背領域封裝原則。

  1. 🛡? Setter 允許任意修改內部狀態,打破一致性:領域對象的核心職責是:保障業務數據的完整性和一致性。Setter 讓外部可以繞過業務規則,隨意設置對象屬性:
order.setStatus("已支付"); // 沒有校驗是否可以從“已取消”變成“已支付”

而正確的做法應是:

public void markAsPaid() {if (!canPay()) {throw new IllegalStateException("當前狀態不可支付");}this.status = Status.PAID;
}
  1. 🏗? 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 建議

@Getter

? 只在讀取聚合標識、只讀字段時局部使用

@Setter

? 禁止在領域對象中使用

@AllArgsConstructor

? 不建議使用

@NoArgsConstructor

? 避免(除非 ORM 必須)

構造函數

? 應包含業務校驗邏輯

Builder 模式

? 可用于構造復雜值對象

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注解,要小心:

  1. 說明你的領域模型很可能耦合了基礎設施層邏輯(違背 DDD 分層)
  2. 可能導致業務邏輯難以復用或測試

說明

是否可用框架注解

domain

純領域邏輯模型、實體、聚合、值對象、領域服務

? 不依賴任何框架

infrastructure

數據持久化、消息中間件、緩存等實現細節

? 用 Spring 管理

application

調用編排、流程協調、事務管理等

? 用 Spring

interfaces

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)由 DomainServiceApplicationService 在外部注入,傳參給領域對象,不在對象中注入依賴。
1.2.3.3. 🧪 好處:
  • ? 易于單元測試:構造純對象即可測試,不依賴 Spring 環境。
  • ? 解耦框架:更容易遷移、更少“技術污染”。
  • ? 聚焦業務:領域對象只關心業務含義,職責清晰。

1.2.4. Domain對象行為拒絕setter、update、modify、save、delete等無明確業務含義的方法?

這是 DDD(領域驅動設計)中對 Domain 對象(即領域模型) 的一種強烈編碼規范:領域對象的方法必須具備明確的業務含義。這些方法通常只表示技術操作,而沒有任何具體的業務語義,違背了 DDD 中“領域模型體現業務行為”的基本理念。

錯誤方式(技術性方法)

正確方式(業務性方法)

user.setStatus("DISABLED")

user.disable()

loan.updateStatus("APPROVED")

loan.approve()

order.delete()

order.cancel()

account.save()

account.deposit(amount)/ account.withdraw(amount)

1.2.4.1. 領域模型是業務專家的語言映射
  1. setter/update/save 等是 面向 ORM 和數據庫 的語言。
  2. 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;// 記錄拒絕原因,可能還要寫審計日志}
}

方法名

是否允許

說明

setXxx()

?

除非是純值對象,如 DTO、配置類

updateXxx()

?

太籠統,建議改為具體業務操作

approve()cancel()

?

有明確業務語義

validate()calculate()

?

計算、驗證行為是業務的一部分

toDTO()toSnapshot()

?(只讀轉換)

可接受,但可以考慮放到 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 建議隱藏實現細節、突出業務意圖。值對象本身就是“一個不可變、具備自我完整性的業務值”,不管你是用 enumclassrecord 實現的,業務只需要知道這是 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),用于統一標識枚舉。

維度

建議

命名方式

推薦使用業務語言命名,不加 Enum 后綴

示例

LoanStatus 而非 LoanStatusEnum

原因

保持領域語言一致性、隱藏實現細節、業務表達自然

例外

與類型沖突、集成第三方工具時可保留 Enum

1.3. application層

1.3.1. application層拒絕XXXHandler、XXXProcessor、XXXContext等含義不明確的命名?

1.3.1.1. ? 為什么要避免這種模糊命名?

命名

問題

OrderHandler

“處理訂單”具體是創建、取消、派送、結算還是別的?看不出來

LoanProcessor

“處理貸款”是審批?風控?放款?也看不出來

UserContext

是用戶上下文對象?Session?請求參數?環境變量?不清晰

XXXManager

管理什么?責任不清

這些命名是技術導向的,不利于業務溝通和代碼可維護性。

1.3.1.2. ? 推薦的命名方式:動詞 + 業務動作或職責清晰的命名

命名應能直接反映業務操作的意圖,建議使用如下格式:

建議命名

職責

SubmitLoanApplicationService

提交貸款申請

CancelOrderService

取消訂單

ApproveLoanApplicationUseCase

審批貸款申請

TransferFundsApplicationService

發起資金轉賬

GenerateReportService

生成報表

這些命名都表達了 清晰的業務行為,更符合 DDD 中“應用層協調領域服務”的職責。

1.3.1.3. ? 如何重構?

原名

重命名建議

LoanProcessor

LoanApprovalService/ SubmitLoanService

OrderHandler

CreateOrderService/ CancelOrderUseCase

UserContext

CurrentUserInfo/ LoggedInUserInfo

1.3.1.4. 🧱 補充說明:不同于領域層、基礎設施層

層級

命名推薦

不推薦

Domain 層

LoanApplication/Order/User

LoanEntity / OrderDTO

Application 層

ApproveLoanApplicationService

/CancelOrderUseCase

LoanHandler / OrderProcessor

Infrastructure 層

KafkaMessageConsumer/HttpClientAdapter

可容忍 HandlerProcessor命名

命名類別

推薦所在層

說明

XXXHandler

? Infrastructure 層 或 Application 層中實現類

用于技術處理(如消息消費、HTTP 請求處理等)

XXXProcessor

? Infrastructure 層 或 Application 層中實現類

用于組合多個行為、任務編排

XXXContext

? 可用于跨調用傳遞上下文對象(如流程上下文),但不作為核心業務對象

放在 Application 層或跨層共享模塊

1.3.1.5. ? 總結

命名原則

說明

? 避免 XXXHandler / XXXProcessor / XXXContext

業務語義不明確

? 使用 動作+對象+用途

符合統一語言

? 命名體現職責和行為

方便業務溝通、代碼自解釋

? 不建議應用層泛化職責(如一個類什么都管)

導致職責混亂、難以維護

1.3.2. 區分命令和查詢,命令推薦KXXCommandService,查詢推薦XXXQueryService?

你提到的這個命名方式和區分 命令(Command)查詢(Query) 的設計,是現代 DDD(領域驅動設計)中非常推薦的一種 CQRS(Command Query Responsibility Segregation,命令查詢職責分離) 實踐。

1.3.2.1. ? 命名規則推薦

類型

命名規范示例

說明

命令類

XXXCommandServiceXXXCommandAppService

代表狀態變更操作(有副作用)

查詢類

XXXQueryServiceXXXQueryAppService

代表數據讀取操作(無副作用)

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. 👇 實戰建議
  1. 應用服務層(Application Service)就應該按照 Command / Query 分開設計
  2. Controller 層調用時清晰地知道是讀請求還是寫請求
  3. 命名約定保持一致:
UserCommandService / UserCommandAppService
UserQueryService / UserQueryAppService
  1. 不需要為了“統一”而把 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);
}

問題:

  1. CreditResponseDTO 是外部定義的結構,字段命名、含義不一定穩定
  2. 一旦外部結構發生變動,你的整個服務層、接口層都需要改
  3. 你的業務邏輯會被迫使用外部系統的定義,嚴重耦合
1.4.4.2. ? 正確做法:引入 轉換層(Assembler) 和 防腐層(Adaptor)
// 對外暴露領域對象或自定義 VO,而非外部結構
public CreditResult checkCredit(String userId) {CreditResponseDTO responseDTO = creditPlatformClient.query(userId);return CreditAssembler.toCreditResult(responseDTO); // 轉換為內部對象
}
  • CreditResponseDTO 只在 AdaptorAssembler 層使用
  • CreditResult 是你自己定義的領域對象或 VO,用于業務邏輯或接口輸出
  • 這樣無論外部系統怎么變,只需改 Adapter/Assembler,不影響核心業務
1.4.4.3. 🎯 目的總結

原則

說明

防腐

外部系統不穩定,不可信,要設“隔離層”防污染

解耦

內部系統演化應與外部系統解耦

可維護

變化控制在邊界,便于測試和演進

語義清晰

自定義對象語義明確,更符合業務語言

1.4.4.4. 🧩 實戰建議

類型

示例

是否允許透傳?

正確做法

第三方接口返回對象

AliyunRiskResponse

? 禁止透傳

轉換為 RiskResult

數據庫查詢的 PO

UserPO

? 禁止透傳

轉換為 UserEntity

前端提交的請求體 DTO

UserRegisterDTO

? 禁止透傳

轉換為 RegisterCommand

領域模型

UserEntity

? 允許傳遞

按照聚合設計使用

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
1.5.1.3. 結合起來的示例

類型

命名示例

語義說明

命令

CreateOrderCommand

請求創建訂單(動作指令)

事件

OrderCreatedEvent

訂單已被創建(已發生的事實)

事件

PaymentSucceededEvent

支付成功事件(動作完成的結果)

1.5.1.4. 總結

規范點

理由

事件名后綴 Event

明確事件類型,方便區分和維護

動詞過去式命名

事件是“已經發生的事實”,語義準確

2. DDD領域驅動實戰思考

2.1. 你對DDD的理解是什么? DDD怎么樣應用在實踐系統中,怎么發揮DDD的價值?

2.2. 什么項目場景使用DDD ,為什么使用DDD,現有技術框架滿足不了嗎,能不能不使用DDD ? 使用DDD 之后帶來的好處和弊端是什么?

2.3. 實踐過程中DDD遇到問題、怎么解決這些問題的?

博文參考

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/918843.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/918843.shtml
英文地址,請注明出處:http://en.pswp.cn/news/918843.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

開源衛星軟件平臺LibreCube技術深度解析

LibreCube技術深度解析&#xff1a;開源衛星軟件平臺的完整指南 LibreCube是一個專為CubeSat設計的模塊化開源衛星軟件平臺&#xff0c;它通過整合姿態控制、通信管理和任務調度等核心功能&#xff0c;為立方星開發者提供了完整的解決方案。本文將全面剖析LibreCube的技術架構…

React(四):事件總線、setState的細節、PureComponent、ref

React(四) 一、事件總線 二、關于setState的原理 1. setState的三種使用方式 (1)基本使用 (2)傳入一個回調 (3)第一個參數是對象,第二個參數是回調 2. 為什么setState要設置成異步 (1)提升性能,減少render次數 (2)避免state和props數據不同步 3. 獲取異步修改完數…

CPUcores-【硬核優化】CPU增強解鎖全部內核!可優化游戲性能、提升幀數!啟用CPU全內核+超線程,以更高優先級運行游戲!支持各種游戲和應用優化~

軟件介紹&#xff08;文末獲取&#xff09;CPUCores&#xff1a;游戲性能優化利器?這款工具&#xff0c;專為優化提升中低配電腦的幀數而生。其獨創的CPU資源調度技術&#xff0c;能讓老舊硬件煥發新生核心技術原理?采用「內核級隔離」方案&#xff0c;通過&#xff1a;系統進…

HQA-Attack: Toward High Quality Black-Box Hard-Label Adversarial Attack on Text

文本對抗性攻擊分為白盒攻擊和黑盒攻擊&#xff0c;其中黑盒攻擊更貼近現實&#xff0c;又可分為軟標簽和硬標簽設置&#xff0c;。這些名詞分別是什么意思 在文本對抗性攻擊中&#xff0c;“白盒攻擊”“黑盒攻擊”以及黑盒攻擊下的“軟標簽”“硬標簽”設置&#xff0c;核心差…

PyCharm性能優化與大型項目管理指南

1. PyCharm性能深度調優 1.1 內存與JVM配置優化 PyCharm基于JVM運行,合理配置JVM參數可顯著提升性能: # 自定義VM選項文件位置 # Windows: %USERPROFILE%\AppData\Roaming\JetBrains\<product><version>\pycharm64.exe.vmoptions # macOS: ~/Library/Applicat…

基于Java飛算AI的Spring Boot聊天室系統全流程實戰

在當今數字化時代&#xff0c;實時通訊已成為現代應用不可或缺的核心功能。從社交平臺到企業協作&#xff0c;從在線客服到游戲互動&#xff0c;實時聊天功能正以前所未有的速度滲透到各行各業。然而&#xff0c;開發一個功能完善的聊天室系統絕非易事——傳統開發模式下&#…

在 Conda 環境下編譯 C++ 程序時報錯:version `GLIBCXX_3.4.30‘ not found

報錯信息如下 ERROR:/root/SVF/llvm-16.0.4.obj/bin/clang: /opt/miniconda3/envs/py38/lib/libstdc.so.6: version GLIBCXX_3.4.30 not found (required by /root/SVF/llvm-16.0.4.obj/bin/clang)根據錯誤信息&#xff0c;問題是由于 Conda 環境中的libstdc.so.6缺少GLIBCXX_3…

vue+flask基于Apriori算法規則的求職推薦系統

文章結尾部分有CSDN官方提供的學長 聯系方式名片 文章結尾部分有CSDN官方提供的學長 聯系方式名片 關注B站&#xff0c;有好處&#xff01;編號&#xff1a;F069 基于Apriori關聯規則職位相似度的推薦算法進行職位推薦 基于決策樹、隨機森林的預測薪資 vueflaskmysql爬蟲 設計求…

機器學習第九課之DBSCAN算法

目錄 簡介 一、dbscan相關概念 二、dbscan的API 三、案例分析 1. 導入所需庫 2. 數據讀取與預處理 3. 數據準備 4. DBSCAN 參數調優 5. 確定最佳參數并應用 總結 簡介 本次我們將聚焦于一款極具特色的聚類算法 ——DBSCAN。相較于 K-means 等需要預先指定簇數量的算法…

給AI開一副“健忘藥”:Dropout如何治愈神經網絡的死記硬背癥

**——解讀《Dropout: A Simple Way to Prevent Neural Networks from Overfitting》**想象一位學生備考時&#xff0c;只反復背誦三套模擬題答案&#xff0c;卻在真正的考場上面對新題型束手無策——這種**死記硬背不會舉一反三**的問題&#xff0c;正是神經網絡中的“過擬合”…

【框架】跨平臺開發框架自用整理

Tauri 2.0 | Tauri https://github.com/tauri-apps/tauri 創建小型、快速、安全、跨平臺的應用程序 獨立于前端 將你現有的網絡技術棧帶到 Tauri 或開始新的項目。 Tauri 支持任何前端框架&#xff0c;所以你不需要改變你的技術棧。 跨平臺 使用單個代碼庫為 Linux、macOS、W…

web前端第三次作業

一、作業要求&#xff1a;使用js完成抽獎項目 效果和內容自定義&#xff0c;可以模仿游戲抽獎頁面二、代碼<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthde…

wrap cpp variant as dll for c to use

包裝c的variant給c用 variant_wrapper.cpp #include <variant> #include <unordered_map> #include <cstring> #include <cstdio> #include <new> #include <memory> #include <functional> #include <cstdlib>// 類型ID定義 …

GraphRAG查詢(Query)流程實現原理分析

文章目錄說明一 GraphRAG查詢&#xff08;Query&#xff09;流程二 Local Search 實現原理三 Global Search 實現原理四 GraphRAG Python API使用說明 本文學自賦范社區公開課&#xff0c;僅供學習和交流使用&#xff01;本文重在介紹GraphRAG查詢流程&#xff0c;有關索引構建…

服務器的安全檢測和防御技術

1. 服務器安全風險1.1 不必要的訪問&#xff08;如只提供HTTP服務&#xff09;若服務器僅需提供 HTTP 服務&#xff0c;卻開放了其他不必要的訪問途徑&#xff0c;會增加風險。通過應用識別、控制&#xff0c;可精準識別應用類型&#xff0c;限制非必要訪問&#xff0c;保障服務…

FileLink:為企業跨網文件傳輸筑牢安全與效率基石

FileLink&#xff1a;為企業跨網文件傳輸筑牢安全與效率基石在企業數據往來日益頻繁的今天&#xff0c;跨網文件傳輸的安全性和高效性是企業順暢運營的關鍵。傳統傳輸方式在安全防護、系統融合及成本控制上的短板愈發明顯&#xff0c;而 FileLink 憑借在這些方面的突出表現&…

java設計模式之開閉原則使用舉例

1. 輸入法皮膚擴展&#xff08;抽象類實現&#xff09; 場景&#xff1a;用戶可為輸入法更換不同皮膚&#xff08;如默認皮膚、CSDN皮膚&#xff09;。 實現&#xff1a; 抽象層&#xff1a;定義抽象類AbstractSkin&#xff0c;聲明皮膚顯示方法。擴展&#xff1a;新增皮膚只需…

Spark Shuffle機制原理

文章目錄1.什么是Shuffle?2.Shuffle解決什么問題?3.Shuffle Write與Shuffle Read4.Shuffle的計算需求4.1 計算需求表4.2 partitionby4.3 groupByKey4.4 reduceByKey4.5 sortByKey5.Shuffle Write框架設計與實現5.1 Shuffle Write框架實現的功能5.2 Shuffle Write的多種情況5.…

Cursor vs Trae vs VSCode:2025終極IDE橫評,誰才是開發者的效率之選?

前言 2025年的編程世界&#xff0c;AI不再只是輔助&#xff0c;而是編程工作流的核心驅動者。從微軟的VSCode 到新銳 Cursor 與國產黑馬 Trae &#xff0c;三大 IDE 正在重新定義“人機協作”的邊界。本文從架構設計、AI能力、場景適配等維度&#xff0c;帶你看透工具本質&…

Vue 安裝指定版本依賴包、刪除某個依賴包、依賴管理

如何安裝指定版本的依賴包安裝指定版本&#xff1a;一旦你知道了想要的版本號&#xff0c;比如3.4.0&#xff0c;你可以使用以下命令來安裝這個版本的vue-router&#xff1a;npm install vue-router3.4.0 --save這里的^表示安裝3.4.0的最新小版本更新&#xff0c;但不會超過主版…