DDD學(xué)習(xí)筆記4-領(lǐng)域驅(qū)動設(shè)計如何應(yīng)對軟件復(fù)雜度

學(xué)習(xí)資源來自Gitchat上張逸的《領(lǐng)域驅(qū)動設(shè)計實踐》

不管是因為規(guī)模與結(jié)構(gòu)制造的理解力障礙,還是因為變化帶來的預(yù)測能力問題冬耿,最終的決定因素還是因為需求闲擦。需求分為業(yè)務(wù)需求與質(zhì)量屬性需求焕济,因而需求引起的復(fù)雜度可以分為兩個方面:

  1. 質(zhì)量屬性需求帶來的技術(shù)復(fù)雜度
    諸如安全纷妆、高性能、高并發(fā)晴弃、高可用性等需求掩幢,為軟件設(shè)計帶來了極大的挑戰(zhàn),讓人痛苦的是這些因素彼此之間可能又互相矛盾上鞠、互相影響际邻。例如為了滿足系統(tǒng)的高并發(fā)訪問,我們需要對應(yīng)用服務(wù)進行物理分解芍阎,通過橫向增加更多的機器來分散訪問負載世曾;同時,還可以將一個同步的訪問請求拆分為多級步驟的異步請求谴咸,再通過引入消息中間件對這些請求進行整合和分散處理轮听。這種分離一方面增加了系統(tǒng)架構(gòu)的復(fù)雜性,另一方面也因為引入了更多的資源岭佳,使得系統(tǒng)的高可用面臨挑戰(zhàn)血巍,并增加了維護數(shù)據(jù)一致性的難度。

  2. 業(yè)務(wù)需求帶來的業(yè)務(wù)復(fù)雜度
    這種復(fù)雜度往往會隨著需求規(guī)模的增大而增加驼唱。由于需求不可能做到完全獨立藻茂,一旦規(guī)模擴大到一定程度,不僅產(chǎn)生了功能數(shù)量的增加玫恳,還會因為功能互相之間的依賴與影響使得這種復(fù)雜度產(chǎn)生疊加辨赐,進而影響到整個系統(tǒng)的質(zhì)量屬性,比如系統(tǒng)的可維護性與可擴展性京办。另外還會因為溝通不暢掀序、客戶需求不清晰等多種局外因素而帶來的需求變更和修改。如果不能很好地控制這種變更惭婿,則可能會因為多次修改而導(dǎo)致業(yè)務(wù)邏輯糾纏不清不恭,系統(tǒng)可能開始慢慢腐爛而變得不可維護,最終形成一個“大泥球”系統(tǒng)财饥。

領(lǐng)域驅(qū)動設(shè)計通過分層架構(gòu)六邊形架構(gòu)來確保業(yè)務(wù)邏輯與技術(shù)實現(xiàn)的隔離换吧。

分層架構(gòu)

分層架構(gòu)依照“關(guān)注點分離”原則,將屬于業(yè)務(wù)邏輯的關(guān)注點放到領(lǐng)域?qū)樱―omain Layer)中钥星,而將支撐業(yè)務(wù)邏輯的技術(shù)實現(xiàn)放到基礎(chǔ)設(shè)施層(Infrastructure Layer)中沾瓦。同時,領(lǐng)域驅(qū)動設(shè)計又頗具創(chuàng)見的引入了應(yīng)用層(Application Layer)谦炒,應(yīng)用層扮演了雙重角色贯莺。一方面它作為業(yè)務(wù)邏輯的外觀(Facade),暴露了能夠體現(xiàn)業(yè)務(wù)用例的應(yīng)用服務(wù)接口宁改;另一方面它又是業(yè)務(wù)邏輯與技術(shù)實現(xiàn)的粘合劑缕探,實現(xiàn)二者之間的協(xié)作。

六邊形架構(gòu)

六邊形架構(gòu)則以“內(nèi)外分離”的方式还蹲,更加清晰地勾勒出了業(yè)務(wù)邏輯與技術(shù)實現(xiàn)的邊界爹耗,且將業(yè)務(wù)邏輯放在了架構(gòu)的核心位置。


image.png

體現(xiàn)業(yè)務(wù)邏輯的應(yīng)用層與領(lǐng)域?qū)犹幱诹呅渭軜?gòu)的內(nèi)核谜喊,并通過內(nèi)部的六邊形邊界與基礎(chǔ)設(shè)施的模塊隔離開鲸沮。當我們在進行軟件開發(fā)時,只要恪守架構(gòu)上的六邊形邊界锅论,則不會讓技術(shù)實現(xiàn)的復(fù)雜度污染到業(yè)務(wù)邏輯讼溺,保證了領(lǐng)域的整潔。邊界還隔離了變化產(chǎn)生的影響最易。如果我們在領(lǐng)域?qū)踊驊?yīng)用層抽象了技術(shù)實現(xiàn)的接口怒坯,再通過依賴注入將控制的方向倒轉(zhuǎn),業(yè)務(wù)內(nèi)核就會變得更加的穩(wěn)定藻懒,不會因為技術(shù)選型或其他決策的變化而導(dǎo)致領(lǐng)域代碼的修改剔猿。

不了解六邊形架構(gòu)可以看看這篇

案例:隔離數(shù)據(jù)庫與緩存的訪問
領(lǐng)域驅(qū)動設(shè)計建議我們在領(lǐng)域?qū)咏①Y源庫(Repository)的抽象,它的實現(xiàn)則被放在基礎(chǔ)設(shè)施層嬉荆,然后采用依賴注入在運行時為業(yè)務(wù)邏輯注入具體的資源庫實現(xiàn)归敬。那么,對于處于內(nèi)核之外的 Repositories 模塊而言,即使選擇從 MyBatis 遷移到 Spring Data汪茧,領(lǐng)域代碼都不會受到牽連:

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 {}

對緩存的處理可以如法炮制椅亚,但它與資源庫稍有不同之處。資源庫作為訪問領(lǐng)域模型對象的入口舱污,其本身提供的增刪改查功能呀舔,在抽象層面上是對領(lǐng)域資源的訪問。因此在領(lǐng)域驅(qū)動設(shè)計中扩灯,我們通常將資源庫的抽象歸屬到領(lǐng)域?qū)用睦怠彺娴脑L問則不相同,它的邏輯就是對 key 和 value 的操作珠插,與具體的領(lǐng)域無關(guān)惧磺。倘若要為緩存的訪問方法定義抽象接口,在分層的歸屬上應(yīng)該屬于應(yīng)用層捻撑,至于實現(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> {}

限界上下文的分而治之

上面分析緩存訪問接口的歸屬時,我們將接口放在了系統(tǒng)的應(yīng)用層布讹。從層次的職責(zé)來看琳拭,這樣的設(shè)計是合理的,但它卻使得系統(tǒng)的應(yīng)用層變得更加臃腫描验,職責(zé)也變得不夠單一了白嘁。這是分層架構(gòu)與六邊形架構(gòu)的局限所在,因為這兩種架構(gòu)模式僅僅體現(xiàn)了軟件系統(tǒng)的邏輯劃分膘流。倘若我們將一個軟件系統(tǒng)視為一個縱橫交錯的魔方絮缅,前述的邏輯劃分僅僅是一種水平方向的劃分;至于垂直方向的劃分呼股,則是面向垂直業(yè)務(wù)的切割耕魄。這種方式更利于控制軟件系統(tǒng)的規(guī)模,將一個龐大的軟件系統(tǒng)劃分為松散耦合的多個小系統(tǒng)的組合彭谁。

針對前述案例吸奴,我們可以將緩存視為一個獨立的子系統(tǒng),它同樣擁有自己的業(yè)務(wù)邏輯和技術(shù)實現(xiàn)缠局,因而也可以為其建立屬于緩存領(lǐng)域的分層架構(gòu)则奥。在架構(gòu)的宏觀視角,這個緩存子系統(tǒng)與訂單子系統(tǒng)處于同一個抽象層次狭园。這一概念在領(lǐng)域驅(qū)動設(shè)計中读处,被稱之為限界上下文(Bounded Context)。

針對龐大而復(fù)雜的問題域唱矛,限界上下文采用了“分而治之”的思想對問題域進行了分解罚舱,有效地控制了問題域的規(guī)模井辜,進而控制了整個系統(tǒng)的規(guī)模。一旦規(guī)模減小管闷,無論業(yè)務(wù)復(fù)雜度還是技術(shù)復(fù)雜度粥脚,都會得到顯著的降低,在對領(lǐng)域進行分析以及建模時渐北,也能變得更加容易阿逃。限界上下文對整個系統(tǒng)進行了劃分铭拧,在將一個大系統(tǒng)拆分為一個個小系統(tǒng)后赃蛛,我們再利用分層架構(gòu)與六邊形架構(gòu)思想對其進行邏輯分層,以確保業(yè)務(wù)邏輯與技術(shù)實現(xiàn)的隔離搀菩,其設(shè)計會變得更易于把控呕臂,系統(tǒng)的架構(gòu)也會變得更加清晰。

案例:限界上下文幫助架構(gòu)的演進

國際報稅系統(tǒng)是為跨國公司的駐外出差雇員(系統(tǒng)中被稱之為 Assignee)提供方便一體化的稅收信息填報平臺肪跋∑缃客戶是一家會計師事務(wù)所,該事務(wù)所的專員(Admin)通過該平臺可以收集雇員提交的報稅信息州既,然后對這些信息進行稅務(wù)評審谜洽。如果 Admin 評審出信息有問題,則返回給 Assignee 重新修改和填報吴叶。一旦信息確認無誤阐虚,則進行稅收分析和計算,并獲得最終的稅務(wù)報告提交給當?shù)卣约肮蛦T本人蚌卤。

系統(tǒng)主要涉及的功能包括:

  • 駐外出差雇員的薪酬與福利
  • 稅收計劃與合規(guī)評審
  • 對稅收評審的分配管理
  • 稅收策略設(shè)計與評審
  • 對駐外出差雇員的稅收合規(guī)評審
  • 全球的 Visa 服務(wù)

主要涉及的用戶角色包括:

  • Assignee:駐外出差雇員
  • Admin:稅務(wù)專員
  • Client:出差雇員的雇主

在早期的架構(gòu)設(shè)計時实束,架構(gòu)師并沒有對整個系統(tǒng)的問題域進行拆分,而是基于用戶角色對系統(tǒng)進行了簡單粗暴的劃分逊彭,分為了兩個相對獨立的子系統(tǒng):Frond End 與 Office End咸灿,這兩個子系統(tǒng)單獨部署,分別面向 Assignee 與 Admin侮叮。系統(tǒng)之間的集成則通過消息和 Web Service 進行通信避矢。兩個子系統(tǒng)的開發(fā)分屬不同的團隊,F(xiàn)rond End 由美國的團隊負責(zé)開發(fā)與維護囊榜,而 Office End 則由印度的團隊負責(zé)审胸。整個架構(gòu)如下圖所示:


image.png

采用這種架構(gòu)面臨的問題如下:

  • 龐大的代碼庫:整個 Front End 和 Office End 都沒有做物理分解,隨著需求的增多锦聊,代碼庫會變得格外龐大歹嘹。
  • 分散的邏輯:系統(tǒng)分解的邊界是不合理的,沒有按照業(yè)務(wù)分解孔庭,而是按照用戶的角色進行分解尺上,因而導(dǎo)致大量相似的邏輯分散在兩個不同的子系統(tǒng)中材蛛。
  • 重復(fù)的數(shù)據(jù):兩個子系統(tǒng)中存在業(yè)務(wù)重疊,因而也導(dǎo)致了部分數(shù)據(jù)的重復(fù)怎抛。
  • 復(fù)雜的集成:Front End 與 Office End 因為某些相關(guān)的業(yè)務(wù)需要彼此通信卑吭,這種集成關(guān)系是雙向的,且由兩個不同的團隊開發(fā)马绝,導(dǎo)致集成的接口混亂豆赏,消息協(xié)議多樣化。
  • 知識未形成共享:兩個團隊完全獨立開發(fā)富稻,沒有掌握端對端的整體流程掷邦,團隊之間沒有形成知識的共享。
  • 無法應(yīng)對需求變化:新增需求包括對國際旅游椭赋、Visa 的支持抚岗,現(xiàn)有系統(tǒng)的架構(gòu)無法很好地支持這些變化。

采用領(lǐng)域驅(qū)動設(shè)計哪怔,我們將架構(gòu)的主要關(guān)注點放在了“領(lǐng)域”宣蔚,與客戶進行了充分的需求溝通和交流。通過分析已有系統(tǒng)的問題域认境,結(jié)合客戶提出的新需求胚委,對整個問題域進行了梳理,并利用限界上下文對問題域進行了分解叉信,獲得了如下限界上下文:

  • Account Management:管理用戶的身份與配置信息亩冬;
  • Calendar Management:管理用戶的日程與旅行足跡。

之后茉盏,客戶希望能改進需求鉴未,做到全球范圍內(nèi)的工作指派與管理,目的在于提高公司的運營效率鸠姨。通過對領(lǐng)域的分析铜秆,我們又識別出兩個限界上下文。在原有的系統(tǒng)架構(gòu)中讶迁,這兩個限界上下文同時處于 Front End 與 Office End 之中连茧,屬于重復(fù)開發(fā)的業(yè)務(wù)邏輯:

  • Work Record Management:實現(xiàn)工作的分配與任務(wù)的跟蹤;
  • File Sharing:目的是實現(xiàn)客戶與會計師事務(wù)所之間的文件交換巍糯。

隨著我們對領(lǐng)域知識的逐漸深入理解與分析啸驯,又隨之識別出如下限界上下文:

  • Consent:管理合法的遵守法規(guī)的狀態(tài);
  • Notification:管理系統(tǒng)與客戶之間的交流祟峦;
  • Questionnaire:對問卷調(diào)查的數(shù)據(jù)收集罚斗。

Questionnaire:對問卷調(diào)查的數(shù)據(jù)收集。
這個領(lǐng)域分析的過程實際上就是通過對領(lǐng)域的分析而引入限界上下文對問題域進行分解宅楞,通過降低規(guī)模的方式來降低問題域的復(fù)雜度针姿;同時袱吆,通過為模型確定清晰的邊界,使得系統(tǒng)的結(jié)構(gòu)變得更加的清晰距淫,從而保證了領(lǐng)域邏輯的一致性绞绒。一旦確定了清晰的領(lǐng)域模型,就能夠幫助我們更加容易地發(fā)現(xiàn)系統(tǒng)的可重用點與可擴展點榕暇,并遵循“高內(nèi)聚蓬衡、松耦合”的原則對系統(tǒng)職責(zé)進行合理分配,再輔以分層架構(gòu)以劃分邏輯邊界彤枢,如下圖所示:


image.png

我們將識別出來的限界上下文定義為微服務(wù)狰晚,并對外公開 REST 服務(wù)接口。UI Applications 是一個薄薄的展現(xiàn)層堂污,它會調(diào)用后端的 RESTful 服務(wù)家肯,也使得服務(wù)在保證接口不變的前提下能夠單獨演化龄砰。每個服務(wù)都是獨立的盟猖,可以單獨部署,因而可以針對服務(wù)建立單獨的代碼庫和對應(yīng)的特性團隊(Feature Team)换棚。服務(wù)的重用性和可擴展性也有了更好的保障式镐,服務(wù)與 UI 之間的集成變得更簡單,整個架構(gòu)會更加清晰固蚤。

個人寄語:學(xué)到現(xiàn)階段娘汞,用自己的白話做點總結(jié):領(lǐng)域驅(qū)動設(shè)計的初衷是解決軟件復(fù)雜度問題,復(fù)雜度主要由規(guī)模夕玩、結(jié)構(gòu)你弦、變化造成,其中變化是無處不在的是整個開發(fā)過程中都要注意并尋求一個成本與好處的平衡燎孟;識別限界上下文分拆分微服務(wù)分而治之可以解決規(guī)模問題禽作;分層架構(gòu)+六邊形架構(gòu)解決拆分以后各自的結(jié)構(gòu)問題,分層的意思就是領(lǐng)域?qū)用嫦驑I(yè)務(wù)揩页,基礎(chǔ)設(shè)施層面向技術(shù)旷偿,應(yīng)用層粘合二者并提供接口。六邊形架構(gòu)主要強調(diào)通過接口與適配器的方式恪守對領(lǐng)域與基礎(chǔ)設(shè)施的隔離爆侣。
但是對第一篇描述的領(lǐng)域驅(qū)動設(shè)計的過程還不是很理解透徹萍程,強調(diào)建模的時候建立統(tǒng)一語言,然后識別限界上下午進行拆分我都能理解兔仰,但是分層架構(gòu)和六邊形架構(gòu)怎么會用在戰(zhàn)略階段呢茫负?不應(yīng)該主要是戰(zhàn)術(shù)階段嗎?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末乎赴,一起剝皮案震驚了整個濱河市忍法,隨后出現(xiàn)的幾起案子置吓,更是在濱河造成了極大的恐慌,老刑警劉巖缔赠,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件衍锚,死亡現(xiàn)場離奇詭異,居然都是意外死亡嗤堰,警方通過查閱死者的電腦和手機戴质,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來踢匣,“玉大人告匠,你說我怎么就攤上這事±牖#” “怎么了后专?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長输莺。 經(jīng)常有香客問我戚哎,道長,這世上最難降的妖魔是什么嫂用? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任型凳,我火速辦了婚禮,結(jié)果婚禮上嘱函,老公的妹妹穿的比我還像新娘甘畅。我一直安慰自己,他們只是感情好往弓,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布疏唾。 她就那樣靜靜地躺著,像睡著了一般函似。 火紅的嫁衣襯著肌膚如雪槐脏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天缴淋,我揣著相機與錄音准给,去河邊找鬼。 笑死重抖,一個胖子當著我的面吹牛露氮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播钟沛,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼畔规,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了恨统?” 一聲冷哼從身側(cè)響起叁扫,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤三妈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后莫绣,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體畴蒲,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年对室,在試婚紗的時候發(fā)現(xiàn)自己被綠了模燥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡掩宜,死狀恐怖蔫骂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情牺汤,我是刑警寧澤辽旋,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站檐迟,受9級特大地震影響补胚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜锅减,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一糖儡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧怔匣,春花似錦、人聲如沸桦沉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽纯露。三九已至剿骨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間埠褪,已是汗流浹背浓利。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留钞速,地道東北人贷掖。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像渴语,于是被迫代替她去往敵國和親苹威。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

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