天天看點

軟體架構(16)-Explicit Architecture在代碼中反映架構和領域

建立應用程式時,最簡單的部分是建構可以工作的東西。要建構在處理大量資料負載的情況下仍具有性能的東西,這有點困難。但最大的挑戰是建構一個實際可維護多年(10、20、100 年)的應用程式。

我工作過的大多數公司都有每 3 到 5 年重建一次應用程式的曆史,有些甚至是 2 年。這具有極高的成本,它對應用程式的成功程度以及公司的成功程度有重大影響,此外還讓開發人員非常沮喪地使用混亂的代碼庫,并使他們想離開公司。一家具有長遠眼光的嚴肅公司承受不起任何損失,無論是經濟損失、時間損失、聲譽損失、客戶損失還是人才損失。

在代碼庫中反映架構和域是應用程式可維護性的基礎,是以對于防止所有這些讨厭的問題至關重要。

顯式架構是我如何合理化由比我經驗豐富得多的開發人員提倡的一組原則和實踐,以及我如何組織代碼庫以使其反映和傳達項目的架構和領域。

在我之前的文章中,我談到了我如何将所有這些想法放在一起,并展示了一些資訊圖表和UMLish圖表,以嘗試建立某種概念圖來說明我的想法。

但是,我們如何在我們的代碼庫中實際應用它?!

在這篇文章中,我将讨論我如何在代碼中反映項目的架構和領域,并将提出一個我認為可以幫助我們規劃可維護性的通用結構。

我的兩張心理地圖

在本系列的最後兩篇文章中,我解釋了我用來幫助我思考代碼群組織代碼庫的兩個思維導圖,至少在我的腦海中。

第一個由一系列同心層組成,這些層最終被切片以構成應用程式的領域智能子產品,即元件。在此圖中,依賴方向向内,這意味着外層知道内層,但反之則不知道。

軟體架構(16)-Explicit Architecture在代碼中反映架構和領域

第二個是一組水準層,前面的圖表位于頂部,然後是元件共享的代碼(共享核心),然後是我們自己對語言的擴充,最後是實際的程式設計語言在底部。在這裡,依賴關系方向向下。

軟體架構(16)-Explicit Architecture在代碼中反映架構和領域

架構明顯的編碼風格

使用架構明顯的編碼風格意味着我們的編碼風格(編碼标準、類、方法和變量命名約定、代碼結構……)以某種方式将領域和架構傳達給閱讀代碼的人。關于如何實作架構明顯的編碼風格,有兩個主要想法。

“一種架構明顯的編碼風格,讓您可以向代碼閱讀者提供提示,以便他們能夠正确地推斷出設計。”

第一個是關于使用代碼工件(類、變量、子產品……)名稱來傳達域和體系結構的含義。是以,如果我們有一個類是處理發票實體的存儲庫,我們應該将其命名為類似“InvoiceRepository”的名稱,這将告訴我們它處理發票域概念,其架構角色是存儲庫。這有助于我們知道和了解它應該位于何處,以及如何以及何時使用它。盡管如此,我認為我們不需要對代碼庫中的每個代碼人工制品都這樣做,例如我覺得用“實體”對實體進行後處理是多餘的,隻會增加噪音。

“ 代碼應該反映架構。換句話說,如果我檢視代碼,我應該能夠清楚地識别每個元件 [...] ”

第二個是關于使子域明确地作為我們代碼庫的頂級人工制品,作為領域智能子產品,作為元件。

是以,第一個應該很清楚,我認為不需要任何進一步的解釋。然而,第二個更棘手,是以讓我們深入研究一下。

使架構明确

在我的第一個圖表中,我們已經看到在最高縮放級别我們有 3 種不同類型的代碼:

  • 使用者界面,包含使傳遞機制适應用例的代碼;
  • 應用程式核心,包含用例和領域邏輯;
  • 基礎設施,包含使工具/庫适應應用程式核心需求的代碼。
軟體架構(16)-Explicit Architecture在代碼中反映架構和領域

是以,在我們的源檔案夾的根目錄下,我們可以通過建立 3 個檔案夾來反映這些類型的代碼,每個檔案夾對應一種代碼類型。這三個檔案夾将代表三個命名空間,稍後我們甚至可以建立一個測試來斷言使用者界面和基礎設施都知道核心,但反之則不然,換句話說,我們可以測試依賴方向是否正确向内。

使用者界面

在 Web 企業應用程式中,通常有多個 API,例如一個用于用戶端的 REST API,另一個用于 3rd 方應用程式使用的 Web 挂鈎,可能是仍然需要維護的遺留 SOAP API,或者可能是 GraphQL用于新移動應用程式的 API……

對于此類應用程式,Cron 作業或按需維護操作使用多個 CLI 指令也很常見。

當然,它會有網站本身,供普通使用者使用,但也可能有應用程式管理者使用的另一個網站。

這些都是同一個應用程式的不同視圖,它們都是應用程式的不同使用者界面。

是以我們的應用程式實際上可以有多個使用者界面,即使其中一些僅由非人類使用者(其他第 3 方應用程式)使用。讓我們通過檔案夾/命名空間來反映,以分離和隔離多個使用者界面。

我們主要有 3 種類型的使用者界面,API、CLI 和網站。是以,讓我們首先在UserInterface根命名空間内通過為它們中的每一個建立一個檔案夾來使這種差異顯式化。

軟體架構(16)-Explicit Architecture在代碼中反映架構和領域

接下來,我們深入了解每種類型的命名空間,如果需要,我們為每個 UI 建立一個命名空間(對于 CLI,我們可能不需要這樣做)。

基礎設施

與使用者界面中的方式類似,我們的應用程式使用多種工具(庫和第三方應用程式),例如 ORM、消息隊列或 SMS 提供程式。

此外,對于這些工具中的每一個,我們可能需要有多個實作。例如,考慮一家公司擴充到另一個國家的情況,出于價格原因,最好在每個國家使用不同的 SMS 提供商:我們将需要使用相同端口的不同擴充卡實作,以便它們可以互換使用。另一種情況是當我們重構資料庫模式,甚至切換資料庫引擎時,需要(或決定)也切換到另一個 ORM:那麼我們将有 2 個 ORM 擴充卡連接配接到我們的應用程式中。

軟體架構(16)-Explicit Architecture在代碼中反映架構和領域

是以,在 Infrastructure 命名空間中,我們首先為每種工具類型(ORM、MessageQueue、SmsClient)建立一個命名空間,然後在每個工具類型中,我們為我們使用的供應商的每個擴充卡(Doctrine、Propel、MessageBird、Twilio)建立一個命名空間,……)。

核心

在核心中,在最進階别的縮放中,我們有三種類型的代碼,即元件、共享核心和端口。是以,我們為所有這些建立檔案夾/命名空間。

在 Components 命名空間中,我們為每個元件建立一個命名空間,在每個元件中,我們為應用層建立一個命名空間,為領域層建立一個命名空間。在 Application 和 Domain 命名空間中,我們首先将所有類轉儲到那裡,随着類數量的增加,我們開始根據需要對它們進行分組(我發現建立一個檔案夾隻放一個類在裡面太過分了,是以我甯願這樣做它作為它出現的需要)。

軟體架構(16)-Explicit Architecture在代碼中反映架構和領域

在這一點上,我們需要決定是否應該按主題(發票、交易……)或技術角色(存儲庫、服務、價值對象……)對它們進行分組,但我覺得,無論選擇什麼,它都不是真的影響很大,因為我們處于組織樹的葉節點,是以,如果需要,很容易對結構的最後一點進行更改,而不會對代碼庫的其餘部分産生太大影響。

端口

Ports 命名空間将為核心使用的每個工具包含一個命名空間,就像我們為基礎設施所做的那樣,我們将在其中放置核心将使用的代碼以使用底層工具。

軟體架構(16)-Explicit Architecture在代碼中反映架構和領域

此代碼也将由擴充卡使用,其作用是在端口和實際工具之間進行轉換。它是最簡單的形式,端口隻是一個接口,但在許多情況下,它還需要值對象、DTO、服務、建構器、查詢對象甚至存儲庫。

共享核心

在共享核心中,我們将放置在元件之間共享的代碼。在嘗試了共享核心的不同内部結構之後,我無法決定适合所有場景的結構。我覺得對于某些代碼來說,像我們所做的那樣将它按元件分開是有意義的Core\Component(即實體 ID 顯然屬于一個元件)但其他情況則不然(即事件可能被多個元件觸發和監聽,是以他們不屬于任何人)。也許混合更合适。

軟體架構(16)-Explicit Architecture在代碼中反映架構和領域

使用者空間語言擴充

最後但同樣重要的是,我們有自己的語言擴充。正如本系列上一篇文章中所解釋的,這些代碼可能是該語言的一部分,但出于某種原因,它不是。例如,對于 PHP,我們可以考慮一個基于 PHP 提供的 DateTime 類但具有一些額外方法的類。另一個例子可能是一個 UUID 類,雖然 PHP 沒有提供它,但它本質上是非常無菌的,域不可知的,是以可以被獨立于域的任何項目使用。

軟體架構(16)-Explicit Architecture在代碼中反映架構和領域

這段代碼的使用就好像它是由語言本身提供的一樣,是以它需要完全在我們的控制之下。然而,這并不意味着我們不能使用 3rd 方庫。我們可以并且應該在有意義的時候使用它們,但它們必須由我們自己的實作包裝(以便我們可以輕松切換底層的第 3 方庫),這是将直接在應用程式代碼庫上使用的代碼。最終,它可以成為一個獨立的項目,在自己的 CVS 存儲庫中,并在多個項目中使用。

加強架構

所有這些想法以及我們決定将它們付諸實踐的方式都需要大量吸收,而且它們并不容易掌握。即使我們掌握了這一切,最終我們也隻是人,是以我們會犯錯誤,我們的同僚也會犯錯誤,就是這樣。

就像我們在代碼中犯錯誤并有一個測試套件來防止這些錯誤進入生産一樣,我們必須對代碼庫結構做同樣的事情。

為此,在 PHP 世界中,我們有一個名為Deptrac的小工具(但我敢打賭其他語言​也存在類似的工具),它是由 Sensiolabs 建立的。我們使用 yaml 檔案對其進行配置,我們在其中定義了我們擁有的層以及它們之間允許的依賴關系。然後我們通過指令行運作它,這意味着我們可以輕松地在 CI 中運作它,就像我們在 CI 中運作測試套件一樣。

我們甚至可以讓它建立一個依賴關系圖,這将直覺地向我們展示依賴關系,包括那些破壞配置規則集的依賴關系:

軟體架構(16)-Explicit Architecture在代碼中反映架構和領域

結論

應用程式由域和技術結構(體系結構)組成。這些是應用程式中真正的差異,而不是使用的工具、庫或傳遞機制。如果我們希望一個應用程式能夠長期維護,這兩者都需要在代碼庫中明确,以便開發人員可以了解它、了解它、遵守它并根據需要進一步發展它。

這種明确性将幫助我們在編寫代碼時了解邊界,這反過來又将幫助我們保持應用程式設計的子產品化、高内聚和低耦合。

同樣,我在之前的文章中一直在談論的這些想法和實踐中的大多數都來自比我更好、更有經驗的開發人員。我已經與不同公司的許多同僚詳細讨論了它們,我已經在企業應用程式的代碼庫中對它們進行了試驗,并且它們在我參與的項目中一直運作良好。

盡管如此,我相信沒有 靈丹妙藥,沒有萬能的靴子,沒有聖杯。

繼續閱讀