單體轉變到微服務之前採取DDD的三個步驟 - Jim Rottinger

banq發表於2019-06-23

作為單體一部分編寫程式碼很容易,我們可以隨時查詢資料庫,在應用程式的其他部分呼叫我們想要的任何函式,而不必考慮整個單體組織結構,因為我們正在插入現有的體系結構。然而,這種型別的開發導致的問題是一個脆弱的,糾纏不清的程式碼庫,其中對應用程式的一部分的任何更改都可以改變甚至破壞某些其他部分中的某些內容,而無需任何人知道原因。不僅如此,部署變得困難,而且還為進入程式碼庫的新開發人員創造了可怕的學習曲線。這根本不可取,並且許多發現自己處於這種情況的人開始閱讀並理解基於服務的體系結構(SOA)的優點。問題是 - 巨石越大,分解就越難。

從一個混亂的整體直接轉向SOA是不可行的。

我們可以得到某種中間狀態,這樣可以更容易地開始分解。這個中間狀態仍然是一個整體,但是它是由領域組織的,反而沒有我們原始程式碼庫的糾纏或脆弱。一旦我們做到這一點,就可以更容易地決定我們的應用程式的未來,特別是在決定什麼是突破服務以及哪些部分應該保持在一起。

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

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

領域驅動設計可以幫助我們實現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();
}


透過檢視這個介面,我們可以在不檢視程式碼的情況下推斷出它的工作原理。它使用目的地地址,運輸來源和商店的稅收關係來獲得訂單的稅率。即使在單一的程式碼庫中,這種將所有細節隱藏在單個介面背後的做法也是一個很好的習慣。

步驟3-使用不可變值物件
今天許多流行的程式設計語言,包括我主要使用的兩種程式設計語言,PHP和JavaScript,都可以很容易地將隨機容器資訊作為關聯陣列或物件傳遞。分解我們的程式碼庫的本質意味著許多資料將透過我們的各種新元件流動。雖然傳遞普通的舊物件可以工作,但是如果有某種合同明確指出每個子域區域內的內容以及將要從中返回的內容將會很好。如果這些物件是不可變的並且只有在明確定義了setter時才能改變它也會很好。

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


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;
    }
}


起初,它可能看起來很乏味,也沒必要這樣做,但它讓我們相信,在一個以稅率運作的系統中,我們將始終使用稅率,而不是隨機的價值​​,可能有也可能沒有我們需要的領域。

總結
從整體程式碼轉向SOA微服務是一項艱鉅的任務,而且不可能一步到位。如果您發現自己處於程式碼庫太大而無法快速迭代的位置,請不要立即開始嘗試破解它。相反,使用本文中描述的DDD概念將您的整體組織成明確定義的子域是您的目標。一旦完成,就可以更容易地將程式碼作為單獨的服務分解出來。

相關文章