DDD 是一套 應對復雜業務系統 的設計方法論,核心是 讓代碼直接映射業務邏輯,避免技術實現與業務需求脫節。
關鍵區別:
- 傳統開發:根據數據庫表寫 CRUD(技術驅動)。
- DDD:根據業務行為建模(業務驅動)。
舉例:
- 傳統方式:設計?
orders
?表 → 寫?OrderDAO
?→ 實現增刪改查。 - DDD 方式:先分析業務如何描述“訂單”(如“下單”、“取消”、“支付超時”)→ 再建模為?
Order
?對象的行為。
貧血模型與充血模型
本質特征
特征 | 貧血模型 | 充血模型 |
---|---|---|
業務邏輯存放位置 | Service層 | 領域對象(Entity/Value Object)內部 |
領域對象職責 | 僅作為數據載體(DTO) | 封裝數據和行為 |
代碼表現 | 大量Getter/Setter,無業務方法 | 包含業務方法 |
適用場景 | 簡單CRUD系統 | 復雜業務系統(DDD推薦) |
代碼實現對比
貧血模型實現
Order
?只是數據的容器,業務邏輯散落在?Service
?中。- 當業務復雜時,
Service
?會變成“上帝類”(God Class)。
// 1. 貧血的Order類(僅有數據)
public class Order {private String orderId;private String productId;private int quantity;private String status;// 只有Getter/Setter,無業務邏輯public String getOrderId() { return orderId; }public void setOrderId(String orderId) { this.orderId = orderId; }// 其他Getter/Setter省略...
}// 2. 業務邏輯全在Service中
@Service
public class OrderService {public void createOrder(String productId, int quantity) {Order order = new Order();order.setOrderId(UUID.randomUUID().toString());order.setProductId(productId);order.setQuantity(quantity);order.setStatus("CREATED");// 校驗邏輯也在Service中if (quantity <= 0) {throw new IllegalArgumentException("Quantity must be positive");}orderRepository.save(order);}
}
充血模型實現
Order
?自己負責業務規則(如創建校驗、狀態轉換)。- 業務邏輯高內聚,Service層變薄。
// 1. 充血的Order類(封裝數據和行為)
public class Order {private String orderId;private String productId;private int quantity;private OrderStatus status; // 枚舉// 私有構造方法,強制通過工廠方法創建private Order(String productId, int quantity) {this.orderId = UUID.randomUUID().toString();this.productId = productId;this.quantity = quantity;this.status = OrderStatus.CREATED;}// 靜態工廠方法(封裝創建邏輯)public static Order create(String productId, int quantity) {if (quantity <= 0) {throw new IllegalArgumentException("Quantity must be positive");}return new Order(productId, quantity);}// 領域行為public void cancel() {if (this.status == OrderStatus.PAID) {throw new IllegalStateException("Paid order cannot be cancelled");}this.status = OrderStatus.CANCELLED;}
}// 2. 應用層Service(僅協調流程)
@Service
public class OrderAppService {public void createOrder(String productId, int quantity) {Order order = Order.create(productId, quantity); // 調用領域對象orderRepository.save(order);}
}
DDD實戰
架構圖
架構設計
src/
├── presentation/ # 用戶接口層
├── application/ # 應用層
├── domain/ # 領域層
│ ├── model/ # 領域模型(實體、值對象等)
│ └── repository/ # 倉儲接口
└── infrastructure/ # 基礎設施層(簡單實現)
代碼示例
(1) 用戶接口層(Presentation Layer)
處理HTTP請求,接收用戶輸入。
// presentation/OrderController.java
@RestController
@RequestMapping("/orders")
public class OrderController {private final OrderAppService orderAppService; // 依賴應用層@PostMappingpublic ResponseEntity<String> createOrder(@RequestBody CreateOrderRequest request) {orderAppService.createOrder(request.toCommand()); // 轉換為命令對象return ResponseEntity.ok("Order created");}
}// DTO:用戶請求參數
public class CreateOrderRequest {private String productId;private int quantity;// 轉換為應用層命令對象public CreateOrderCommand toCommand() {return new CreateOrderCommand(productId, quantity);}
}
(2) 應用層(Application Layer)
協調領域對象完成用例,不包含業務邏輯。
// application/OrderAppService.java
@Service
public class OrderAppService {private final OrderRepository orderRepository; // 依賴領域層倉儲public void createOrder(CreateOrderCommand command) {// 1. 調用領域層創建訂單Order order = Order.create(command.getProductId(), command.getQuantity());// 2. 通過倉儲保存聚合根orderRepository.save(order);// 3. 可發布領域事件(此處省略)}
}// 應用層命令對象
public class CreateOrderCommand {private String productId;private int quantity;// 構造方法、getter省略...
}
(3) 領域層(Domain Layer)
核心業務邏輯,包含實體、值對象、倉儲接口等。
3.1 實體(Entity)與聚合根
// domain/model/Order.java
public class Order {private String orderId; // 唯一標識private String productId;private int quantity;private OrderStatus status; // 枚舉:CREATED, PAID, CANCELLED等// 私有構造方法,強制通過工廠方法創建private Order(String productId, int quantity) {this.orderId = UUID.randomUUID().toString();this.productId = productId;this.quantity = quantity;this.status = OrderStatus.CREATED;}// 工廠方法(封裝創建邏輯)public static Order create(String productId, int quantity) {if (quantity <= 0) {throw new IllegalArgumentException("Quantity must be positive");}return new Order(productId, quantity);}// 領域行為public void cancel() {this.status = OrderStatus.CANCELLED;}
}
3.2 倉儲接口(Repository)
// domain/repository/OrderRepository.java
public interface OrderRepository {void save(Order order);// 其他查詢方法省略...
}
(4) 基礎設施層(Infrastructure Layer)
實現領域層定義的倉儲接口(如數據庫操作)。
// infrastructure/persistence/OrderRepositoryImpl.java
@Repository
public class OrderRepositoryImpl implements OrderRepository {// 模擬數據庫(實際可用JPA/MyBatis等)private Map<String, Order> orders = new HashMap<>();@Overridepublic void save(Order order) {orders.put(order.getOrderId(), order);System.out.println("Order saved: " + order.getOrderId());}
}
調用流程圖
對比傳統CRUD
DDD | 傳統CRUD |
---|---|
先設計Order 的行為方法 | 直接操作orders 表 |
業務邏輯在領域層 | 業務邏輯散落在Service中 |
通過聚合根保證一致性 | 需手動校驗外鍵約束 |
充血模型特點
- 禁止Setter:通過構造方法或工廠方法創建對象。
- 領域方法命名:使用業務語言(如?
cancel()
?而非?setStatus("CANCELLED")
)。 - 避免“貧血”的充血模型:
- 錯誤示例:在?
Order
?中加了方法,但核心邏輯仍在Service中。
- 錯誤示例:在?
常見誤區
- ??認為充血模型等于“把所有代碼塞進Entity”:
- 跨聚合邏輯應放在領域服務(Domain Service)中。
- ??忽視聚合根的一致性邊界:
- 例如訂單和庫存屬于不同聚合,不能直接在?
Order
?中修改庫存,應通過領域事件解耦。
- 例如訂單和庫存屬于不同聚合,不能直接在?