避免CRUD思維洩漏領域邏輯 - mscharhag
許多軟體架構試圖將域邏輯與應用程式的其他部分分開。為了遵循這種做法,我們總是需要知道什麼是領域邏輯,什麼不是。不幸的是,這並不總是那麼容易分開。如果我們做出錯誤的決定,領域邏輯很容易洩漏到其他元件和層中。
我們將通過檢視使用六邊形應用程式架構的示例來解決這個問題。
假設一個商店系統將新訂單釋出到訊息系統(如 Kafka)。我們的產品負責人現在告訴我們,我們必須監聽這些訂單事件並將相應的訂單儲存在資料庫中。
使用六邊形體系結構,在介面卡內實現與訊息傳遞系統的整合。所以,我們從一個簡單的介面卡實現開始,它監聽 Kafka 事件:
@AllArgsConstructor public class KafkaAdapter { private final SaveOrderUseCase saveOrderUseCase; @KafkaListener(topic = ...) public void onNewOrderEvent(NewOrderKafkaEvent event) { Order order = event.getOrder(); saveOrderUseCase.saveOrder(order); } } |
的@AllArgsConstructor註釋會生成一個建構函式,該建構函式接受每個欄位(此處為saveOrderUseCase)作為引數。
介面卡將訂單的儲存委託給UseCase實現。
UseCase是我們領域核心的一部分,與領域模型一起實現領域邏輯。我們的簡單示例用例如下所示:
@AllArgsConstructor public class SaveOrderUseCase { private final SaveOrderPort saveOrderPort; public void saveOrder(Order order) { saveOrderPort.saveOrder(order); } } |
這裡沒什麼特別的。我們只是使用傳出埠介面來儲存傳遞的訂單。
雖然所示方法可能工作正常,但我們這裡有一個重大問題:我們的業務邏輯已洩漏到 Adapter 介面卡實現中。
也許你想知道:什麼業務邏輯?
我們有一個簡單的業務規則要實現:每次檢索到新訂單時,都應該將其持久化。在我們當前的實現中,此規則由介面卡實現,而我們的業務層(用例)僅提供通用儲存操作。
現在假設,一段時間後,新的需求到來:每次檢索到新訂單時,都應將一條訊息寫入審計日誌。
對於我們當前的實現,我們無法在SaveOrderUseCase 中寫入審計日誌訊息。顧名思義,用例用於儲存訂單而不是檢索新訂單,因此可能被其他元件使用。因此,在此處新增稽核日誌訊息可能會產生不良副作用。
也許解決方案很簡單:我們在介面卡中寫入審計日誌訊息:
@AllArgsConstructor |
public class KafkaAdapter { private final SaveOrderUseCase saveOrderUseCase; private final AuditLog auditLog; @KafkaListener(topic = ...) public void onNewOrderEvent(NewOrderKafkaEvent event) { Order order = event.getOrder(); saveOrderUseCase.saveOrder(order); auditLog.write("New order retrieved, id: " + order.getId()); } } |
而現在我們讓情況變得更糟。更多的業務邏輯已洩漏到介面卡中。
如果auditLog物件將訊息寫入資料庫,我們也可能搞砸了事務處理,這通常不會在傳入介面卡中處理。
使用更具體的域操作
這裡的核心問題是通用的SaveOrderUseCase。我們應該提供更具體的 UseCase 實現,而不是為介面卡提供通用的儲存操作。
例如,我們可以建立一個接受新檢索到的訂單的NewOrderRetrievedUseCase:
@AllArgsConstructor public class NewOrderRetrievedUseCase { private final SaveOrderPort saveOrderPort; private final AuditLog auditLog; @Transactional public void onNewOrderRetrieved(Order newOrder) { saveOrderPort.saveOrder(order); auditLog.write("New order retrieved, id: " + order.getId()); } } |
現在這兩個業務規則都在用例中實現。我們的介面卡實現現在只負責對映傳入資料並將其傳遞給用例:
@AllArgsConstructor public class KafkaAdapter { private final NewOrderRetrievedUseCase newOrderRetrievedUseCase; @KafkaListener(topic = ...) public void onNewOrderEvent(NewOrderKafkaEvent event) { NewOrder newOrder = event.toNewOrder(); newOrderRetrievedUseCase.onNewOrderRetrieved(newOrder); } } |
這種變化似乎只是一個很小的差異。但是,對於未來的需求,我們現在有一個特定的位置來處理我們業務層中的傳入訂單。否則,隨著新需求的出現,我們將更多的業務邏輯洩漏到不應該定位的地方的可能性很高。
像這樣的洩漏尤其經常發生在域層中過於通用的建立、儲存/更新和刪除操作。因此,在實施業務操作時儘量做到非常具體。
相關文章
- 提升思維邏輯—SimpleMind Pro(思維導圖) for Mac/winMac
- 領域邏輯的組織模式模式
- 測試筆試邏輯思維題筆試
- 軍事思維者的思考邏輯
- 如何建立強大的邏輯思維能力?
- 記憶體洩漏-原因、避免和定位記憶體
- 計算機程式的思維邏輯 (84) – 反射計算機反射
- 邏輯思維2019跨年演講——小趨勢
- 如何避免JavaScript中的記憶體洩漏?JavaScript記憶體
- 資料分析應有的邏輯思維及分析方法
- 計算機程式的思維邏輯 (34) – 隨機計算機隨機
- 計算機程式的思維邏輯 (50) – 剖析EnumMap計算機
- 計算機程式的思維邏輯 (41) – 剖析HashSet計算機
- 計算機程式的思維邏輯 (29) – 剖析String計算機
- 計算機程式的思維邏輯 (30) – 剖析StringBuilder計算機UI
- 計算機程式的思維邏輯 (82) – 理解ThreadLocal計算機thread
- 計算機程式的思維邏輯 (43) – 剖析TreeMap計算機
- 計算機程式的思維邏輯 (44) – 剖析TreeSet計算機
- 資料分析應學習邏輯思維和分析方法
- 程式設計師,你的邏輯思維有多強?程式設計師
- 計算機程式的思維邏輯 (71) – 顯式鎖計算機
- 計算機程式的思維邏輯 (56) – 檔案概述計算機
- [譯] Swift:通過示例避免記憶體洩漏Swift記憶體
- 從理性與感性的思維邏輯談遊戲製作遊戲
- 計算機程式的思維邏輯 (14) – 類的組合計算機
- 計算機程式的思維邏輯 (83) – 併發總結計算機
- 計算機程式的思維邏輯 (72) – 顯式條件計算機
- 計算機程式的思維邏輯 (55) – 容器類總結計算機
- 做一個有產品思維的研發:邏輯設計
- 計算機程式的思維邏輯 (88) – 正規表示式 (上)計算機
- 計算機程式的思維邏輯 (23) – 列舉的本質計算機
- 計算機程式的思維邏輯 (70) – 原子變數和CAS計算機變數
- [譯]Kotlin是如何幫助你避免記憶體洩漏的?Kotlin記憶體
- 計算機程式的思維邏輯 (20) – 為什麼要有抽象類?計算機抽象
- 計算機程式的思維邏輯 (47) – 堆和PriorityQueue的應用計算機
- 分析記憶體洩漏和goroutine洩漏記憶體Go
- 計算機程式的思維邏輯 (15) – 初識繼承和多型計算機繼承多型
- 計算機程式的思維邏輯 (94) – 組合式非同步程式設計計算機非同步程式設計