DDD - 領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)對(duì)軟件復(fù)雜度的應(yīng)對(duì)(上)

不管是因?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ā):

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市事扭,隨后出現(xiàn)的幾起案子捎稚,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件阳藻,死亡現(xiàn)場(chǎng)離奇詭異晰奖,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)腥泥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門匾南,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蛔外,你說(shuō)我怎么就攤上這事蛆楞。” “怎么了夹厌?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵豹爹,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我矛纹,道長(zhǎng)臂聋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任或南,我火速辦了婚禮孩等,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘采够。我一直安慰自己肄方,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布蹬癌。 她就那樣靜靜地躺著权她,像睡著了一般。 火紅的嫁衣襯著肌膚如雪逝薪。 梳的紋絲不亂的頭發(fā)上隅要,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音董济,去河邊找鬼步清。 笑死,一個(gè)胖子當(dāng)著我的面吹牛感局,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播暂衡,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼询微,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了狂巢?” 一聲冷哼從身側(cè)響起撑毛,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后藻雌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體雌续,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年胯杭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了驯杜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡做个,死狀恐怖鸽心,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情居暖,我是刑警寧澤顽频,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站太闺,受9級(jí)特大地震影響糯景,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜省骂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一蟀淮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧冀宴,春花似錦灭贷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至逃延,卻和暖如春览妖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背揽祥。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工讽膏, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拄丰。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓府树,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親料按。 傳聞我的和親對(duì)象是個(gè)殘疾皇子奄侠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容