本文整理一套適合中小型 Monolithic Application 的 Simplified DDD Application Layer 設計原則。
這套設計的目標不是完整實作所有 DDD Pattern,而是在以下幾個面向取得平衡:
- 開發效率
- 清楚的業務邊界
- 可維護性
- 可測試性
- 後續重構與擴展彈性
核心原則是:
先以簡單、直接且足以支撐目前需求的 Application Service 完成 Use Case;只有當 Business Logic 的複雜度、重用性或 Domain significance 提升時,才引入必要的 Domain abstraction。
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
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 更接近真正的業務責任邊界。
以 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(...):
...Application Service 主要負責:
- 執行 Use Case control flow。
- 載入 Use Case 所需資料。
- 協調 Domain Object。
- 協調一個或多個 Repository。
- 執行簡單且局部的 Business Logic。
- 管理 Use Case-level transaction coordination。
- 將結果回傳 API Layer。
Application Service 不應:
- 依賴 HTTP / Framework-specific request object。
- 直接包含 transport concern。
- 直接撰寫 SQL 或 Persistence implementation。
- 為了形式完整建立大量 Manager、Helper 或抽象類別。
- 預設透過另一個 Application Service 才能取得其他 Business Capability 的資料。
在 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
這樣可以避免過早抽象,並降低中小型系統的設計與實作成本。
Business Logic 不需要一開始全部放入 Domain。
當出現以下情況時,再考慮抽離:
- 同一規則開始被多個 Application Service 使用。
- 規則包含多個條件、狀態轉換或 invariant。
- 規則本身需要獨立 Unit Test 才容易理解。
- Application Service 因 Business Logic 持續膨脹。
- 該邏輯已形成明確且穩定的 Domain Concept。
- 即使只被單一 Service 使用,但其業務複雜度已足以形成獨立責任。
可以抽離成:
- Entity behavior
- Value Object
- Domain Service
- Domain Policy
抽離的目的不是單純追求 DRY,而是建立清楚的 Domain responsibility。
不應因為少量程式碼重複,就建立:
CommonService
SharedDomainService
BusinessHelper
DomainHelper
這些類別通常缺乏明確 Domain 語意,最後容易變成另一種 God Object。
真正值得抽離的邏輯,應該具備明確且穩定的業務概念。
流程協調(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 的設計成本。
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 的流程協調。
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
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 階段過早固定。
先採最小、清楚、可維護的 Service / Repository / Domain 結構。
Application Service 依 Business Capability 劃分,而不是依 HTTP Endpoint 數量劃分。
簡單且局部的 Business Logic 可以先保留在 Application Service。
當 Business Logic 的複雜度、重用性或 Domain significance 提高時,再抽離至 Domain。
跨功能 Use Case 由 owning Application Service 直接協調所需 Repository 與 Domain Object。
不要因為採用 DDD,就預先建立:
- Aggregate
- Factory
- Domain Service
- Policy
- Specification
- Event
- CQRS
- Mediator
只有在需求真正需要時才引入。
這套設計特別適合:
- 中小型 Business Application。
- Modular Monolith。
- CRUD 與 Workflow 混合型系統。
- 尚未需要 Microservices 的系統。
- 希望兼顧開發效率與架構清晰度的團隊。
- 希望保留未來重構與 Domain Model 演進空間的系統。
它的核心精神不是追求 Architecture Purity,而是:
在目前系統規模下採用足夠好的結構,同時保留未來演進與重構的空間。
完整 DDD 強調將 Business Logic 清楚放入 Domain。
這個方向本身沒有問題。
但在中小型系統中,如果一開始就完整導入所有 Domain abstraction,往往會產生比 Business Complexity 本身更高的 Architecture Complexity。
Simplified DDD 的價值在於允許系統逐步演進:
Simple Logic
↓
Application Service
↓ complexity grows
Domain Concept emerges
↓
Refactor to Domain
也就是:
先讓設計支撐現在的複雜度,再讓架構隨真正出現的複雜度成長。
這樣可以同時兼顧開發效率、可理解性,以及未來的重構彈性。