Skip to content

Instantly share code, notes, and snippets.

@kenming
Last active September 19, 2026 04:55
Show Gist options
  • Select an option

  • Save kenming/b753a6d454abe88682a3ee1d010fbea1 to your computer and use it in GitHub Desktop.

Select an option

Save kenming/b753a6d454abe88682a3ee1d010fbea1 to your computer and use it in GitHub Desktop.
簡化 DDD 的 Application Layer 設計:Business Capability、Service 顆粒度與 Domain Logic 放置原則

Simplified DDD Application Layer Design

1. 文件目的

本文整理一套適合中小型 Monolithic Application 的 Simplified DDD Application Layer 設計原則。

這套設計的目標不是完整實作所有 DDD Pattern,而是在以下幾個面向取得平衡:

  • 開發效率
  • 清楚的業務邊界
  • 可維護性
  • 可測試性
  • 後續重構與擴展彈性

核心原則是:

先以簡單、直接且足以支撐目前需求的 Application Service 完成 Use Case;只有當 Business Logic 的複雜度、重用性或 Domain significance 提升時,才引入必要的 Domain abstraction。


2. Architecture Direction

Backend 採 Modular Monolith / Simplified DDD。

基本依賴方向:

API
 ↓
Application
 ↓
Domain
 ↑
Infrastructure

Application Layer 是 API 與 Domain / Persistence 之間的主要 Use Case 執行層。

在 Monolithic Application 中,不需要刻意將內部功能模組設計成類似 Microservices 的 API-to-API 呼叫。

跨功能流程可由目前 Use Case 所屬的 Application Service,直接協調所需的 Domain Object 與 Repository。

概念上:

API
 ↓
Application Service
 ├─ Use Case control flow
 ├─ Simple business rules
 ├─ Domain coordination
 ├─ Repository coordination
 └─ Transaction coordination
 ↓
Domain / Repository Abstraction
 ↓
Infrastructure

3. Application Service Granularity

Application Layer 採 Business Capability-oriented Service。

也就是:

以 Business Capability(業務能力)作為主要 Service Boundary。

這裡不採以下兩種極端:

  • 每個 HTTP Endpoint 建立一個 Service。
  • 每個 Use Case 建立一個獨立 Service Class。

Use Case 仍然是重要的業務操作設計單位,但不需要機械式對應成一個 Class。

例如:

OrderService
PaymentService
InventoryService
FulfillmentService

這些 Service 分別代表不同 Business Capability。

相較於「功能模組」這個較容易受 UI、技術結構或組織方式影響的概念,Business Capability 更接近真正的業務責任邊界。


4. Naming Convention

以 Python 為例:

Class       → PascalCase
Module/File → snake_case
Method      → snake_case
Variable    → snake_case
Constant    → UPPER_SNAKE_CASE

例如:

application/
├─ order_service.py
├─ payment_service.py
├─ inventory_service.py
└─ fulfillment_service.py

Class:

class OrderService:
    ...

class PaymentService:
    ...

class InventoryService:
    ...

Service method 則表達具體 Application Operation:

class PaymentService:
    def process_payment(...):
        ...

    def retry_payment(...):
        ...

5. Application Service Responsibility

Application Service 主要負責:

  1. 執行 Use Case control flow。
  2. 載入 Use Case 所需資料。
  3. 協調 Domain Object。
  4. 協調一個或多個 Repository。
  5. 執行簡單且局部的 Business Logic。
  6. 管理 Use Case-level transaction coordination。
  7. 將結果回傳 API Layer。

Application Service 不應:

  • 依賴 HTTP / Framework-specific request object。
  • 直接包含 transport concern。
  • 直接撰寫 SQL 或 Persistence implementation。
  • 為了形式完整建立大量 Manager、Helper 或抽象類別。
  • 預設透過另一個 Application Service 才能取得其他 Business Capability 的資料。

6. Business Logic Placement Strategy

6.1 Application-first Strategy

在 Simplified DDD 中,簡單、局部,而且目前只服務單一流程的 Business Logic,可以先直接實作於 Application Service。

例如:

class InventoryService:
    def adjust_inventory(self, product_id, direction, quantity):
        inventory = self.inventory_repository.get(product_id)

        if direction == "Decrease" and inventory.quantity < quantity:
            raise InsufficientInventoryError()

        # apply adjustment
        ...

這類簡單規則不需要一開始就建立:

InventoryAdjustmentPolicy
InventoryDomainService
InventoryQuantityRule

這樣可以避免過早抽象,並降低中小型系統的設計與實作成本。


7. Domain Extraction Criteria

Business Logic 不需要一開始全部放入 Domain。

當出現以下情況時,再考慮抽離:

  1. 同一規則開始被多個 Application Service 使用。
  2. 規則包含多個條件、狀態轉換或 invariant。
  3. 規則本身需要獨立 Unit Test 才容易理解。
  4. Application Service 因 Business Logic 持續膨脹。
  5. 該邏輯已形成明確且穩定的 Domain Concept。
  6. 即使只被單一 Service 使用,但其業務複雜度已足以形成獨立責任。

可以抽離成:

  • Entity behavior
  • Value Object
  • Domain Service
  • Domain Policy

抽離的目的不是單純追求 DRY,而是建立清楚的 Domain responsibility。


8. Avoid Generic Domain Abstractions

不應因為少量程式碼重複,就建立:

CommonService
SharedDomainService
BusinessHelper
DomainHelper

這些類別通常缺乏明確 Domain 語意,最後容易變成另一種 God Object。

真正值得抽離的邏輯,應該具備明確且穩定的業務概念。


9. 流程協調(Orchestration)

流程協調(Orchestration)指的是:

單一 Application Use Case 為完成一個完整業務目標,需要協調兩個以上 Business Capability 所屬的 Domain Object 與 Repository。

例如付款成功可能同時影響:

Payment
  └─ Pending → Success

Order
  └─ PendingPayment → Paid

Inventory
  └─ Deduct Stock

Inventory Movement
  └─ Create Movement Record

Application Service 可以直接協調:

PaymentService
   ├─ PaymentRepository
   ├─ OrderRepository
   ├─ InventoryRepository
   └─ InventoryMovementRepository

而不一定需要:

PaymentService
   ↓
OrderService
   ↓
InventoryService

更不需要在同一 Monolithic Application 中,刻意模擬:

Payment Module
   ↓ HTTP
Order Module
   ↓ HTTP
Inventory Module

後者比較接近 Distributed System / Microservices 的設計成本。


10. Service-to-Service Dependency

Application Service 之間預設不互相形成深層 call chain。

例如不建議:

PaymentService
   ↓
OrderService
   ↓
InventoryService

主要原因:

  • transaction boundary 變得不清楚。
  • Service ownership 容易重疊。
  • call chain 難以追蹤。
  • exception handling 複雜化。
  • 測試成本提高。

較務實的方式是:

PaymentService
   ├─ PaymentRepository
   ├─ OrderRepository
   ├─ InventoryRepository
   └─ InventoryMovementRepository

由 owning Application Service 負責完整 Use Case 的流程協調。


11. Repository Responsibility

Repository 用來隔離 Application / Domain 與 Persistence implementation。

Application Service 可以:

  • 使用一個 Repository。
  • 同時協調多個 Repository。
  • 透過 Repository 載入不同 Business Capability 所需資料。

Application Service 不應:

  • 直接操作 SQL。
  • 依賴 database connection。
  • 依賴 ORM-specific implementation detail。

概念上:

Application Service
        ↓
Repository Abstraction
        ↓
Infrastructure Repository
        ↓
Database

12. Transaction Responsibility

Use Case-level transaction coordination 通常屬於 Application Layer。

例如付款成功需要保證:

Payment = Success
Order = Paid
Inventory deducted
Inventory Movement created

這些操作應形成一致的 transaction boundary。

但具體實作方式,例如:

  • Unit of Work
  • ORM transaction
  • Database connection ownership
  • Locking
  • Optimistic concurrency
  • Pessimistic concurrency

應依實際技術與系統規模再決定,不需要在 Application Service Design 階段過早固定。


13. Simplified DDD Design Principles

Simplicity First

先採最小、清楚、可維護的 Service / Repository / Domain 結構。

Business Capability Boundary

Application Service 依 Business Capability 劃分,而不是依 HTTP Endpoint 數量劃分。

Application-first Business Logic

簡單且局部的 Business Logic 可以先保留在 Application Service。

Extract on Real Complexity

當 Business Logic 的複雜度、重用性或 Domain significance 提高時,再抽離至 Domain。

No Service Call Chain by Default

跨功能 Use Case 由 owning Application Service 直接協調所需 Repository 與 Domain Object。

No Premature DDD

不要因為採用 DDD,就預先建立:

  • Aggregate
  • Factory
  • Domain Service
  • Policy
  • Specification
  • Event
  • CQRS
  • Mediator

只有在需求真正需要時才引入。


14. 適用情境

這套設計特別適合:

  • 中小型 Business Application。
  • Modular Monolith。
  • CRUD 與 Workflow 混合型系統。
  • 尚未需要 Microservices 的系統。
  • 希望兼顧開發效率與架構清晰度的團隊。
  • 希望保留未來重構與 Domain Model 演進空間的系統。

它的核心精神不是追求 Architecture Purity,而是:

在目前系統規模下採用足夠好的結構,同時保留未來演進與重構的空間。


15. 最後的設計觀點

完整 DDD 強調將 Business Logic 清楚放入 Domain。

這個方向本身沒有問題。

但在中小型系統中,如果一開始就完整導入所有 Domain abstraction,往往會產生比 Business Complexity 本身更高的 Architecture Complexity。

Simplified DDD 的價值在於允許系統逐步演進:

Simple Logic
    ↓
Application Service
    ↓ complexity grows
Domain Concept emerges
    ↓
Refactor to Domain

也就是:

先讓設計支撐現在的複雜度,再讓架構隨真正出現的複雜度成長。

這樣可以同時兼顧開發效率、可理解性,以及未來的重構彈性。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment