Redux 和領域驅動設計

Redux 的創建者 Dan Abramov 說他不知道什麼是領域驅動設計。儘管如此,令人印象深刻的是 Redux 與 DDD 的相似之處。在本文中,我解釋了 DDD 是什麼,一些關鍵概念,以及 Redux 如何實現其思想。理解兩者,我們可以提供更好的實現;來自不同世界的兩種方法相互碰撞並利用相同的設計原則。

領域驅動設計

領域驅動設計是一種軟件建模技術,旨在創建強大的微服務架構以及集成多個現有解決方案。
Eric Evans 最初於 2003 年在《領域驅動設計:解決軟件核心中的複雜性》一書中提出它。目前,DDD 有更多的書籍、更多的示例,並且已被證明可以有效地擴展和保持大型系統中的高級性能。如果您聽說過 Event-Sourcing 或 CQRS,那麼您已經與 DDD 擦肩而過。
我們可以將 DDD 分爲兩個領域:戰略和戰術。該策略引入了泛在語言和限界上下文。它在開發人員和業務之間創建了一種通用語言,但這種語言超越了會議:所有文檔、故事甚至代碼都共享該語言。每個聲明的變量、函數、類或包名都與通用語言匹配。
策略更多的是關於如何實施系統。主要目標是在許多位置實現跨多個微服務的系統擴展。使用的抽象是查詢、命令、域事件和聚合。應用程序將查詢和命令指向聚合,聚合執行所有計算,域事件在整個系統中保持最終一致性。
戰術的相關概念是:

不幸的是,許多人混淆了命令和領域事件。兩者都是動詞,都可能暗示狀態的變化,但它們是不同的。命令是意圖,領域事件是事實。這就是爲什麼命令可能會失敗,但域事件不會。命令是我們想要發生的事情,而領域事件是已經發生的事情。
如果您想了解有關 DDD 的更多信息,我強烈建議您閱讀 Vernon Vaughn «Domain-Driven Design Distilled» (2016) 的書。本書快速介紹了所有概念,並全面介紹瞭如何開始做 DDD。

Redux

Redux 與領域驅動設計有着驚人的關聯。雖然它不共享相同的術語,但想法是存在的。Redux 幾乎是功能範式中 DDD 策略的實現。
讓我們將之前的概念與 Redux 進行比較:

不幸的是,Redux 詞彙表並不容易區分命令和領域事件。DDD 使用不定式動詞來表示命令;和事件的過去分詞。儘管如此,通常會看到 redux 操作類型,例如命令 “FETCH_POST” 或事件“FETCH_POST_SUCCESSFUL”。

Redux 上的 DDD 模式

有兩種模式使 DDD 流行起來:事件溯源和 CQRS。兩者都源於提高可擴展性和性能的必要性,並且這兩種技術通常都應用在 Redux 中。

第一個是事件溯源。

DDD 用於事件溯源的目標是增加數據庫中寫入的吞吐量。它不會將每個更改保存在數據庫中,而是僅存儲每個聚合發出的域事件,並在可能的情況下存儲聚合的快照。推理很簡單:您可以通過重放其事件來重建任何聚合的狀態。

例如,您可以通過重播 PostAdded 事件來重建所有帖子。

你熟悉 Redux 中的這個概念嗎?幾乎可以肯定,是的。在 Redux 中,這稱爲 Time Traveling,您可能在開發人員工具中調試時經常使用它。
這種模式很棒;它不僅使我們能夠更快地修復錯誤或加快服務器上的寫入速度,而且有助於使應用程序更安全。數據丟失?沒問題,重播事件,就可以重建狀態。由於錯誤導致數據損壞?解決錯誤、重播事件並獲得原始狀態。你在幫助其他用戶嗎?只需重播他們的事件即可知道他們的狀態。

第二個是 CQRS。

CQRS 的 DDD 的目標是創建組合來自多個聚合的數據的模型。與其執行大量慢速查詢,不如在一個模型上進行一次快速快速查詢。如果事件溯源處理慢更新,它解決慢查詢。這個想法是,一個獨特的模型將消耗多個事件並一致地計算派生狀態。然後,使用該新模型。例如,我們可以創建一個模型來統計帖子。它接收 PostAdded 事件並增加每個事件的計數。

Redux 中的等價物是多個 reducer 在不同的地方使用相同的操作進行更新。儘管我們有帶記憶的選擇器,但有時,我們更喜歡保留計算得出的數據以提高性能。例如,當我們有一個帶有由鍵索引的實體的對象時,但我們有一個帶有鍵的數組。它加快了列表查詢。

function reducePosts(state, action) {
  switch (action.type) {
    case ADD_POST:
      return { ...state, [action.post.id]: action.post };
    ...
  }
}function reducePostList(state, action) {
  switch (action.type) {
    case ADD_POST:
      return [...state, action.post.id];
    ...
  }
}function getPostList(state) {
  // instead of Object.keys(state.post)
  return state.postList; 
}

DDD 依賴解耦。

雖然它不是一種模式,但 DDD 很好地解耦了它們之間的聚合。除了性能的可擴展性之外,它是 DDD 的主要優勢之一。聚合的概念以及它如何與其他人交互它提供了高度的可維護性和更好的實現。正是這種精確的特性阻止了有害的大泥球的產生。

讓我們看一個例子:我們有一家銷售產品並使用營銷活動來提供報價的公司。商店中的現有商品最初標有相應的產品售價,但當活動開始時,它會用廣告價格重新標記商品。
如果沒有 DDD,我們有如下代碼:

// Without DDD
class Campaign {
  ...
  startCampaign() {
    product.relabelUnits(advertisedPrice);
  }
}class Product {
  ...
  relabelUnits(price) {
    units.forEach(unit => unit.relabelPrice(price));
  }
}class Unit {
  ...
  relabelPrice(price) {
    labeledPrice = price;
  }
}

如果我們應用 DDD,我們可以中繼域事件來更新其他聚合:

// With DDD
class Campaign {
  ...
  startCampaign() {
    emit(new CampaignStarted(..., productId, advertisedPrice));
  }
}class Unit {
  ...
  onCampaignStarted(event) {
    labeledPrice = event.advertisedPrice;
  }
}

你注意到不同了嗎?現在產品已經消失了。該產品不再依賴於該單元。我們減少了應用程序的耦合,我們可以在不更改任何代碼的情況下從系統中插入和拔出單元。
Redux 做同樣的解耦。每個組合的減速器就像一個聚合體。當 reducer 收到一個動作時,它會獨立地減少它。

function reduceUnit(state, action) {
  switch (action.type) {
    case START_CAMPAIGN:
      return { ...state, labeledPrice: action.advertisedPrice };
    ...
  }
}

結論

Redux 和 DDD 有許多相似之處,並且都具有許多優點。儘管它們是從不同的抽象和不同的背景創建的,但它們都受益於相同的架構原則。
主要區別在於領域事件。這個概念在 Redux 中並沒有明確存在。它有後果,可能會在進一步的文章中進行研究。

超級架構師 架構師的寶庫,每天一篇,開拓你的視野和深度。分享企業架構,業務架構,應用架構,數據架構,技術架構,安全架構等。討論架構框架, 規劃,治理, 標準,落地。交流新興的架構風格和模型。如微服務,事件驅動,微前端,大數據,數倉,物聯網,人工智能架構。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/tz4sVybWL8JgAvaOZbE--Q