不管是因?yàn)橐?guī)模與結(jié)構(gòu)制造的理解力障礙,還是因?yàn)樽兓瘞?lái)的預(yù)測(cè)能力問(wèn)題虚循,最終的決定因素還是因?yàn)樾枨罅晕铩ric Evans 認(rèn)為“很多應(yīng)用程序最主要的復(fù)雜性并不在技術(shù)上,而是來(lái)自領(lǐng)域本身淮椰、用戶的活動(dòng)或業(yè)務(wù)”五慈。因而,領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)關(guān)注的焦點(diǎn)在于領(lǐng)域和領(lǐng)域邏輯主穗,因?yàn)檐浖到y(tǒng)的本質(zhì)其實(shí)是給客戶(用戶)提供具有業(yè)務(wù)價(jià)值的領(lǐng)域功能泻拦。
需求引起的軟件復(fù)雜度
需求分為業(yè)務(wù)需求與質(zhì)量屬性需求,因而需求引起的復(fù)雜度可以分為兩個(gè)方面:技術(shù)復(fù)雜度與業(yè)務(wù)復(fù)雜度忽媒。
技術(shù)復(fù)雜度來(lái)自需求的質(zhì)量屬性争拐,諸如安全、高性能晦雨、高并發(fā)架曹、高可用性等需求,為軟件設(shè)計(jì)帶來(lái)了極大的挑戰(zhàn)金赦,讓人痛苦的是這些因素彼此之間可能又互相矛盾音瓷、互相影響。例如夹抗,系統(tǒng)安全性要求對(duì)訪問(wèn)進(jìn)行控制绳慎,無(wú)論是增加防火墻,還是對(duì)傳遞的消息進(jìn)行加密漠烧,又或者對(duì)訪問(wèn)請(qǐng)求進(jìn)行認(rèn)證和授權(quán)等杏愤,都需要為整個(gè)系統(tǒng)架構(gòu)添加額外的間接層,這不可避免會(huì)對(duì)訪問(wèn)的低延遲產(chǎn)生影響已脓,拖慢了系統(tǒng)的整體性能珊楼。又例如,為了滿足系統(tǒng)的高并發(fā)訪問(wèn)度液,我們需要對(duì)應(yīng)用服務(wù)進(jìn)行物理分解厕宗,通過(guò)橫向增加更多的機(jī)器來(lái)分散訪問(wèn)負(fù)載画舌;同時(shí),還可以將一個(gè)同步的訪問(wèn)請(qǐng)求拆分為多級(jí)步驟的異步請(qǐng)求已慢,再通過(guò)引入消息中間件對(duì)這些請(qǐng)求進(jìn)行整合和分散處理曲聂。這種分離一方面增加了系統(tǒng)架構(gòu)的復(fù)雜性,另一方面也因?yàn)橐肓烁嗟馁Y源佑惠,使得系統(tǒng)的高可用面臨挑戰(zhàn)朋腋,并增加了維護(hù)數(shù)據(jù)一致性的難度。
業(yè)務(wù)復(fù)雜度對(duì)應(yīng)了客戶的業(yè)務(wù)需求膜楷,因而這種復(fù)雜度往往會(huì)隨著需求規(guī)模的增大而增加旭咽。由于需求不可能做到完全獨(dú)立,一旦規(guī)模擴(kuò)大到一定程度赌厅,不僅產(chǎn)生了功能數(shù)量的增加穷绵,還會(huì)因?yàn)楣δ芑ハ嘀g的依賴與影響使得這種復(fù)雜度產(chǎn)生疊加,進(jìn)而影響到整個(gè)系統(tǒng)的質(zhì)量屬性特愿,比如系統(tǒng)的可維護(hù)性與可擴(kuò)展性请垛。在考慮系統(tǒng)的業(yè)務(wù)需求時(shí),還會(huì)因?yàn)闇贤ú粫城⒁椤⒖蛻粜枨蟛磺逦榷喾N局外因素而帶來(lái)的需求變更和修改。如果不能很好地控制這種變更漫拭,則可能會(huì)因?yàn)槎啻涡薷亩鴮?dǎo)致業(yè)務(wù)邏輯糾纏不清亚兄,系統(tǒng)可能開始慢慢腐爛而變得不可維護(hù),最終形成一種如 Brian Foote 和 Joseph Yoder 所說(shuō)的“大泥球”系統(tǒng)采驻。
以電商系統(tǒng)的促銷規(guī)則為例审胚。針對(duì)不同類型的顧客與產(chǎn)品,商家會(huì)提供不同的促銷力度礼旅;促銷的形式多種多樣膳叨,包括贈(zèng)送積分、紅包痘系、優(yōu)惠券菲嘴、禮品;促銷的周期需要支持定制汰翠,既可以是特定的日期龄坪,如雙十一促銷,也可以是節(jié)假日的固定促銷模式复唤。如果我們?cè)谠O(shè)計(jì)時(shí)沒(méi)有充分考慮促銷規(guī)則的復(fù)雜度健田,并處理好促銷規(guī)則與商品、顧客佛纫、賣家與支付乃至于物流妓局、倉(cāng)儲(chǔ)之間的關(guān)系总放,開發(fā)過(guò)程則會(huì)變得踉踉蹌蹌、舉步維艱好爬。
技術(shù)復(fù)雜度與業(yè)務(wù)復(fù)雜度并非完全獨(dú)立局雄,二者混合在一起產(chǎn)生的化合作用更讓系統(tǒng)的復(fù)雜度變得不可預(yù)期,難以掌控抵拘。同時(shí)哎榴,技術(shù)的變化維度與業(yè)務(wù)的變化維度并不相同,產(chǎn)生變化的原因也不一致僵蛛,倘若未能很好地界定二者之間的關(guān)系尚蝌,系統(tǒng)架構(gòu)缺乏清晰邊界,會(huì)變得難以梳理充尉。復(fù)雜度一旦增加飘言,團(tuán)隊(duì)規(guī)模也將隨之?dāng)U大,再揉以嚴(yán)峻的交付周期驼侠、人員流動(dòng)等諸多因素姿鸿,就好似將各種不穩(wěn)定的易燃易爆氣體混合在一個(gè)不可逃逸的密閉容器中一般,隨時(shí)都可能爆炸:
隨著業(yè)務(wù)需求的增加與變化倒源,以及對(duì)質(zhì)量屬性的高標(biāo)準(zhǔn)要求查吊,自然也引起了軟件系統(tǒng)規(guī)模的增大與結(jié)構(gòu)的繁雜仙辟,至于變化,則是軟件開發(fā)繞不開的話題。因此坤邪,當(dāng)我們面對(duì)一個(gè)相對(duì)復(fù)雜的軟件系統(tǒng)時(shí)茸苇,通常面臨的問(wèn)題在于:
問(wèn)題域過(guò)于龐大而復(fù)雜唆垃,使得從問(wèn)題域中尋求解決方案的挑戰(zhàn)增加接校,該問(wèn)題與軟件系統(tǒng)的規(guī)模有關(guān)。
開發(fā)人員將業(yè)務(wù)邏輯的復(fù)雜度與技術(shù)實(shí)現(xiàn)的復(fù)雜度混淆在一起糖耸,該問(wèn)題與軟件系統(tǒng)的結(jié)構(gòu)有關(guān)秘遏。
隨著需求的增長(zhǎng)和變化,無(wú)法控制業(yè)務(wù)復(fù)雜度和技術(shù)復(fù)雜度嘉竟,該問(wèn)題與軟件系統(tǒng)的變化有關(guān)邦危。
針對(duì)這三個(gè)問(wèn)題,領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)都給出了自己的應(yīng)對(duì)措施舍扰。
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的應(yīng)對(duì)措施
隔離業(yè)務(wù)復(fù)雜度與技術(shù)復(fù)雜度
要避免業(yè)務(wù)邏輯的復(fù)雜度與技術(shù)實(shí)現(xiàn)的復(fù)雜度混淆在一起铡俐,首要任務(wù)就是確定業(yè)務(wù)邏輯與技術(shù)實(shí)現(xiàn)的邊界,從而隔離各自的復(fù)雜度妥粟。這種隔離也是題中應(yīng)有之義审丘,畢竟技術(shù)與業(yè)務(wù)的關(guān)注點(diǎn)完全不同。例如勾给,在電商的領(lǐng)域邏輯中滩报,訂單業(yè)務(wù)關(guān)注的業(yè)務(wù)規(guī)則包括驗(yàn)證訂單有效性锅知、計(jì)算訂單總額、提交和審核訂單的流程等脓钾;技術(shù)關(guān)注點(diǎn)則從實(shí)現(xiàn)層面保障這些業(yè)務(wù)能夠正確地完成售睹,包括確保分布式系統(tǒng)之間的數(shù)據(jù)一致性,確保服務(wù)之間通信的正確性等可训。
業(yè)務(wù)邏輯并不關(guān)心技術(shù)是如何實(shí)現(xiàn)的昌妹,無(wú)論采用何種技術(shù),只要業(yè)務(wù)需求不變握截,業(yè)務(wù)規(guī)則就不會(huì)發(fā)生變化飞崖。換言之,在理想狀態(tài)下谨胞,我們應(yīng)該保證業(yè)務(wù)規(guī)則與技術(shù)實(shí)現(xiàn)是正交的固歪。
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)通過(guò)分層架構(gòu)與六邊形架構(gòu)來(lái)確保業(yè)務(wù)邏輯與技術(shù)實(shí)現(xiàn)的隔離。
分層架構(gòu)的關(guān)注點(diǎn)分離
分層架構(gòu)遵循了“關(guān)注點(diǎn)分離”原則胯努,將屬于業(yè)務(wù)邏輯的關(guān)注點(diǎn)放到領(lǐng)域?qū)樱―omain Layer)中牢裳,而將支撐業(yè)務(wù)邏輯的技術(shù)實(shí)現(xiàn)放到基礎(chǔ)設(shè)施層(Infrastructure Layer)中。同時(shí)叶沛,領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)又頗具創(chuàng)見(jiàn)的引入了應(yīng)用層(Application Layer)蒲讯,應(yīng)用層扮演了雙重角色。一方面它作為業(yè)務(wù)邏輯的外觀(Facade)灰署,暴露了能夠體現(xiàn)業(yè)務(wù)用例的應(yīng)用服務(wù)接口伶椿;另一方面它又是業(yè)務(wù)邏輯與技術(shù)實(shí)現(xiàn)的粘合劑,實(shí)現(xiàn)二者之間的協(xié)作氓侧。
下圖展現(xiàn)的就是一個(gè)典型的領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)分層架構(gòu),藍(lán)色區(qū)域的內(nèi)容與業(yè)務(wù)邏輯有關(guān)导狡,灰色區(qū)域的內(nèi)容與技術(shù)實(shí)現(xiàn)有關(guān)约巷,二者涇渭分明,然后匯合在應(yīng)用層旱捧。應(yīng)用層確定了業(yè)務(wù)邏輯與技術(shù)實(shí)現(xiàn)的邊界独郎,通過(guò)直接依賴或者依賴注入(DI,Dependency Injection)的方式將二者結(jié)合起來(lái):
六邊形架構(gòu)的內(nèi)外分離
由 Cockburn 提出的六邊形架構(gòu)則以“內(nèi)外分離”的方式枚赡,更加清晰地勾勒出了業(yè)務(wù)邏輯與技術(shù)實(shí)現(xiàn)的邊界氓癌,且將業(yè)務(wù)邏輯放在了架構(gòu)的核心位置。這種架構(gòu)模式改變了我們觀察系統(tǒng)架構(gòu)的視角:
體現(xiàn)業(yè)務(wù)邏輯的應(yīng)用層與領(lǐng)域?qū)犹幱诹呅渭軜?gòu)的內(nèi)核贫橙,并通過(guò)內(nèi)部的六邊形邊界與基礎(chǔ)設(shè)施的模塊隔離開贪婉。當(dāng)我們?cè)谶M(jìn)行軟件開發(fā)時(shí),只要恪守架構(gòu)上的六邊形邊界卢肃,則不會(huì)讓技術(shù)實(shí)現(xiàn)的復(fù)雜度污染到業(yè)務(wù)邏輯疲迂,保證了領(lǐng)域的整潔才顿。邊界還隔離了變化產(chǎn)生的影響。如果我們?cè)陬I(lǐng)域?qū)踊驊?yīng)用層抽象了技術(shù)實(shí)現(xiàn)的接口尤蒿,再通過(guò)依賴注入將控制的方向倒轉(zhuǎn)郑气,業(yè)務(wù)內(nèi)核就會(huì)變得更加的穩(wěn)定,不會(huì)因?yàn)榧夹g(shù)選型或其他決策的變化而導(dǎo)致領(lǐng)域代碼的修改腰池。
案例:隔離數(shù)據(jù)庫(kù)與緩存的訪問(wèn)
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)建議我們?cè)陬I(lǐng)域?qū)咏①Y源庫(kù)(Repository)的抽象尾组,它的實(shí)現(xiàn)則被放在基礎(chǔ)設(shè)施層,然后采用依賴注入在運(yùn)行時(shí)為業(yè)務(wù)邏輯注入具體的資源庫(kù)實(shí)現(xiàn)示弓。那么讳侨,對(duì)于處于內(nèi)核之外的 Repositories 模塊而言,即使選擇從 MyBatis 遷移到 Sprint Data避乏,領(lǐng)域代碼都不會(huì)受到牽連:
package practiceddd.ecommerce.ordercontext.application;
@Transaction
public class OrderAppService {
? ? @Service
? ? private PlaceOrderService placeOrder;
? ? public void placeOrder(Identity buyerId, List<OrderItem> items, ShippingAddress shipping, BillingAddress billing) {
? ? ? ? try {
? ? ? ? ? ? palceOrder.execute(buyerId, items, shipping, billing);
? ? ? ? } catch (OrderRepositoryException | InvalidOrderException | Exception ex) {
? ? ? ? ? ? ex.printStackTrace();
? ? ? ? ? ? logger.error(ex.getMessage());
? ? ? ? }
? ? }
}
package practiceddd.ecommerce.ordercontext.domain;
public interface OrderRepository {
? ? List<Order> forBuyerId(Identity buyerId);
? ? void add(Order order);
}
public class PlaceOrderService {
? ? @Repository
? ? private OrderRepository orderRepository;
? ? @Service
? ? private OrderValidator orderValidator;? ?
? ? public void execute(Identity buyerId, List<OrderItem> items, ShippingAddress shipping, BillingAddress billing) {
? ? ? ? Order order = Order.create(buyerId, items, shipping, billing);
? ? ? ? if (orderValidator.isValid(order)) {
? ? ? ? ? ? orderRepository.add(order);
? ? ? ? } else {
? ? ? ? ? ? throw new InvalidOrderException(String.format("the order which placed by buyer with %s is invalid.", buyerId));
? ? ? ? }
? ? }
}
package practiceddd.ecommerce.ordercontext.infrastructure.db;
public class OrderMybatisRepository implements OrderRepository {}
public class OrderSprintDataRepository implements OrderRepository {}
對(duì)緩存的處理可以如法炮制爷耀,但它與資源庫(kù)稍有不同之處。資源庫(kù)作為訪問(wèn)領(lǐng)域模型對(duì)象的入口拍皮,其本身提供的增刪改查功能歹叮,在抽象層面上是對(duì)領(lǐng)域資源的訪問(wèn)。因此在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)中铆帽,我們通常將資源庫(kù)的抽象歸屬到領(lǐng)域?qū)优毓ⅰ?duì)緩存的訪問(wèn)則不相同,它的邏輯就是對(duì) key 和 value 的操作爹橱,與具體的領(lǐng)域無(wú)關(guān)萨螺。倘若要為緩存的訪問(wèn)方法定義抽象接口,在分層的歸屬上應(yīng)該屬于應(yīng)用層愧驱,至于實(shí)現(xiàn)則屬于技術(shù)范疇慰技,應(yīng)該放在基礎(chǔ)設(shè)施層:
package practiceddd.ecommerce.ordercontext.application;
@Transaction
public class OrderAppService {
? ? @Repository
? ? private OrderRepository orderRepository;
? ? @Service
? ? private CacheClient<List<Order>> cacheClient;
? ? public List<Order> findBy(Identity buyerId) {
? ? ? ? Optional<List<Order>> cachedOrders = cacheClient.get(buyerId.value());
? ? ? ? if (cachedOrders.isPresent()) {
? ? ? ? ? ? return orders.get();
? ? ? ? }
? ? ? ? List<Order> orders = orderRepository.forBuyerId(buyerId);
? ? ? ? if (!orders.isEmpty()) {
? ? ? ? ? ? cacheClient.put(buyerId.value(), orders);
? ? ? ? }
? ? ? ? return orders;
? ? }
}
package practiceddd.ecommerce.ordercontext.application.cache;
public interface CacheClient<T> {
? ? Optional<T> get(String key);
? ? void put(String key, T value);
}
package practiceddd.ecommerce.ordercontext.infrastructure.cache;
public class RedisCacheClient<T> implements CacheClient<T> {}
本例中對(duì)應(yīng)的代碼結(jié)構(gòu)在分層架構(gòu)中的體現(xiàn)將會(huì)在后續(xù)章節(jié)中深入介紹,敬請(qǐng)期待~
分享交流
我們?yōu)榉窒斫涣鲃?chuàng)建了微信交流群组砚,以方便更有針對(duì)性地討論課程相關(guān)問(wèn)題吻商。入群方式請(qǐng)?zhí)砑有【幍奈⑿盘?hào): Robynn-D,并注明「DDD」糟红,謝謝~
閱讀文章過(guò)程中有任何疑問(wèn)隨時(shí)可以跟其他小伙伴討論艾帐,或者直接向作者提問(wèn)(作者看到后抽空回復(fù))。你的分享不僅幫助他人盆偿,更會(huì)提升自己柒爸。
本文首發(fā):