http://tommwq.tech/blog/2020/12/13/275
這篇文章是閱讀Thought Works中國知乎專欄 DDD-領域驅動設計 時記錄的筆記。
架構設計是以組件化爲手段,實現關注點分離,從而降低局部性複雜度的一項軟件設計工作。設計首先是要解決問題的複雜度,其次是要建立團隊協作溝通的共識。達成這兩點,根本目的在於讓系統能夠更快地響應外界業務的變化,並且使得系統能夠持續演進。
類似工業總線(ESB)這樣的組件化其實是面向技術的,希望通過技術平臺的靈活性來解決業務變化的多樣性。雖然短時間能夠收到一定的成效,長期看必然把自身做成瓶頸,因爲所有業務的變化最後都堆積到了這個技術組件來解決。這也回答了爲什麼實施了傳統SOA架構的企業最後都發現響應速度其實並沒有提升起來。
面向業務變化而架構就要求首先理解業務的核心問題,即有針對性地進行關注點分離來找到相對內聚的業務活動形成子問題域。子問題域內部是相對穩定的,即未來的變化頻率不會很高,而子問題邊界是很容易變化的,比如在一個物流系統中:計算貨物從A地到B地的路徑是相對固定的,計算包裹的體積及歸類也是相對固定的,但根據包裹的體積優化路徑卻經常會根據業務條件而變化。
業務對象可以採用實體(entity)或值對象(value object)表達。二者的區別在於,實體是有狀態的,擁有唯一標識和生命週期。
賬戶管理領域的“轉賬”業務行爲,可以使用領域服務(domain service)封裝。轉賬涉及兩個賬戶,因此不適合作爲賬戶的行爲。同時轉賬也不適合作爲實體。
應用服務(application service)是領域模型的門面(參考GoF facade模式)。協議適配層(如controller)將使用某種協議封裝的請求轉換爲對應用服務的請求,委託應用服務傳遞給領域模型。
聚合根(aggregate root)的意義在於維護業務規則一致性。比如下面的代碼
order.changeProductCount(productId, count); order.updateTotalPrice(); price = order.getTotalPrice();
訂單種品項(order item)和總價是密切相關的,品項的變化導致總價發生變化。在這段代碼裏,維護二者的一致性的職責由客戶端代碼承擔。如果客戶端代碼發生錯誤,業務約束一致性將遭到破壞。因此應當將職責分配給聚合根Order。
public class Order { public void changeProductCount(int productId, int count) { if (isPaid()) { throw new PaidOrderCannotBeModifiedException(id); } OrderItem item = retrieveItem(productId); item.updateCount(item); updateTotalPrice(); } }
聚合根一定是實體,但反之實體未必是聚合根。Listing 1: DDD處理業務流程的典型例子
public class OrderApplicationService { public void updateProductCount(UpdateProductCountCommand command) { String orderId = command.getOrderId(); String productId = command.getProductId(); int count = command.getCount(); Order order = orderRepository.byId(orderId); order.updateProductCount(productId, count); orderRepository.save(order); } }
領域事件通常隨着聚合根狀態的變化而產生。領域事件本身是不變的,並且只應當包含事件相關的上下文信息,不應包含聚合根的全部狀態。
事件驅動架構有兩種風格:事件通知、事件攜帶狀態轉移(Event-Carried State Transfer)。在事件通知風格中,事件包含的信息較少,通常只有聚合根ID。接收方需要通過額外的API獲得事件上下文。這種方式實現起來較爲簡單,但增加了接收方和發送方的耦合度。事件攜帶狀態轉移風格則要求事件攜帶全部的必要信息,接受方通過事件就可以知道發生了什麼。