如何將單體分解成微服務?

banq發表於2017-06-18
本文您推薦採取三個領域驅動的步驟,能使您的程式碼庫變得更易於管理

毫不諱言,在單體(整體/鐵板一塊monolith)架構中編寫程式碼是容易的。我們可以隨時直接查詢資料庫,在應用程式的其他部分呼叫我們想要的任何功能,而不必考慮整體架構組織,因為我們正在向現有架構插入新程式碼。然而,這種型別風格的發展會導致脆弱的混亂的程式碼庫,其中對應用程式的一部分任何改變都可能會改變甚至破壞其他部分的正常功能,而且沒有人知道為什麼。

不僅如此,部署也變得困難,而且為新開發人員進入程式碼庫創造了一個糟糕的學習曲線。這些都是不可取的,許多發現自己處於這種情況的人開始閱讀並理解基於服務架構(SOA)的優勢。問題是 - 單體monolith變得越大,越難分解。

雖然事情是很難的,但卻不會變成不可能。話雖如此,我們仍然在感性上感覺到這是不可能,因為從一個混亂的單體直接轉到SOA是不太可行的。必須有某種中介狀態,這樣可以更容易地開始突破。

這個中介狀態雖然仍然是一個單體,但是由業務領域進行組織的,而不是原始程式碼庫的糾纏或脆弱的狀態。一旦我們達到這一階段,對於我們的應用程式的未來再做出決定就要容易得多,特別是關於決定分解哪些服務,哪些部件應該保持在一起。

在這篇文章中,我們將介紹領域驅動設計(DDD),然後再透過三個步驟,將一個凌亂的單體轉化為剛剛描述的有組織的中間狀態。


領域驅動設計
在開始任何重構之前,我們必須弄清楚我們應用程式的業務領域。最簡單的方法是將應用程式分解成可以根據業務邏輯來解釋的元件。例如,在結算應用程式中,某些領域可能是地址驗證,運輸,稅收和支付處理。在軟體中,這種分解或構建您的程式碼是根據業務邏輯而進行組織的行為稱為領域驅動設計(DDD)。

DDD的主要概念之一是在一些有界上下文環境中透過子域來組織您的程式碼職責功能。在我們的結算應用程式的示例中,結算是一個較大的電子商務平臺內的有限上下文,而運輸和稅收將分別是結算上下文中的子域。

[img index=1]

在上圖左側,所有邏輯都在一個程式中處理,造成相互依賴的關係。在右邊,所有的職責功能都是由不具有交叉點的域分解出來的。

領域驅動設計可以幫助我們實現SOA的原因在於,透過將程式碼分解成單個的領域,我們實際上已經建立了類似於服務的部件。

然後,如果需要,這些類似服務的部件可以被分解成主要應用程式所要求的相應的微服務。或者,如果您決定保留單體架構,您現在可以在易於迭代的狀態下輕鬆理解應用程式中發生的情況。

至此,我們可以採取下面一些步驟來組織我們的程式碼。

步驟1-根據領域“檢疫”您的業務邏輯,以消除相互依賴關係

“檢疫”一詞在這裡是有目的選擇的一個詞語,因為它不僅意味著隔離。透過隔離,我們不只是將功能分離成不同的檔案,我們希望進一步,甚至不讓它們接觸應用程式的其他部分,除非我們特別要透過注入一個依賴關係。

在構建應用程式時,通常情況下,您的邏輯將觸及許多不同的領域。讓我們再考慮電子商務應用程式中的結算流程。單個結算涉及驗證送貨地址,計算稅率,從運營商處獲取運送費用,驗證庫存是否可用,...有很多步驟。這些完全可以在一個過程中處理所有的結算步驟,但這隻會使您的程式碼難以維護,幾乎不可能進行測試。相反,我們要完全分離所有與這些部分相關的邏輯,使它們完全不相互接觸。

保持您的應用程式邏輯按領域排序,是建立基於服務架構的第一步。

如果您擁有控制器或實用程式檔案,具有不同功能的抓取包,請先分開它們並透過職責功能進行組織,並刪除每個功能的相互依賴關係。

步驟2 - 定義您的介面,並隱藏所有其他內容

在一個單體程式碼庫中工作的缺點之一是新的開發人員有一個巨大的學習曲線。當他第一次看到程式碼庫時已經被巨大單體程式碼的氣勢壓倒,如同面對一座大山或巨石,一切邏輯都混在一個地方,你不知道誰呼叫誰和在哪裡呼叫。所有的邏輯都在一個地方,你不知道從哪裡開始。

作為開發人員,我們不應該瞭解應用程式的其他部分的內部工作,這樣才能方便快速進入程式碼庫工作。

DDD為我們帶來的優勢之一是它允許我們從業務邏輯的角度考慮我們的應用程式。

每個領域中的所有邏輯都應該由一個單一的介面來表示,這個介面可以用來理解隱藏在其中的一切功能。

以下是計算訂單稅率的介面示例。

// iTaxCalculator.php
include ValueObject\Address;
include ValueObject\TaxRate;
interface iTaxCalculator
{
 /**
  * @throws InvalidArgumentError
  * @return bool
  */
  public function setTaxNexus(array $addresses);
 /**
  * @return bool
  */
  public function setDestination(Address $address);
 /**
  * @return bool
  */
  public function setShippingOrigin(Address $address);
 /**
  * @return \ValueObject\TaxRate
  */
  public function getTaxRate();
}
<p class="indent">




只要看這個介面,我們就可以推斷出它的工作原理,甚至不用看程式碼。它使用目的地地址,運輸來源和商店的稅務關係來獲得訂單的稅率。即使在一個單體程式碼庫中,這種在單個介面背後隱藏所有細節的做法也是一個很好的習慣。

步驟3-使用不可變值物件

今天許多流行的程式語言,包括我主要使用的兩個:PHP和JavaScript,使得很容易使用關聯陣列或物件作為容器傳遞資訊。破壞我們的程式碼庫的本質就是大量的資料流過我們各種新的元件並將它們流粘在一起。

其實,只是傳遞簡單的普通物件就可以了,如果有某種合約能說明每個子域中的內容以及將要從中退出的內容,那將是很好的。如果這些物件是不可變的,那麼也是很好的,只有在明確定義了setter時才可以顯式更改這些物件。

這就是使用值物件的地方。值物件不是模型例項,它們沒有ID。它們是具有定義屬性的不可變資訊容器,它們的狀態完全取決於其值。這是一個例子:

//TaxRate.php
class TaxRate
{
   /**
    * @var float
    */
    private $tax_rate;
   /**
    * @var string add|base|instead
    */
    private $rate_type;
    public function __construct($tax_rate, $rate_type)
    {
        $this->tax_rate = $tax_rate;
        $this->rate_type = $rate_type;
    }
   /**
    * Get the tax rate
    */
    public function getTaxRate()
    {
        return $this->tax_rate;
    }
   /**
    * Get tax type
    */
    public function getTaxType()
    {
        return $this->tax_type;
    }
}
<p class="indent">


起初看起來似乎是很乏味的,但這使我們有信心在稅率上執行的系統中,我們將始終使用這個稅率值物件,而不是隨機的一堆blob值包裝,可能有也可能沒有我們需要的領域。

概要
從單體程式碼到SOA是一項艱鉅的任務,不可能一步完成。如果您發現自己的程式碼庫變得太大,無法快速迭代,請立即開始嘗試將其分解或破解。使用本文中描述的DDD概念將您的單體組織架構分解到明確的子域中。一旦做到這一點,再開始將程式碼分解為單獨的微服務將變得更加容易。


How to Organize your Monolith Before Breaking it i

相關文章