引例
DDD
?? 子域與界限上下文
?? 經(jīng)典分層架構(gòu)
?? UI層與應(yīng)用層
?? 領(lǐng)域?qū)?/h2>
?? 基礎(chǔ)設(shè)施層
總結(jié)
引例
在電商系統(tǒng)中”修改商品數(shù)量“的業(yè)務(wù)需求:
在用戶未支付訂單(Order)之前,用戶可以修改Order中商品(Goods)的數(shù)量, 數(shù)量更新后需要更新相應(yīng)的訂單金額(totalPrice)哼鬓。
實(shí)現(xiàn)一: Service + 貧血模型
在用戶未支付訂單(Order)之前,用戶可以修改Order中商品(Goods)的數(shù)量, 數(shù)量更新后需要更新相應(yīng)的訂單金額(totalPrice)哼鬓。
貧血類 Order, OrderItem
, 充當(dāng)ORM持久化對象
public class Order { // order 表
private Long id; // 訂單id
private Long totalPrice;
private Status status;
// address, time etc
// getter, setter
}
public class OrderItem { // order_item 表
private Long id;
private Long orderId; // 訂單id
private Long goodsId;
private Integer count;
private Long price;
// getter setter
}
OrderServiceImpl
類
@Transactional
public void changeGoodsCount(long id, long goodsId, int count) {
Order order = dao.findOrderById(id);
if (order.getStatus() == PAID) {
throw new OrderCannotBeModifiedException(id);
}
List<OrderItem> orderItems = dao.findItemByOrderId(id);
findAndSetCount(orderItems);
order.setTotalPrice(calculateTotalPrice(orderItems));
dao.saveOrUpdate(order);
dao.saveOrUpdate(item);
}
面向過程編程监右,業(yè)務(wù)邏輯Service
層中實(shí)現(xiàn),隨著項(xiàng)目演化异希,這些業(yè)務(wù)邏輯會分散在不同的Service類中秸侣。
實(shí)現(xiàn)二: 基于事務(wù)腳本的實(shí)現(xiàn)
事務(wù)腳本: 通過過程的調(diào)用來組織業(yè)務(wù)邏輯,每個過程處理來自表現(xiàn)層的單個請求。
@Transactional
public void changeGoodsCount(long id, long goodsId, int count) {
OrderStatus orderStatus = DAO.getOrderStatus(id);
if (orderStatus == PAID) {
throw new OrderCannotBeModifiedException(id);
}
DAO.updateGoodsCount(id, command.getGoodsId(), command.getCount());
DAO.updateTotalPrice(id);
}
實(shí)現(xiàn)三: 基于領(lǐng)域?qū)ο髮?shí)現(xiàn)
業(yè)務(wù)表達(dá)邏輯被內(nèi)聚到領(lǐng)域?qū)ο螅?code>Order)中味榛, Order
不再是一個貧血對象椭坚,除了屬性還有行為。
public class Order {
private List<OrderItem> items;
public void changeGoodsCount(long goodsId, int count) {
if (status == PAID) {
throw new OrderCannotBeModifiedException(id);
}
OrderItem item = items.stream().filter(x -> x.getGoodsId() == goodsId).findFirst().get();
item.setCount(count);
this.totalPrice = items.stream().mapToLong(x -> x.getPrice() * x.getCount()).sum();
}
}
OrderApplicationService
提供接口
@Transactional
public void changeGoodsCount(long id, long goodsId, int count) {
Order order = repository.findById(id);
order.changeGoodsCount(goods, count);
repository.save(order);
}
Quote
I find this a curious term because there are few things that are less logical than business logic.
Matin Flowler @ Patterns of Enterprise Application Architecture
DDD簡述
2004年Eric Evans發(fā)表Domain-Driven Design: Tackling Complexity in the Heart of Software搏色,簡稱Evans DDD善茎。領(lǐng)域驅(qū)動設(shè)計(jì)分為兩個階段:
- 以一種領(lǐng)域?qū)<摇⒃O(shè)計(jì)人員频轿、開發(fā)人員都能理解的通用語言作為相互交流的工具垂涯,在交流的過程中發(fā)現(xiàn)領(lǐng)域概念,然后將這些概念設(shè)計(jì)成一個領(lǐng)域模型航邢;
- 由領(lǐng)域模型驅(qū)動軟件設(shè)計(jì)耕赘,用代碼來實(shí)現(xiàn)該領(lǐng)域模型;
它是一套完整而系統(tǒng)的設(shè)計(jì)方法膳殷,嘗試對業(yè)務(wù)復(fù)雜性和技術(shù)復(fù)雜性進(jìn)行分離或者至少降低耦合性操骡,達(dá)到軟件架構(gòu)和業(yè)務(wù)清晰的表達(dá)。
戰(zhàn)略: 子域和界限上下文劃分
領(lǐng)域: 一種邊界赚窃,范圍册招,可以理解為業(yè)務(wù)邊界;
Bounded Context: 界限上下文,定義了解決方案的邊界;
戰(zhàn)術(shù): 經(jīng)典分層架構(gòu)
- UI 層: controller, outer-api勒极;
- 應(yīng)用層: 業(yè)務(wù)邏輯無關(guān)是掰,一個業(yè)務(wù)用例對應(yīng)
ApplicationService
上的一個方法; - 領(lǐng)域?qū)? 核心層, 表達(dá)業(yè)務(wù)辱匿,包含領(lǐng)域模型及領(lǐng)域服務(wù)键痛;
- 基礎(chǔ)設(shè)施層: 為上層提供基礎(chǔ)服務(wù),如持久化服務(wù)等匾七。
用戶界面層(User Interface): 上下游適配器
適配不同的協(xié)議: REST
,RPC
, SOAP
等絮短,負(fù)責(zé)向用戶展現(xiàn)信息以及執(zhí)行用戶命令。更細(xì)的方面來講就是:
- 請求應(yīng)用層以獲取用戶所需要展現(xiàn)的數(shù)據(jù)乐尊;
- 發(fā)送命令給應(yīng)用層要求其執(zhí)行某個用戶命令戚丸;
@PostMapping("/{id}/changeGoods")
public void changeGoodsCount(@PathVariable(name = "id") Long id, @RequestBody @Valid ChangeGoodsCountCommand command) {
orderApplicationService.changeGoodsCount(id, command.getProdcutId(), command.getCount());
}
應(yīng)用層 (ApplicationService): 領(lǐng)域模型的門面
作用: 對外為展現(xiàn)層提供各種應(yīng)用功能(包括查詢或命令)划址,對內(nèi)調(diào)用領(lǐng)域?qū)樱I(lǐng)域?qū)ο蠡蝾I(lǐng)域服務(wù))完成各種業(yè)務(wù)邏輯扔嵌。
原則:
(1). 一個業(yè)務(wù)用例對應(yīng)ApplicationService上的一個業(yè)務(wù)方法,比如修改產(chǎn)品個數(shù): OrderApplicationService.changeGoodsCount()
夺颤;
(2). 與事務(wù)一一對應(yīng)痢缎;
(3). 不包含業(yè)務(wù)邏輯,所有業(yè)務(wù)內(nèi)聚在聚合根中世澜,只用對領(lǐng)域?qū)ο筮M(jìn)行調(diào)用独旷,無需知道領(lǐng)域模型內(nèi)部實(shí)現(xiàn);
(4). 作為領(lǐng)域模型的門面,封裝領(lǐng)域模型的對外提供的功能嵌洼,不應(yīng)處理UI交互或者通信協(xié)議之類的技術(shù)細(xì)節(jié)
領(lǐng)域服務(wù) (DomainService): 多領(lǐng)域模型協(xié)調(diào)者
領(lǐng)域中的一些操作比如涉及到多個領(lǐng)域模型對象不適合歸類到某個具體的領(lǐng)域?qū)ο笾邪钙#I(lǐng)域服務(wù)用來協(xié)調(diào)跨多個對象的操作,無狀態(tài)麻养。 比如在OrderPayByService
中褐啡,代支付需收取1%手續(xù)費(fèi);
public void pay(Account account, Order order) {
account.payby(order.getId(), order.getPrice()); // Account領(lǐng)域模型中實(shí)現(xiàn)1%業(yè)務(wù)邏輯
order.paid();
paymentGateway.pay(account.getId(), order.getPrice);
}
OrderApplicationService
@Transactional
public void orderPayBy(long orderId, long payByAccount) {
Order order = DAO.findOrderById(id);
Account account = DAO.findAccountById(payByAccount);
orderPayByService.pay(account, order);
DAO.saveOrUpdate(order, account);
}
領(lǐng)域(Domain)層: 業(yè)務(wù)邏輯的載體
核心: 識別對象之間的內(nèi)在的關(guān)系鳖昌,構(gòu)建領(lǐng)域?qū)ο蟆?/p>
聚合(Aggreate): 定義了一組具有內(nèi)聚關(guān)系的相關(guān)對象的集合: (1)修改數(shù)據(jù)的單元; (2)業(yè)務(wù)邏輯的載體备畦。由聚合根,實(shí)體及值對象組成许昨;
聚合根(Aggreate Root): 是集合的根節(jié)點(diǎn)懂盐,可被外界獨(dú)立訪問,具有獨(dú)立的生命周期糕档。
實(shí)體(Entity) vs 值對象(Value Object)
聚合根 | 實(shí)體 | 值對象 | |
---|---|---|---|
有無標(biāo)識(id) | 有 | 有 | 無 |
是否只讀 | 否 | 否 | 是 |
是否有生命周期 | 有 | 有 | 無 |
相等條件 | ???? 對象標(biāo)識符 ???? | ???? 對象標(biāo)識符 ???? | ???? 對象屬性 ???? |
示例 | Order | OrderLineItem | Goods, Address |
聚合根的設(shè)計(jì)
- 聚合用來封裝不變性(Invariants) (業(yè)務(wù)規(guī)則)莉恼, 而不是簡單對象從屬關(guān)系
帖子及回復(fù)關(guān)系? 公司與部門之間關(guān)系?
public class Post extends AggregateRoot {
private string title; private List<Reply> replies;
}
- 聚合應(yīng)盡量小
- 聚合之間通過ID關(guān)聯(lián)翼岁,而不是對象
- 聚合內(nèi)強(qiáng)一致性类垫,聚合之間最終一致性
Order領(lǐng)域模型
public class Order { // Order聚合根
private Long id;
private Long totalPrice;
private Status status;
private List<OrderLineItem> items; // 實(shí)體對象 1:N關(guān)系
private Address address; // 值對象
}
public class OrderLineItem { // 訂單商品對象: entity
private Long id; private Long orderId; private Goods goods; private Integer count;
}
public class Goods { // 商品對象: value object, 只讀
private Long id; private String name; private String desc; private Long price;
}
public class Address { // 送貨地址: value object, 只讀
private String country; private String province; private String area; private String detail;
}
聚合根的創(chuàng)建 - Factory模式
- 直接在聚合根中實(shí)現(xiàn)Factory方法,常用于簡單的創(chuàng)建過程
- 獨(dú)立的Factory類琅坡,用于有一定復(fù)雜度的創(chuàng)建過程悉患,或者創(chuàng)建邏輯不適合放在聚合根上
public static Order create(Long id, List<OrderLineItem> items, Address address) {
return new Order(id, items, address);
}
public class OrderFactory {
private OrderIdGenerator idGenerator;
public Order create(List<OrderLineItem> items, Address address) {
long orderId = idGenerator.get();
return Order.create(orderId, items, address);
}
}
基礎(chǔ)設(shè)施層(Infrasture): 聚合根的家
倉儲(Repository
)為聚合根提供查詢及持久化機(jī)制;類似JPA
榆俺,可以看成聚合的容器售躁。
-
Repository
與聚合根一一對應(yīng); -
DAO
直接與表數(shù)據(jù)進(jìn)行交互茴晋,比較薄陪捷。
@Repository
public class OrderRepository {
public Order findById(Long id) { }
public Order save(Order order) { }
}
模型到表映射
- 實(shí)體,聚合根與表一一對應(yīng)
- 值對象通常與聚合根一起诺擅,是表的一列
對于上述的Order
聚合根來說市袖,可以映射到以下2張表中 (t_order_item
和t_order
):
列名 | 類型 | 含義 | 對象映射 |
---|---|---|---|
item_id | bigint | 訂單項(xiàng)目id | OrderLineItem.id |
order_id | bigint | 訂單id, 外鍵 | OrderLineItem.orderId; Order.id |
goods | varchar | 商品信息, json格式 | OrderLineItem.goods |
count | int | 商品數(shù)量 | OrderLineItem.count |
t_order
表
列名 | 類型 | 含義 | 對象映射 |
---|---|---|---|
order_id | ???? bigint???? | 訂單id | Order.id |
user_id | bigint | 用戶id | Order.userId |
total_price | bigint | 訂單總金額 | Order.totalPrice |
address | varchar | 地址,json格式 | Order.address |
status | tinyint | 訂單狀態(tài) | Order.status |
代碼結(jié)構(gòu)
├── OrderDddApplication.java
├── ddd
│ ├── application
│ │ └── OrderApplicationService.java
│ ├── domain
│ │ ├── Address.java
│ │ ├── Goods.java
│ │ ├── Order.java
│ │ └── OrderLineItem.java
│ ├── factory
│ │ ├── OrderFactory.java
│ │ └── RedisOrderIdGenerator.java
│ ├── repository
│ │ ├── OrderLineItemPoDao.java
│ │ ├── OrderPoDao.java
│ │ ├── OrderRepository.java
│ │ └── po
│ └── ui
│ └── OrderController.java
Git: https://github.com/JiangWork/order-ddd-sample
DDD為什么有效
有效的邊界劃分,限定職責(zé)范圍
但劃分邊界是件有難度的事烁涌,隨著對業(yè)務(wù)深入的了解苍碟,劃分也會不同。
總結(jié)
Reference
https://www.cnblogs.com/davenkin/p/ddd-coding-practices.html
http://www.reibang.com/p/6bce48596a69
https://www.cnblogs.com/netfocus/p/3307971.html
https://zhuanlan.zhihu.com/p/32459776
https://martinfowler.com/eaaCatalog/transactionScript.html
https://github.com/JiangWork/order-ddd-sample