奈飛Netflix如何在資料整合API領域使用六邊形架構與Clean架構切換到微服務架構? - Netflix TechBlog

banq發表於2020-03-11

大約一年前,我們的工作流程團隊開始開發跨業務多個領域的新應用。我們面臨著一個有趣的挑戰:是從頭開始構建應用程式的核心,同時還需要使用許多不同系統中存在的資料。
我們需要的一些資料點,例如有關電影,製作日期,員工和拍攝地點的資料,分佈在實現各種協議的許多服務中:gRPC,JSON API,GraphQL等。現有資料對於我們應用程式的行為和業務邏輯至關重要。我們從一開始就需要高度整合。

可切換的資料來源
將可見性引入我們產品的早期應用程式之一是作為單體構建的。單體架構允許快速開發和快速更改。某一時刻,有30多個開發人員正在使用它,並且它具有300多個資料庫表。
隨著時間的流逝,應用程式從廣泛的服務產品演變為高度專業化的產品。這導致決定將單體分解為特定的服務。該決定並非針對效能問題,而是針對所有這些不同領域設定了界限,並使專門團隊能夠獨立開發針對特定領域的服務。
單體仍然提供了我們為新應用所需的大量資料,但我們知道單體將在某個時候分解。我們不確定分手的時間,但是我們知道分手是不可避免的,我們需要做好準備。
因此,我們一開始可以利用來自單體的某些資料,因為它們仍然是真實的來源,但要準備將這些資料來源在聯機後立即交換給新的微服務

利用六角形架構
在不影響業務邏輯的情況下我們還需要支援交換資料來源的能力,因此需要將這兩者分離。我們決定根據Hexagonal Architecture和Bob叔叔的Clean Architecture背後的原理來構建我們的應用程式。
六邊形架構思想是將輸入和輸出置於我們設計的邊緣。業務邏輯不應該依賴於我們公開REST還是GraphQL API,也不應該依賴於我們從何處獲取資料,不依賴於資料庫,透過gRPC或REST公開的微服務API,或者僅僅是一個簡單的CSV檔案。
該模式使我們能夠將應用程式的核心邏輯與外界的關注隔離開。將我們的核心邏輯隔離開意味著我們可以輕鬆更改資料來源詳細資訊,而不會造成重大影響或無需將主要程式碼重寫為程式碼庫。
在具有清晰邊界的應用程式中,我們還看到的主要優勢之一是測試策略-我們的大多數測試都可以驗證我們的業務邏輯,而無需依賴易於更改的協議。

定義核心概念
借鑑六邊形/六角形結構,定義業務邏輯的三個主要概念是實體,儲存庫和互動器。

  • 實體是領域物件(例如電影或拍攝地點),它們不知道它們自己的儲存位置(與Ruby on Rails或Java Persistence API中的Active Record不同)。
  • 儲存庫是獲取實體以及建立和更改實體的介面。它們保留用於與資料來源通訊並返回單個實體或實體列表的方法列表。(例如UserRepository)
  • 互動器是用於編排和執行領域操作的類-考慮服務物件或用例物件。他們實施特定於領域操作的複雜業務規則和驗證邏輯(例如,入職生產)

使用這三種主要型別的物件,我們可以定義業務邏輯,而無需任何知識或關心資料儲存在何處以及如何觸發業務邏輯。業務邏輯之外是資料來源和傳輸層:
  • 資料來源是不同儲存實現的介面卡。資料來源可能是SQL資料庫的介面卡(Rails中的Active Record類或Java中的JPA),彈性搜尋介面卡,REST API,甚至是諸如CSV檔案或Hash之類的簡單介面卡。資料來源實現在儲存庫上定義的方法,並儲存獲取和推送資料的實現。
  • 傳輸層可以觸發互動器執行業務邏輯。我們將其視為系統的輸入。微服務最常見的傳輸層是HTTP API層和一組處理請求的控制器。透過將業務邏輯提取到互動器中,我們不會耦合到特定的傳輸層或控制器實現。互動器不僅可以由控制器觸發,還可以由事件,cron作業或命令列觸發。

奈飛Netflix如何在資料整合API領域使用六邊形架構與Clean架構切換到微服務架構? - Netflix TechBlog


使用傳統的分層體系結構,我們將使所有依賴項指向一個方向,上面的每一層都取決於下面的層。傳輸層將取決於互動程式,互動程式將取決於持久層。
在“六角體系結構”中,所有依賴點都向內指向-我們的核心業務邏輯對傳輸層或資料來源一無所知。傳輸層仍然知道如何使用互動器,資料來源知道如何符合儲存庫介面。
這樣,我們就可以為其他Studio系統的不可避免的更改做好準備,並且只要需要進行更改,就很容易完成交換資料來源的任務。

切換資料來源
交換資料來源的需求比我們預期的要早:我們突然遇到了一個單體的讀取限制,並且需要將某個實體的特定讀取切換到在GraphQL聚合層上公開的較新的微服務。微服務和單體保持同步,並且從一個服務或另一個服務讀取的資料相同,產生的結果相同。
我們設法在2小時內將讀取資料從JSON API傳輸到GraphQL資料來源。
我們之所以能夠如此快地完成它,主要原因是六角結構(透過適當的抽象,就很容易更改資料來源)。我們沒有讓任何永續性細節洩漏到我們的業務邏輯中。我們建立了一個實現儲存庫介面的GraphQL資料來源。一個簡單的單行(one-line)變化是所有我們需要開始從不同的資料來源讀取。
此時,我們知道六角建築已經為我們發揮作用了。
單行更改的很大一部分在於,它可以減輕釋出風險。如果下游微服務在初始部署時失敗,則回滾非常容易。這也使我們能夠分離部署和啟用,因為我們可以決定透過配置使用哪個資料來源。

隱藏資料來源詳細資訊
該體系結構的一大優勢是我們能夠封裝資料來源實現細節。我們遇到了這樣一種情況:我們需要一個尚不存在的API呼叫-服務具有一個API來獲取單個資源,但沒有實現批次獲取。與提供API的團隊進行交流後,我們意識到此端點需要一些時間才能交付。因此,我們決定在構建此端點的同時,提出另一種解決方案來解決該問題。
我們定義了一個儲存庫方法,該方法可以在給定多個記錄識別符號的情況下獲取多個資源-並且該方法在資料來源上的初始實現向下遊服務傳送了多個併發呼叫。我們知道這是一個臨時解決方案,資料來源實現的第二個要點是在實現後使用批次API。
這樣的設計使我們能夠繼續滿足業務需求,而不會產生太多技術債務,也無需事後更改任何業務邏輯。因為我們的業務邏輯不需要了解特定的資料來源限制。

測試策略
當我們開始嘗試六角結構時,我們知道我們需要提出一種測試策略。我們知道,要獲得高速發展的先決條件就是擁有可靠且超快速的測試套件。我們認為它不是一個很好的選擇,而是必須具備的。
我們決定在三個不同的層次上測試我們的應用程式:

  • 測試我們的互動器,我們的業務邏輯的核心生活在其中,但與任何型別的永續性或傳輸無關。我們利用依賴注入並模擬任何型別的儲存庫互動。這是對我們的業務邏輯進行詳細測試的地方,而這些正是我們力求進行的大部分測試。
  • 測試資料來源,以確定它們是否與其他服務正確整合,它們是否符合儲存庫介面,並檢查它們在出現錯誤時的行為。我們嘗試最小化這些測試的數量。
  • 我們具有整合規範,遍及整個堆疊,從我們的Transport / API層到互動器,儲存庫,資料來源以及重要的下游服務。這些規格測試了我們是否正確“佈線”了一切。如果資料來源是外部API,我們將命中該端點並記錄響應(並將其儲存在git中),從而使我們的測試套件可以在每次後續呼叫時快速執行。我們不會在這一層進行廣泛的測試,通常每個域操作只有一個成功方案和一個失敗方案。

我們不測試儲存庫,因為它們是資料來源實現的簡單介面,並且我們很少測試實體,因為它們是定義了屬性的普通物件。我們測試實體是否具有其他方法(不涉及持久層)。
與可以輕鬆在任何機器上執行的測試套件一起工作非常好,並且我們的開發團隊可以在不中斷的情況下處理其日常功能。

延遲決策
在將資料來源切換到不同的微服務時,我們處於非常有利的位置。關鍵好處之一是,我們可以延遲有關是否以及如何儲存應用程式內部資料的某些決定。根據功能的用例,我們甚至可以靈活地確定資料儲存的型別-是關係型還是文件型。
鮑伯叔叔說得很好:
好的架構的目的是延遲決策。為什麼?因為當我們推遲做出決定時,我們需要更多的資訊來做出決定。
在專案開始時,關於我們正在構建的系統的資訊量最少。我們不應該將自己鎖定在一個不知情的決定而導致專案悖論的體系結構中。
我們現在做出的決定對我們的需求很有意義,並且使我們能夠快速行動。六角結構的最好之處在於,它可以使我們的應用程式靈活地適應將來的需求。
 

相關文章