ENode框架Conference案例分析系列之 - 架構設計


Conference架構概述

先貼一下Conference案例的在線地址,UI因為完全拿了微軟的實現,所以都是英文的,以后我有空再改為中文的。

前一篇文章介紹了Conference案例的上下文划分和領域模型的設計思路,本文想介紹一下Conference案例的架構設計。我做Conference案例的出發點是為了給大家展示如何使用ENode框架來開發DDD+CQRS+ES+EDA風格的應用程序。所以,Conference案例的架構自然就是使用這個架構了。下面我展開來說一下:

  • DDD:是軟件設計的一種方式,出發點是通過領域模型封裝業務邏輯和業務規則,解決領域內的復雜業務問題;
  • CQRS+ES:命令查詢分離的架構,通過將命令查詢分離,做到處理業務邏輯的部分和查詢的部分可以分離,方便兩部分可以各自發展,不受對方約束;CQRS的實現方式主要有兩種:1)共享存儲的CQRS,即背后的數據庫存儲是一份,數據是一份,只是在代碼、架構層面做到命令和查詢邏輯的分離。這種方式很好的利用了CQRS的思想,同時也不會有數據一致性的問題,因為是共享存儲的,所以CQ兩端不需要有消息通信,大部分應用程序用這種方式即可。2)存儲分離的CQRS,這種方式主要用ES(Event Sourcing,事件溯源)技術來徹底實現CQ兩端的完全分離,這種架構比較復雜。C端不存儲對象的最新狀態,而是存儲對象產生的所有事件;讓我們要還原一個對象時,通過ES的方式來還原。然后Q端通過訂閱C端產生的事件來更新讀庫。這種架構是一種EDA的架構,CQ兩端需要通過事件來進行聯系,所以是一種面向最終一致性思路的架構。那這種方式有何好處呢?它和前面我說的第一種方式的CQRS架構,我覺得主要的好處是,我們可以設計一套框架,幫我們從架構層面解決並發問題、消息的冪等處理問題;同時結合in-memory, group commit等技術,還能大大提高系統的吞吐量以及抵御高峰的能力(因為消息可以堆積);從而可以讓開發人員不用關心技術問題,專心實現業務邏輯和設計業務流程即可。而共享存儲的CQRS架構,是需要我們每次Command修改完聚合根之后,需要主動保存(可能通過ORM實現)聚合根的狀態的。總之兩種實現方式各有優缺點,關於CQRS架構的更多介紹,我博客里已經寫過很多文章了,有興趣的朋友可以進一步看看。
  • EDA:這是一種事件驅動的架構,他和SOA架構屬於同一個層次;EDA是事件驅動的思想,即B訂閱A產生的消息來進行主動響應的思路;而SOA是一種面向服務然后通過服務之間相互調用(RPC)的思想;這是兩種不同的架構風格,我們在不同的場景會使用不同的方式。ENode實現的是EDA架構,面向的是最終一致性。ENode在很多方面都體現出了EDA的思想。比如:
    1. ENode規定,一個Command不能同時修改兩個聚合根,必須通過事件驅動的方式,先修改一個聚合根,然后該聚合根產生事件,然后另一個聚合根訂閱響應事件,再修改自己的狀態,從而實現兩個聚合根之間的交互。這個思路 背后的原則是:聚合內強一致性、聚合之間最終一致性。
    2. CQRS兩端,也是最終一致性,通過事件來同步數據。C端產生的事件通過MQ被Q端訂閱,然后Q端更新自己的讀庫,從而實現數據的最終一致性;
    3. 兩個BC(Bounded Context,上下文)之間的數據傳遞,也是基於消息驅動的思想。比如支付上下文在完成支付后,會產生一個消息,然后訂單上下文訂閱響應該消息,實現上下文之間的交互。

ENode架構圖

也許有人沒看過ENode架構圖,呵呵。我這里再貼一下,誰如果要進一步了解ENode的架構設計,可以看我博客中的其他關於ENode框架的介紹文章。

Conference項目結構介紹

 

 

 

前篇文章中我們了解到,Conference案例共有三個上下文,分別是:會議管理(ConferenceManagement)、訂單處理(Registration)、訂單支付(Payment)。關於這個案例,微軟的實現和我的實現有所不同,但上下文的划分是一致的。微軟的實現中,三個上下文用的技術架構是不同的。

  • ConferenceManagement,由於只是后台管理系統,業務邏輯相對不是很復雜,所以,采用的是普通的三層結構;
  • Registration,由於是整個系統的整個Conference系統的核心,業務邏輯和流程都比較負責,所以使用的是CQRS+ES的架構;
  • Payment,由於Conference這個項目只是一個案例,所以其實沒有真正實現支付的功能,只是簡單示范了一下功能,所以這個上下文的業務邏輯也不太復雜。但是由於訪問量也比較大,每個訂單都會需要支付,所以也是采用的CQRS的架構,但是因為業務邏輯不復雜(不需要在存儲層面也分離),所以沒有采用ES技術。Payment聚合根里產生事件,最后Repository保存聚合根的最新狀態,然后通過事件總線發布事件,通知Registration上下文訂單支付結果。

上面我簡要分析了一下微軟的實現。我覺得微軟的實現還是非常有參考價值的,因為它充分展示了DDD中不同的BC可以采用不同的技術架構實現,然后通過EDA的整體架構,來實現3個BC之間的數據交互。非常棒。

下面我說一下ENode實現的Conference案例。

前面說過,本案例主要是為了展示ENode框架的使用,所以我給這3個BC都是使用ENode框架實現,所以每個BC都是采用的DDD+CQRS+ES的技術架構。由於有ENode框架的支持,所以代碼實現還不算復雜,可以讓開發只需要專注於業務邏輯的實現即可,不需要關心消息傳遞,消息不丟失,消息冪等處理,並發問題,C端數據持久化等技術問題。這些技術問題如果沒有框架支持,要由應用開發人員自己實現,是很有難度的。通過這個案例實踐下來,基本可以證明ENode框架是可以被使用來開發出一個可實際使用的項目的,這點我目前很有信心。

采用ENode框架開發一個BC的實現的時候,我們一般需要定義以下的一些工程: 

  • Commands,定義所有的命令;
  • CommandHandlers,定義所有的CommandHandlers;
  • Domain,就是對應DDD領域層;
  • ReadModel,表示CQRS的Query Side的實現,主要包括query service的實現,以及event handler用戶更新讀庫;
  • Messages,定義了當前BC所有對外的消息,其他BC可以訂閱這些消息;消息都是DTO。
  • MessagePublishers,該項目的職責是訂閱當前BC內部的Domain Event,然后轉換為Message,然后把Message發布出去,從而實現通知到其他的BC。之所以要定義出Message這種DTO,是因為,我認為DDD中的Domain Event最好不要跨BC,因為Domain Event是屬於當前Domain的東西,Domain Event中可能會包括當前Domain里定義的各種值對象;如果直接發布到其他BC,就會導致BC的邊界不清。
  • Repositories,這種工程的作用就是實現Domain里可能定義的倉儲接口。為何使用ENode框架后,還需要定義倉儲接口?因為有些情況下,我們需要有一些二級索引的檢查,比如創建會議時,我們要判斷會議的某個屬性是唯一的,但是ES不支持二級索引,而我們又不能通過查詢讀庫來判斷唯一性。所以我們需要在C端設計一些存儲聚合根索引信息的倉儲,用來支持二級索引。左邊的圖里我把項目名稱命名為Repositories.Dapper,說明這個倉儲是用Dapper來實現的。如果我們用EF來實現,那可以命名為Repositories.EF。
  • ProcessManagers,這種項目是用來承載CQRS架構中的Saga的,即流程管理器。Saga的作用是對業務流程進行建模。Saga的原理也是事件驅動,一個Saga會響應事件,然后發送命令。通過事件+命令串聯的方式實現事件驅動架構的業務流程。Saga(ProcessManager)是無狀態的,所有的狀態應該都在聚合根里。這點我和微軟的Saga的設計也是不同的,有興趣的朋友可以看一下微軟的實現。

另外,上面介紹了單個BC內部可能出現的項目,這些項目是我通過做這個案例后總結出來的覺得可以給開發者做參考的相互划分方式。大家如果覺得這樣的方式不好,可以自己決定如何划分。通過上面的划分,我們的頂層Web項目只需要依賴簡單的Commands,ReadModel這兩個項目,就可以實現命令的發送和讀庫數據的查詢了。而BC之間的交互,另一個BC只需要依賴當前BC的Messages項目就行,做到了最小的依賴。

最后介紹一下剩余的幾個頂層項目:

Conference案例有兩個Web項目,分別為用戶提供Conference的后台管理和前台訂單預定的Web界面。這個不需要多解釋了應該。然后,還有3個ProcessorHost項目。這3個項目分別是3個BC負責處理后台業務邏輯的頂層宿主項目。它們只需要用控制台應用或者Windows服務的方式啟動即可(案例里的實現同時支持這兩種方式的啟動,會自動識別當前的應用程序類型)。這3個后台服務,它們都是從EQueue訂閱消息,然后處理消息的方式,實現自己的功能。所以它們的唯一數據來源就是EQueue。那EQueue消息隊列的服務端是哪個呢?就是最下面的MessageBroker,這個項目承載了整個系統的消息中心,也就是EQueue的Broker。所有的消息都會發送給MessageBroker,然后相關的ProcessorHost訂閱相關的Topic,實現消息的消費。

結尾

下一篇文章打算從代碼的角度,以創建一個會議為例,從前台Controller到最后更新讀庫的整個代碼鏈路簡單介紹一下,方便讀者能對實現某個功能要寫哪些代碼先有一個清晰總體的認識。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM