有界上下文
還好,確實有其他方案可供選擇。我們可以讓領域驅動設計(DDD)來幫忙。只要將領域拆分成有界上下文,就可以利用其優勢來完成工作。在一個超簡化的系統模型中,我們可以定義如下的有界上下文:
租賃協議客戶 車輛。
租賃專員:使用系統來辦理租賃協議租賃中介:通常情況下客戶并不直接租車,而是通過代理人來租車
所有這些實體都出現在租賃協議中,但又自成有界上下文。因此,在最初設定消息格式的時候,我們可以根據正在進行的操作來引入主要的有界上下文。在本例中,初始的消息內容設計應該是下面的樣子:
{"Status":"Closed","RentalAgreementID":1234,"CustomerID":8965,"VehicleID":98263,"RentalAgent":24352,"Broker":6723}
有了這樣的消息結構,我們總算可以在不打破OCP原則和“迪米特法則”的情況下實現CustomerThanking微服務了。更不用說,我們還可以應用這樣的消息結構來應付將來新的業務需要。比如:
計算租賃專員傭金。 提供租賃代理的經濟信息。 車輛維護事宜。 諸如此類。
最重要的是,通過這樣設計的消息內容,我們打開了一扇門,可以添加大量與這個事件相關的新功能(這些功能我們不可能在一開始就想清楚),而不需要改變現存的代碼。
四、事件和消息數據
消息是如何構成的?消息的必要內容是什么?
為了回答這些問題,我們首先需要了解消息類型和目標的差異。首先,消息表征了事件,而事件即事實,代表已經發生的事情。我們在既往的時間定義了事件,這些事件代表已經發生過的事情,我們無法改變既成事實。我們在Topic中存儲事件的時候,以事件為Topic命名。為了更好的理解事件,我推薦Jonas Bonér的演講。
事件的目標又是什么呢?我所知道的兩種類型事件的目標是這樣的:
表征事實。
建立數據流。
用來表征事實的事件,用于如前文所述的系統之中。其主要的目標就是傳達某事已經發生的消息,并提供與這一事情相關的有用的數據。我們嘗試不多不少地僅提供所需的信息,比如僅提供與事件相關的有界上下文實體的ID。
用于為系統構建數據流的事件,一般是應用于大數據系統中。在大數據系統中充斥著橫跨系統范圍內大量的信息,信息在傳遞過程中會經過多次轉換。可以讓事件附加我們所能提供的盡量多的信息,以便減少在信息轉換所帶來的額外的成本。
最小化消息信息
為什么在表征事實的時候,最小化信息量是如此的重要?我們來看一個例子。
假設我們需要為系統添加一個新的功能:一個Recommendations的微服務,來向客戶發送一封郵件,并根據客戶的個人資料推薦一些優惠信息。簡化起見,假定我們只需要客戶的年齡信息來進行推薦。為了不產生額外的開銷,我們可以選擇不讀數據庫,而是把年齡數據存儲在消息中(此處先不考慮OCP原則,我們只是在分析在消息中添加新數據產生的影響)。
{"Status":"Closed","RentalAgreementID":5678,"CustomerID":8965,"VehicleID":98263,"RentalAgent":24352,"Broker":6723,"CustomerAge":27}
系統示意圖如下:

看起來我們有了一個良好解耦的系統,這很好。但是,事實真的是這樣么?假定我們需要修改推薦算法,把用戶駕照的發證信息納入考慮。很簡單,只需要把這個字段加到JSON里即可。但在本例中,我們的微服務并沒有實際解耦,因此,每次我們需要一個新的字段的時候,就需要同時修改訂閱者和發布者。我們的微服務是緊耦合的,并且是以一種很丑陋的方式,直到我們需要對系統做出變更的時候,我們才會意識到這一點。
我們可能會這樣想,是不是可以把每個可能的字段都加到消息數據中,這樣問題就解決了,我們再也不需要修改發布者和訂閱者了。但系統是隨時間進化的,總會有一個時刻,需要在模型中添加新的字段,同時需要修改每一個微服務。因此這條路也行不通。
我們所能采取的最好的方案是,在消息中提供足夠的信息,既滿足我們最初的設計中所考慮到的用例場景的需要,同時也要讓這些信息對我們可能尚未考慮到的新的服務是可用的。比如把主要的有界上下文的實體的ID引入 進來,這些實體要么是我們與事件通信的事實的一個組成部分,要么與之相關。新的微服務需要涵蓋很多的實體,當然,這會打破“迪米特法則”,但這是必要的妥協。軟件架構的要義即在于此:做出恰當的權衡以達成盡量好的系統。遵循開閉原則的能力是如此的重要而有意義,從某種程度上講,我們可以為此不惜違背“迪米特法則”。
五、總結
1、事件驅動系統給了我們很好的機會來在架構層級應用開閉原則。我們可以重用已有的代碼,并且在未知的方向上實現功能的擴展。
2、然而,需要謹慎的設計事件的內容,同時警惕糟糕的設計可能引入的耦合的可能性。
3、要根據系統的目標來指導架構設計,為某一目標設計某種適用的架構(如,為大數據系統設計的流式數據)很可能對于另一目標來說是糟糕的設計(不適用于表征事實發生的事件驅動系統)。
4、領域驅動設計的有界上下文可以為事件內容的設計提供一些指導。
5、架構是關于決策和權衡,最大化應用開閉原則很可能意味著對“迪米特法則”的最小化遵循,必須謹小慎微地尋找一個平衡點。