DDD 專案 Code Review 完整指南:如何確保領域驅動設計的程式碼品質

更新 發佈閱讀 17 分鐘
raw-image

領域驅動設計(Domain-Driven Design, DDD)已經成為現代軟體開發的重要方法論,但在實際專案中,我們經常看到團隊雖然採用了 DDD 的架構,卻沒有真正遵循其核心原則。本文將提供一份完整的 DDD Code Review 檢查清單,幫助你的團隊寫出真正符合 DDD 精神的程式碼。

為什麼需要 DDD Code Review 檢查清單?

在我多年的軟體開發經驗中,我發現最常見的問題不是團隊不了解 DDD 理論,而是在實作時容易偏離正軌。一個結構看起來像 DDD 的專案,可能實際上只是把傳統的 MVC 架構重新包裝,業務邏輯仍然散落在各個地方,Domain 層變成了貧血模型的集合。

這就是為什麼我們需要一個系統性的檢查清單,來確保每一行程式碼都符合 DDD 的設計原則。

專案結構:DDD 的基礎骨架

四層架構檢查

首先,我們需要確保專案具有完整的四層結構:

project/
├── interfaces/ # 介面層(Controllers, DTOs)
├── application/ # 應用層(Application Services, Commands)
├── domain/ # 領域層(Entities, Value Objects, Domain Services)
└── infrastructure/ # 基礎設施層(Repositories, External Services)

依賴方向檢查重點:

  • interfaces → application → domain ← infrastructure
  • Domain 層不能依賴任何其他層(依賴反轉原則)
  • 絕對不允許跨層直接依賴

我曾經參與過一個專案,看起來有 DDD 的目錄結構,但是 Controller 直接注入 Repository,完全違反了 DDD 的分層原則。這種違規會導致業務邏輯散落,難以維護。

Domain 層結構完整性

Domain 層是 DDD 的核心,必須包含完整的領域概念:

domain/
├── entity/ # 領域實體
├── aggregate/ # 聚合根
├── valueobject/ # 值物件
├── repository/ # 倉儲介面(僅介面,不含實作)
├── service/ # 領域服務
├── event/ # 領域事件
└── exception/ # 領域異常

Domain 層:DDD 的靈魂所在

告別貧血模型:富含業務邏輯的實體

最常見的 DDD 反模式就是貧血模型。讓我們看看正確和錯誤的實作:

✅ 正確示例:富有業務邏輯的實體

public class InsurancePolicy {
private PolicyId id;
private PolicyStatus status;
private Money premium;

// ✅ 包含業務邏輯的方法
public ClaimResult processClaim(ClaimRequest request) {
this.validateCanClaim();
Money amount = this.calculateClaimAmount(request);
this.recordClaimHistory(request);
return ClaimResult.of(amount);
}

// ✅ 業務規則封裝在實體內部
private void validateCanClaim() {
if (!this.status.canProcessClaim()) {
throw new PolicyCannotClaimException("保單狀態不允許申請理賠");
}
}

// ✅ 使用領域語言的方法名
public void activate() {
if (this.status.isExpired()) {
throw new CannotActivateExpiredPolicyException();
}
this.status = PolicyStatus.ACTIVE;
}
}

❌ 錯誤示例:貧血模型

// ❌ 只有資料,沒有行為
public class InsurancePolicy {
private String id;
private String status;
private BigDecimal premium;

// 只有 getter/setter,業務邏輯在別的地方
public String getId() { return id; }
public void setStatus(String status) { this.status = status; }
public BigDecimal getPremium() { return premium; }
}

Code Review 檢查要點

實體檢查清單:

  • [ ] 是否包含業務邏輯方法,而不只是 getter/setter?
  • [ ] 方法名是否使用領域專家的語言?
  • [ ] 是否正確封裝狀態,透過方法控制變更?
  • [ ] 是否避免了基礎設施相關的註解(如 @Entity, @Table)?

值物件:不可變的領域概念

值物件是 DDD 中容易被忽略但非常重要的概念:

// ✅ 正確的值物件實作
public class Money {
private final BigDecimal amount;
private final Currency currency;

public Money(BigDecimal amount, Currency currency) {
this.validateAmount(amount); // ✅ 建立時驗證
this.amount = amount;
this.currency = currency;
}

// ✅ 不可變性:返回新的物件
public Money add(Money other) {
this.validateSameCurrency(other);
return new Money(this.amount.add(other.amount), this.currency);
}

// ✅ 透過屬性值判斷相等性
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Money)) return false;
Money other = (Money) obj;
return Objects.equals(amount, other.amount) &&
Objects.equals(currency, other.currency);
}
}

值物件檢查清單:

  • [ ] 是否不可變(final 欄位,無 setter)?
  • [ ] 是否在建立時執行驗證?
  • [ ] equals/hashCode 是否基於屬性值?
  • [ ] 是否包含有意義的業務方法?

Application 層:流程編排者

Application 層常常是業務邏輝洩露的重災區。讓我們看看正確的實作:

// ✅ 正確的應用服務
@ApplicationService
@Transactional
public class PolicyApplicationService {

public PolicyId createPolicy(CreatePolicyCommand command) {
// 1. 載入必要的領域物件
Customer customer = customerRepo.findById(command.getCustomerId());

// 2. 委託領域層處理業務邏輯
Policy policy = Policy.create(customer, command.toPolicyRequest());

// 3. 協調基礎設施操作
policyRepo.save(policy);
eventPublisher.publish(new PolicyCreatedEvent(policy.getId()));

return policy.getId();
}
}

❌ 常見錯誤:業務邏輯寫在 Application 層

@ApplicationService
public class PolicyApplicationService {
public void createPolicy(CreatePolicyCommand command) {
// ❌ 業務規則不應該寫在這裡
if (command.getAge() < 18) {
throw new UnderageException();
}

// ❌ 複雜計算不應該在這裡
BigDecimal premium = calculateBasePremium()
.multiply(getAgeRisk(command.getAge()))
.multiply(getOccupationRisk(command.getOccupation()));
}
}

Application 層檢查清單:

  • [ ] 是否只做流程編排,不包含業務邏輯?
  • [ ] 是否正確管理事務邊界?
  • [ ] 是否委託給 Domain 層處理複雜邏輯?
  • [ ] 是否避免複雜的 if-else 判斷?

常見違規:Domain 層的禁區

在 Code Review 中,以下任何一項出現在 Domain 層都應該被標記為違規:

🚫 絕對不能出現在 Domain 層的東西

  • 資料庫註解@Entity, @Table, @Column, @JoinColumn
  • Spring 框架@Service, @Repository, @Autowired, @Component
  • HTTP 相關HttpServletRequest, @RequestBody, @PathVariable
  • 外部服務RestTemplate, WebClient, HttpClient
  • 持久化技術:SQL 語句、JDBC、JPA Criteria
  • 序列化註解@JsonProperty, @XmlElement
  • 快取操作@Cacheable, Redis 操作
  • 檔案操作File, InputStream, OutputStream

記住:Domain 層必須保持純淨,只關注業務邏輯!

Infrastructure 層:技術實作的歸宿

Infrastructure 層是所有技術細節的家,但也要遵循一定的原則:

// ✅ 正確的倉儲實作
@Repository
public class JpaPolicyRepository implements PolicyRepository {

@Override
public Optional<Policy> findByPolicyNumber(PolicyNumber policyNumber) {
Optional<PolicyEntity> entity = jpaRepository.findByNumber(policyNumber.getValue());
return entity.map(this::toDomainObject);
}

@Override
public void save(Policy policy) {
PolicyEntity entity = this.toEntity(policy);
jpaRepository.save(entity);
}

// ✅ 正確處理 Domain Object ↔ Database Entity 轉換
private Policy toDomainObject(PolicyEntity entity) {
// 轉換邏輯
}
}

Code Review 最佳實踐

檢查流程

  1. 結構檢查:先看目錄結構和分層是否正確
  2. 依賴檢查:用工具檢查依賴方向
  3. Domain 檢查:重點審查業務邏輯放置
  4. 命名檢查:確保使用一致的領域語言
  5. 測試檢查:驗證業務規則測試覆蓋

實用工具推薦

  • ArchUnit:自動化架構檢查
  • SonarQube:程式碼品質分析
  • Checkstyle:命名規範檢查

測試:DDD 專案的品質保證

好的 DDD 專案應該有分層的測試策略:

// ✅ Domain 層單元測試
public class InsurancePolicyTest {

@Test
public void should_throw_exception_when_claim_on_expired_policy() {
// Given
Policy expiredPolicy = PolicyBuilder.anExpiredPolicy().build();
ClaimRequest request = new ClaimRequest(Money.of(1000));

// When & Then
assertThatThrownBy(() -> expiredPolicy.processClaim(request))
.isInstanceOf(PolicyCannotClaimException.class);
}
}

測試檢查要點:

  • [ ] Domain 層有豐富的單元測試?
  • [ ] 測試是否使用領域語言?
  • [ ] 是否測試了重要的業務規則?
  • [ ] Domain 測試是否不依賴外部資源?

結論:保持 DDD 的初心

DDD 不只是一種架構模式,更是一種思維方式。它要求我們:

  1. 以業務為中心:所有設計決策都要回到業務價值
  2. 保持領域純淨:Domain 層只關注業務邏輯
  3. 使用通用語言:程式碼要能被領域專家理解
  4. 持續演進:隨著業務理解的深入而重構

記住,優秀的 DDD 專案不是一蹴而就的,需要團隊持續的努力和嚴格的 Code Review。使用這份檢查清單,可以幫助你的團隊寫出真正體現 DDD 精神的程式碼。

最後提醒:Domain 層是整個 DDD 的核心,必須保持純淨!

每一次 Code Review 都是學習和改進的機會。讓我們一起寫出更好的 DDD 程式碼!

留言
avatar-img
留言分享你的想法!
avatar-img
Baozilla, Let's go!
75會員
547內容數
我不急著定義自己是誰,也不急著證明什麼。 但我知道,只要內心不設限,生活的每一步都有可能是新的開始。 世界不一定溫柔,但我願意以溫柔回應它。 像水一樣,柔軟卻有力量; 像雲一樣,自由卻不迷失方向。 這是我的路,也可能是某些人的共鳴。
Baozilla, Let's go! 的其他內容
2025/08/29
在軟體開發的世界裡,「軟體架構師」這個角色常常被誤解。有些人認為他們是脫離實際的象牙塔理論家,有些人則把他們當作超級資深的開發者。那麼,軟體架構師到底在做什麼?讓我們深入探討這個備受爭議卻又至關重要的角色。 常見迷思 vs 現實 迷思一:架構師就是不寫程式碼的設計師 現實: 優秀的軟體架構師必
Thumbnail
2025/08/29
在軟體開發的世界裡,「軟體架構師」這個角色常常被誤解。有些人認為他們是脫離實際的象牙塔理論家,有些人則把他們當作超級資深的開發者。那麼,軟體架構師到底在做什麼?讓我們深入探討這個備受爭議卻又至關重要的角色。 常見迷思 vs 現實 迷思一:架構師就是不寫程式碼的設計師 現實: 優秀的軟體架構師必
Thumbnail
2025/08/20
1. 為什麼需要 Archon? 隨著 AI coding assistants 的興起,例如 Claude Code、Cursor、Windsurf、Kiro,開發者獲得了前所未有的生產力提升。這些助理能: 協助閱讀並解釋大型程式碼庫 自動生成樣板程式碼與單元測試 協助 debug 與重構
Thumbnail
2025/08/20
1. 為什麼需要 Archon? 隨著 AI coding assistants 的興起,例如 Claude Code、Cursor、Windsurf、Kiro,開發者獲得了前所未有的生產力提升。這些助理能: 協助閱讀並解釋大型程式碼庫 自動生成樣板程式碼與單元測試 協助 debug 與重構
Thumbnail
2025/07/30
Ref: Beyond Transformers: RockAI's Alternative Path to AGI 當AI模型真正學會「學習」時會發生什麼? 在2025年上海世界人工智慧大會的一個不起眼展位上,正在發生一場可能改變整個AI發展方向的技術革命。一隻完全離線的機器狗正在即時學習新技
Thumbnail
2025/07/30
Ref: Beyond Transformers: RockAI's Alternative Path to AGI 當AI模型真正學會「學習」時會發生什麼? 在2025年上海世界人工智慧大會的一個不起眼展位上,正在發生一場可能改變整個AI發展方向的技術革命。一隻完全離線的機器狗正在即時學習新技
Thumbnail
看更多
你可能也想看
Thumbnail
雙11於許多人而言,不只是單純的折扣狂歡,更是行事曆裡預定的,對美好生活的憧憬。 錢錢沒有不見,它變成了快樂,跟讓臥房、辦公桌、每天早晨的咖啡香升級的樣子! 這次格編突擊辦公室,也邀請 vocus「野格團」創作者分享掀開蝦皮購物車的簾幕,「加入購物車」的瞬間,藏著哪些靈感,或是對美好生活的想像?
Thumbnail
雙11於許多人而言,不只是單純的折扣狂歡,更是行事曆裡預定的,對美好生活的憧憬。 錢錢沒有不見,它變成了快樂,跟讓臥房、辦公桌、每天早晨的咖啡香升級的樣子! 這次格編突擊辦公室,也邀請 vocus「野格團」創作者分享掀開蝦皮購物車的簾幕,「加入購物車」的瞬間,藏著哪些靈感,或是對美好生活的想像?
Thumbnail
雙11購物節準備開跑,蝦皮推出超多優惠,與你分享實際入手的收納好物,包括貨櫃收納箱、真空收納袋、可站立筆袋等,並分享如何利用蝦皮分潤計畫,一邊購物一邊賺取額外收入,讓你買得開心、賺得也開心!
Thumbnail
雙11購物節準備開跑,蝦皮推出超多優惠,與你分享實際入手的收納好物,包括貨櫃收納箱、真空收納袋、可站立筆袋等,並分享如何利用蝦皮分潤計畫,一邊購物一邊賺取額外收入,讓你買得開心、賺得也開心!
Thumbnail
分享個人在新家裝潢後,精選 5 款蝦皮上的實用家居好物,包含客製化層架、MIT 地毯、沙發邊桌、分類垃圾桶及寵物碗架,從尺寸、功能到價格都符合需求,並提供詳細開箱心得與購買建議。
Thumbnail
分享個人在新家裝潢後,精選 5 款蝦皮上的實用家居好物,包含客製化層架、MIT 地毯、沙發邊桌、分類垃圾桶及寵物碗架,從尺寸、功能到價格都符合需求,並提供詳細開箱心得與購買建議。
Thumbnail
這篇文章著重於解釋軟體專案管理中的戰略意義和專案特性評估,並提出了四個不同像限的專案特性。
Thumbnail
這篇文章著重於解釋軟體專案管理中的戰略意義和專案特性評估,並提出了四個不同像限的專案特性。
Thumbnail
本書大多數的內容都以 OO 的概念出發,詳列了許多設計的臭味道,也有大量的例子。個人雖然不會這樣寫程式,但仍是覺得受益良多,至少在 code review 時能更清楚知道該怎麼描述問題。不過,即便不是用 OO 的概念,有些章節還是可以帶來一些想法,用 OO 概念寫程式的人更不該錯過這本好書。
Thumbnail
本書大多數的內容都以 OO 的概念出發,詳列了許多設計的臭味道,也有大量的例子。個人雖然不會這樣寫程式,但仍是覺得受益良多,至少在 code review 時能更清楚知道該怎麼描述問題。不過,即便不是用 OO 的概念,有些章節還是可以帶來一些想法,用 OO 概念寫程式的人更不該錯過這本好書。
Thumbnail
本書介紹了戰略設計、管理領域複雜度、實際應用領域驅動設計等主題。透過對核心子領域、支持子領域、限界上下文等概念的探討,提供了領域驅動設計的相關知識。這篇文章中還涉及了微服務、事件驅動架構和資料網格等相關主題,提供了設計系統和應用領域驅動設計的指導。
Thumbnail
本書介紹了戰略設計、管理領域複雜度、實際應用領域驅動設計等主題。透過對核心子領域、支持子領域、限界上下文等概念的探討,提供了領域驅動設計的相關知識。這篇文章中還涉及了微服務、事件驅動架構和資料網格等相關主題,提供了設計系統和應用領域驅動設計的指導。
Thumbnail
這本書從 docker 的角度出發,介紹很多可重複使用的 pattern,除了翻譯某些地方有點怪之外,算是很有趣的一本書,後面很多的 pattern 可以想成是 sidecar 的進階使用方式,在不改變應用程式的情況下,增加不同的功能,相當實用。
Thumbnail
這本書從 docker 的角度出發,介紹很多可重複使用的 pattern,除了翻譯某些地方有點怪之外,算是很有趣的一本書,後面很多的 pattern 可以想成是 sidecar 的進階使用方式,在不改變應用程式的情況下,增加不同的功能,相當實用。
Thumbnail
列出一套完整的程式 程式設計有許多種方法,不過通常會先列出清單的再逐一執行,這樣會加快程式設計的速度。設計通常會採取順推的辦法。所以順推的程式設計方式就是經歷觀念溝通、系統分析、資料統合、權限管理、頻率與時間、後台管理、畫面設計等等階段後,將框架設計完了以後,先列出一套完整的程式,將所有使用者都確
Thumbnail
列出一套完整的程式 程式設計有許多種方法,不過通常會先列出清單的再逐一執行,這樣會加快程式設計的速度。設計通常會採取順推的辦法。所以順推的程式設計方式就是經歷觀念溝通、系統分析、資料統合、權限管理、頻率與時間、後台管理、畫面設計等等階段後,將框架設計完了以後,先列出一套完整的程式,將所有使用者都確
Thumbnail
今天來聊個最近很夯的主題 DDD,但不是 DDD 的本尊 Domain Driven Design,而是無所不在的 Database Driven Design,Database Driven Design 不是不好,只是你的模型容易變成貧血模型,邏輯都集中在 service 層等等。
Thumbnail
今天來聊個最近很夯的主題 DDD,但不是 DDD 的本尊 Domain Driven Design,而是無所不在的 Database Driven Design,Database Driven Design 不是不好,只是你的模型容易變成貧血模型,邏輯都集中在 service 層等等。
Thumbnail
程式設計中不可或缺的一部分 介面是使用者與程式互動的媒介,因此介面的設計會影響使用者的體驗和感受。一個清晰明白、易懂的介面,可以讓使用者輕鬆地使用程式,並獲得良好的使用體驗。 需要與程式設計師密切溝通 設計師需要了解程式的功能和需求,並根據使用者的習慣和需求進行設計。設計師和程式設計師之間的溝
Thumbnail
程式設計中不可或缺的一部分 介面是使用者與程式互動的媒介,因此介面的設計會影響使用者的體驗和感受。一個清晰明白、易懂的介面,可以讓使用者輕鬆地使用程式,並獲得良好的使用體驗。 需要與程式設計師密切溝通 設計師需要了解程式的功能和需求,並根據使用者的習慣和需求進行設計。設計師和程式設計師之間的溝
Thumbnail
確保沒有遺漏或錯誤 程式的完整資訊資料對於程式設計至關重要。這是因為只有透過完整的資訊,我們才能確保在程式設計中沒有任何遺漏或錯誤。最終,後台管理扮演著管理系統中所有動作和行為是否符合特定標準的重要角色。 採取不符合預期的行動 這種符合性的重要性在於,當我們設計程式時,希望使用者按照預期的方式
Thumbnail
確保沒有遺漏或錯誤 程式的完整資訊資料對於程式設計至關重要。這是因為只有透過完整的資訊,我們才能確保在程式設計中沒有任何遺漏或錯誤。最終,後台管理扮演著管理系統中所有動作和行為是否符合特定標準的重要角色。 採取不符合預期的行動 這種符合性的重要性在於,當我們設計程式時,希望使用者按照預期的方式
Thumbnail
資料的統合 在程式設計中,其他人通常關心是否注意到執行的細節。作為程式設計師,主要應該關心的是程式的表現,但往往忽略了很多細節,這些細節可以決定程式的好壞。程式的好壞很大程度上取決於資料的統合,也就是資料是否被正規化。 不同類型的資料在系統中呈現一致 正規化可能對一些人來說聽起來很抽象,有些人
Thumbnail
資料的統合 在程式設計中,其他人通常關心是否注意到執行的細節。作為程式設計師,主要應該關心的是程式的表現,但往往忽略了很多細節,這些細節可以決定程式的好壞。程式的好壞很大程度上取決於資料的統合,也就是資料是否被正規化。 不同類型的資料在系統中呈現一致 正規化可能對一些人來說聽起來很抽象,有些人
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News