經典的ddd的告訴我們如果一個領域概念是一個跨多個聚合的動作,比如轉帳,那麼就應該用領域服務來實作這樣的業務概念。領域服務的輸入和輸出參數都是聚合根,領域服務内部按照業務邏輯規定的執行順序,按照面向過程的方式,逐個調用相關聚合根的相關方法完成整個業務操作。這種方式的優點是:1)清晰的表達和封裝了業務邏輯;2)代碼清晰,容易了解,代碼可讀性強;缺點:1)基本的oo思想告訴我們,對象與對象之間應該是通過發送消息和接收消息的方式來通信的。但是通過前面這種方式,對象之間不再像我們想的那樣會通過發送消息和接收消息來互相協作了,而都是被動的被一個第三方協調者服務對象來統一協調,這其實是一種面向過程的思維,而非基于消息通信的面向對象思維;2)這種通過領域服務的方式隻強調了對象的個體行為(即對象隻有改變自己狀态的行為),而沒有注重對象之間的互動行為,對象應該還有能力發送消息給其他對象或者響應其他對象發送的消息;
那麼為了能讓對象之間有互動行為,能互相發送消息和接受消息,該如何做呢?一般來說有兩種方法:1)方法調用,a對象調用b對象的方法,這種方法最常用最直接,但是缺點是會導緻a依賴于b。這個問題我們往往會通過面向接口程式設計在一定程度上消除這種依賴關系;2)事件消息模式,生産者-消費者模式,即采用觸發事件響應事件的模式;通過這種方式,a對象就不是直接調用b對象的方法了,而是觸發一個事件,然後b對象去響應這個事件,或者如果采用消息總線的方式的話,就是a對象通知消息總線釋出某個事件,然後消息總線釋出這個事件,然後b對象響應這個事件。通過這樣的通信方式,巧妙的将a,b兩個對象的關系進行了解耦,本來a直接依賴b(a->b)的情況變成了a與b互相獨立,變成了這種方式:a->消息<-b。
cqrs & event sourcing架構采用的通信方式。首先,在這種方式下,聚合與聚合之間的關系通過id表示,而不是通過對象引用的方式來表達對象關聯。這就讓我們不能直接通過簡單友善的對象導航的方式定位到關聯對象并調用其方法了。那麼這種方式下,對象之間如何通信呢?就是采用上面提到的基于事件消息的通信模式。cqrs架構中command端的大緻執行流程是:ui發送command到commandservice,commandservice根據傳入的command調用相關的command handler,command handler擷取相關的聚合執行相關方法,聚合執行方法後觸發事件,消息總線釋出該事件到外部,事件響應者(event handler)響應事件。關于最後一步的實作還有一些目前不确定的方案,目前采用一般兩種方式:1)event handler生成一個新的command然後發送給command service;2)event handler直接取出目标聚合根,然後調用其方法;另外,一般兩個聚合之間采用異步的方式通信,是以我們會內建nservicebus等異步消息通信架構實作消息的傳遞;
關于對象之間通過直接方法調用來互動,和通過發送消息來互動的一點思考:
1)對象a直接調用對象b的某個方法,實作互動;直接方法調用本質上也是屬于一種特殊的發送與接受消息,它把發送消息和接收消息合并為一個動作完成;方法調用方和被調用方被緊密耦合在一起;因為發送消息和接收消息是在一個動作内完成,是以無法做到消息的異步發送和接收;
2)對象a生成消息->将消息通知給一個消息處理器(如nservicebus,eventbus之類)->消息處理器通過同步或異步的方式将消息傳遞給接收者;這種方式是通過将消息發送和消息接收拆分為兩個過程,通過一個中間者來控制消息是同步還是異步發送;在消息通信的靈活性方面比較有優勢,但是也帶來了一定的複雜度。但是複雜度一般可以由架構封裝,消息的發送方和接收方仍然可以做到比較簡單;
最後,在說一下關于事件與消息的關系的論述:
事件和消息可以說是從不同方面描述的同一個東西,消息是事件發生後産物,消息發送必須有發送事件發生才能實作。每次事件隻發送一次消息,事件和消息是一對一的。