軟體架構的本質與簡潔之道

在軟體開發的過程中,一套有效的設計方法至關重要。好的方法能節省時間、提供具有遠見的解決方案,並避免重複造輪子。業界專家們經常分享他們的最佳實務、模式和反模式,這些經驗的累積形成了各種設計方法。

簡潔架構就是這樣一種方法論,它並非針對特定問題的解決方案,而是提供一種更通用的設計思路。它強調抽象和隔離,將系統的各個部分盡可能獨立,以便在不影響其他部分的情況下進行替換。

然而,這種高度抽象也可能帶來效能損耗和更高的初始開發成本。因此,在應用簡潔架構時,務必保持理性,理解其背後的原理,並根據實際需求進行調整。

簡潔架構的優勢與考量

簡潔架構的核心目標是開發易於維護和擴充套件的軟體系統。透過將業務邏輯與外部框架、資料函式庫等隔離,可以降低系統耦合度,提高程式碼的可測試性和可重用性。

  graph LR
    A[業務邏輯] --> B(介面);
    B --> C[外部系統];
    C --> D{資料函式庫};
    C --> E{Web 框架};

內容解密: 上圖展示了簡潔架構的層次關係,業務邏輯處於核心,透過介面與外部系統互動,實作了與資料函式庫和 Web 框架的解耦。

然而,簡潔架構並非銀彈,它也存在一些侷限性。例如,過多的抽象層次可能影響系統效能,需要在設計時仔細權衡。此外,簡潔架構的初始設定較為複雜,需要一定的學習成本。

Python 與簡潔架構的完美結合

Python 作為一種動態語言,其靈活性非常適合實踐簡潔架構。Python 的豐富生態系統提供了許多工具和函式庫,可以幫助我們更好地實作抽象和隔離。

在接下來的文章中,我將會探討如何在 Python 中應用簡潔架構的各個組成部分,並透過實際案例示範如何構建一個簡潔、可維護的 Python 應用程式。

從程式碼範例理解簡潔架構

以下是一個簡單的 Python 程式碼範例,展示了簡潔架構中業務邏輯的實作方式:

class UseCase:
    def __init__(self, repository):
        self.repository = repository

    def execute(self, request):
        data = self.repository.get_data(request.id)
        # 進行業務邏輯處理
        result = self.business_logic(data)
        return result

    def business_logic(self, data):
        # 根據 data 進行計算或處理
        return processed_data

內容解密: UseCase 類別封裝了業務邏輯,它透過 repository 介面取得資料,並執行 business_logic 方法進行處理。這種設計將業務邏輯與資料存取隔離,提高了程式碼的可測試性和可重用性。

簡潔架構是一種強大的軟體設計方法,它可以幫助我們構建更易於維護、更具彈性的軟體系統。在 Python 中實踐簡潔架構,可以充分發揮 Python 的語言優勢,並結合豐富的生態系統,開發高品質的軟體產品。

打破規則的思維:軟體架構設計的彈性

在軟體開發中,方法論並非一成不變的教條。理解其背後原理後,我們可以根據實際情況取捨,保留有用的部分,摒棄不適用的部分。我個人的經驗是,無論是在設計檔案或程式碼註解中,記錄這些決策的原因至關重要。這不僅方便日後參考,也避免其他開發者因不理解而誤改程式碼。

軟體架構:藍圖與策略

任何產品系統,無論是軟體、機械裝置或流程,都由元件及其連線構成。這些連線讓元件的輸出作為其他元件的輸入,以執行特定動作。架構定義了系統中有哪些元件,以及它們如何互連。

以寫作過程為例,輸入是想法和句子,輸出是文字。可以用筆和紙完成,也可以加入聽寫、校對、設計等環節。不同的架構會影響輸出品質和速度。

架構的粒度如同縮放鏡頭,可以從不同層級檢視系統。最粗略的層級將整個系統視為黑盒,只關注輸入和輸出。隨著粒度細化,我們能看到黑盒內部的元件及其連線方式,進而理解系統的內部運作。

商店的例子可以說明架構的多層次性。從外部看,商店是顧客用錢換取商品的地方。深入一層,我們看到商店的庫存管理、人員組態、銷售流程等。再深入,我們可以分析員工的工作效率、顧客的購物體驗等細節。

軟體系統也類別似。雲端伺服器可以被視為一個處理單元,輸入是成本,輸出是服務請求數。更細粒度的架構會揭示硬體、作業系統、程式語言、函式庫等細節。

架構的優劣取決於成本、輸出品質、簡潔性、可修改性等因素。

清晰架構:化繁為簡之道

本章介紹的架構常被稱為「清晰架構」。這個名稱並非新創,而是多年來許多軟體設計師推崇的設計理念。「清晰」意味著易於理解系統的運作方式,與難以理解的「義大利麵條式程式碼」形成鮮明對比。

清晰架構的核心是明確每個元件的位置和作用,避免系統過於耦合,方便修改和替換元件。清晰架構並非完美方案,它能解決特定問題,但並非萬能。理解其原理,才能判斷是否適用於你的需求。

架構之旅:設計原則重於形式

本章的目標是引導讀者踏上架構設計之旅,而非提供一成不變的步驟。設計原則比最終的系統結構更重要。書中提供的架構示例應被視為靈感來源,而非必須遵循的範本。

  graph LR
    A[輸入: 金錢 & 顧客] --> B{商店};
    B --> C[輸出: 商品 & 顧客];
    D[輸入: 商品庫存] --> B;
    B --> E[輸出: 付款給供應商];

內容解密: 以上Mermaid圖表展示了商店的簡化模型,將其視為一個系統,並顯示了主要的輸入和輸出。顧客和金錢作為輸入進入商店,商店提供商品給顧客,並使用金錢支付給供應商以取得商品庫存。

  graph LR
    subgraph 雲端伺服器
        A[輸入: 成本] --> B{處理單元};
        B --> C[輸出: 服務請求數];
    end
    
    subgraph 細粒度架構
        D[硬體] --> B;
        E[作業系統] --> B;
        F[程式語言] --> B;
        G[函式庫] --> B;
    end

內容解密: 此圖表進一步說明瞭軟體系統的架構,以雲端伺服器為例。它展示瞭如何從單一處理單元細化到更底層的硬體、作業系統、程式語言和函式庫等組成部分,這些都影響著伺服器的效能和功能。

打破軟體設計的藩籬:透視 Clean Architecture 的 Python 實踐之道

玄貓(BlackCat)

摘要: Clean Architecture 是一種軟體設計理念,旨在開發易於維護、測試和擴充套件的系統。本文將深入淺出地解析 Clean Architecture 的核心概念,並以 Python 程式碼為例,逐步引導讀者理解其在實際專案中的應用。從系統架構的宏觀視角到程式碼層級的微觀剖析,本文將帶您領略 Clean Architecture 的精髓,並提供實用的 Python 實踐技巧。


軟體開發如同建築設計,良好的架構是成功的根本。Clean Architecture 正是這樣一種設計理念,它提倡將軟體系統的核心業務邏輯與外部框架、資料函式庫等細節分離,從而提升系統的可維護性、可測試性和可擴充套件性。

我與 Python 的淵源已逾 20 年,它的簡潔與強大深深吸引了我,也促使我在許多專案中選擇它作為主要開發語言。在接觸 Clean Architecture 之初,我正著手開發一個 Python 應用程式,用於整合衛星影像處理鏈的各個步驟。因此,我對 Clean Architecture 的探索之旅,正是從 Python 開始的。

本文將以 Python 為例,闡述 Clean Architecture 的核心概念,但這些概念同樣適用於其他語言,尤其是物件導向語言。理解本文的示例和專案需要具備 Python 的基礎語法知識。Clean Architecture 的概念本身與語言無關,但具體實作方式必然會受到特定語言特性的影響。因此,本文的重點在於 Clean Architecture 的理念,以及我使用 Python 設計的實作方案。

  graph LR
    A[Entities] --> B(Use Cases);
    B --> C{Interface Adapters};
    C --> D[Frameworks and Drivers];

圖示說明: Clean Architecture 的核心組成部分及其互動關係。Entities 代表核心業務實體,Use Cases 代表業務使用案例,Interface Adapters 代表介面介面卡,Frameworks and Drivers 代表外部框架和驅動程式。

Clean Architecture 的核心思想是將軟體系統劃分為相互獨立的層級,每一層都具有明確的職責,並遵循依賴規則。內層不依賴外層,外層依賴內層。這種設計模式有效地隔離了核心業務邏輯與外部細節,使得系統更具彈性,更易於適應變化。

# entities.py
class SatelliteImage:
    def __init__(self, data):
        self.data = data

    def process(self):
        # 核心影像處理邏輯
        pass

# use_cases.py
class ProcessImage:
    def __init__(self, image_repository):
        self.image_repository = image_repository

    def execute(self, image_id):
        image = self.image_repository.get(image_id)
        image.process()
        self.image_repository.save(image)


# interface_adapters.py
class ImageRepository:
    def get(self, image_id):
        # 從資料函式庫或其他來源取得影像資料
        pass

    def save(self, image):
        # 將影像資料儲存到資料函式庫或其他來源
        pass

內容解密: 以上程式碼展示了 Clean Architecture 的 Python 實作示例。SatelliteImage 代表核心業務實體,ProcessImage 代表業務使用案例,ImageRepository 代表介面介面卡。業務使用案例透過介面介面卡與外部資料函式庫或其他來源互動。

Clean Architecture 並非一成不變的教條,而是一種靈活的設計理念。在實際應用中,需要根據具體情況進行調整。本文旨在提供一個 Clean Architecture 的 Python 實踐,希望能幫助讀者更好地理解和應用這一設計理念,構建更健壯、更易於維護的軟體系統。

在探索 Clean Architecture 的過程中,我深刻體會到其在提升軟體品質方面的巨大價值。它不僅是一種架構模式,更是一種設計思維,引導我們以更清晰、更系統的方式思考軟體設計。我相信,Clean Architecture 將會在軟體開發領域發揮越來越重要的作用。

尋找完美租屋:Rent-o-Matic 系統深度解析

在這個資訊爆炸的時代,找到理想的租屋處如同大海撈針。本文將深入剖析 Rent-o-Matic 系統,一個根據 Clean Architecture 設計的租屋平台,帶您探索其如何有效率地處理租屋資訊,並提供使用者最佳的租屋體驗。

Clean Architecture 的核心概念

Clean Architecture 強調關注點分離和控制反轉,這些概念對於開發一個穩健、易維護的系統至關重要。以下,我們將以 Rent-o-Matic 系統為例,逐步解析資料在系統中是如何流動的。

資料流動:從使用者請求到系統回應

想像一下,您正在瀏覽 Rent-o-Matic 網站(https://www.rentomatic.com),想要尋找可出租的房間。您在搜尋欄輸入條件,例如 /rooms?status=available,按下 Enter 鍵後,瀏覽器會傳送一個 HTTP 請求到 Rent-o-Matic 系統。

系統中的 Web 框架會接收這個 HTTP 請求,並解析其中的關鍵資訊,例如 /rooms 代表使用者想要檢視房間列表,status=available 表示使用者只對可出租的房間感興趣。

Web 框架將這些資訊傳遞給 Use Case,Use Case 是 Clean Architecture 中的核心元件,負責實作業務邏輯。在這個例子中,Use Case 的任務是根據 status 引數的值篩選出符合條件的房間。

業務邏輯:系統的核心價值

業務邏輯是系統的靈魂,它定義了系統如何處理資料、提供服務。在 Rent-o-Matic 系統中,業務邏輯包含了如何搜尋房間、如何根據條件篩選房間、如何顯示房間資訊等等。

Use Case 實作了業務邏輯中的一個特定部分,例如根據 status 引數搜尋房間。這意味著 Use Case 需要從系統中提取所有房間,並根據條件進行篩選,最終將結果傳回給 Web 框架。

Web 框架:連線使用者和系統的橋樑

Web 框架在 HTTP 協定的領域中運作,它負責接收使用者的 HTTP 請求,並將請求轉換為系統可以理解的格式。在 Use Case 處理完業務邏輯後,Web 框架會將結果轉換為 HTTP 回應,並發送回使用者的瀏覽器。

  graph LR
    A[使用者瀏覽器] --> B(HTTP 請求);
    B --> C{Web 框架};
    C --> D[Use Case];
    D --> E{資料函式庫};
    E --> D;
    D --> C;
    C --> F(HTTP 回應);
    F --> A;

內容解密: 上圖展示了 Rent-o-Matic 系統中資料的流動過程。使用者瀏覽器傳送 HTTP 請求,Web 框架接收請求並將其傳遞給 Use Case。Use Case 與資料函式庫互動,取得所需資料,並將處理結果傳回給 Web 框架。最後,Web 框架將結果轉換為 HTTP 回應,發送回使用者瀏覽器。

透過這樣的架構,Rent-o-Matic 系統實作了關注點分離,使每個元件各司其職,提高了系統的可維護性和可擴充套件性。

在接下來的文章中,我們將更深入地探討 Rent-o-Matic 系統的各個元件,以及 Clean Architecture 的更多細節。敬請期待!

解耦合的藝術:開發清晰的系統架構

系統架構的核心目標在於職責分離,讓不同職責和領域保持獨立。Web 框架專注於處理 HTTP 協定,由專注於系統特定部分的程式設計師維護。若將業務邏輯加入 Web 框架,會混淆兩個截然不同的領域,增加系統耦合性。

關注點分離的重要性

系統的不同部分應管理流程的不同環節。當系統中兩個獨立的部分處理相同資料或流程的相同部分時,它們就會耦合。雖然耦合不可避免,但兩個元件之間的耦合度越高,在不影響另一個元件的情況下更改其中一個元件就越困難。

分層架構能降低系統維護成本,使單個元件更易於測試和替換。以下圖為例,說明如何提取可用狀態的房間資訊:

  graph LR
    A[Use Case] --> B{Storage Interface}
    B --> C[PostgreSQL]
    B --> D[File System]
    B --> E[Network Endpoint]

內容解密: 上圖展現了 Use Case 如何透過 Storage Interface 與不同的資料來源互動,Storage Interface 作為一個抽象層,遮蔽了底層資料來源的具體實作細節,降低了 Use Case 與資料來源的耦合度。

在這個案例中,Use Case 需要從資料來源提取所有可用狀態的房間。這是業務邏輯,在本例中非常簡單,可能只包含對屬性值的簡單過濾。但實際情況可能更複雜,例如根據推薦系統的排序,這可能需要 Use Case 連線到更多元件。

Use Case 需要處理的資訊儲存在某處,我們稱之為儲存系統。儲存系統可以是檔案、資料函式庫(關聯式或非關聯式)、網路端點或遠端感測器。

抽象化設計的精髓

設計系統時,必須以抽象或建構區塊的方式思考。元件在系統中扮演特定角色,而與其具體實作無關。抽象級別越高,元件的細節就越少。高階抽象不考慮實際問題,因此抽象設計必須使用特定解決方案或技術來實作。

為簡單起見,我們使用 PostgreSQL 作為範例,但請記住更通用的情況。

控制反轉與介面設計

如果在 Use Case 中硬編碼對特定系統的呼叫(例如使用 SQL),則兩個元件將緊密耦合,這是我們在系統設計中應避免的。耦合的元件不是獨立的,它們緊密相連,一個元件的更改會強制另一個元件也進行更改,反之亦然。這也意味著測試元件更加困難,因為一個元件不能脫離另一個元件而存在。

如何避免強耦合?一個簡單的解決方案是控制反轉。控制反轉分為兩個階段:

  1. 使用標準介面包裝被呼叫的物件(在本例中為資料函式庫)。這是目標的每個實作所共用的一組功能,每個介面都將功能轉換為對包裝實作的特定語言的呼叫。

  2. 修改呼叫者(Use Case),避免硬編碼對特定實作的呼叫,因為這又會耦合兩者。Use Case 接受一個傳入物件作為其建構函式的引數,並在建立時接收介面卡的具體例項。

  sequenceDiagram
    participant Use Case
    participant Storage Interface
    participant Database

    Use Case->>Storage Interface: list_rooms_with_status(available)
    Storage Interface->>Database: execute SQL query
    Database-->>Storage Interface: query result
    Storage Interface-->>Use Case: list of rooms

內容解密: 此序列圖展示了 Use Case 如何透過 Storage Interface 與資料函式庫互動。Use Case 只需呼叫 list_rooms_with_status 方法,而無需關心底層資料函式庫的具體實作。Storage Interface 負責將方法呼叫轉換為 SQL 查詢,並將結果傳回給 Use Case。

現在,Use Case 與介面卡連線,並知道介面,它可以呼叫入口點 list_rooms_with_status 並傳遞狀態 available。介面卡知道儲存系統的細節,因此它將方法呼叫和引數轉換為提取請求資料的特定呼叫(或一組呼叫),然後將它們轉換為 Use Case 預期的格式。例如,它可能會傳回一個表示房間的字典 Python 列表。

此時,Use Case 需要應用其餘的業務邏輯(如果需要),並將結果傳回給 Web 框架。Web 框架將從 Use Case 接收的資料轉換為 HTTP 回應。在本例中,由於我們考慮的是網站使用者明確要存取的端點,因此 Web 框架將在回應主體中傳回一個 HTML 頁面。

透過控制反轉和介面設計,我們成功地解耦了 Use Case 和儲存系統,提高了系統的可維護性和可測試性。這種設計模式也讓系統更具彈性,可以輕鬆適應不同的儲存系統,而無需修改 Use Case 的核心業務邏輯。

網頁框架如何傳回 HTTP 回應資料

在設計系統時,不同階段的明確區分和資料轉換至關重要。通用資料格式的使用是我們在電腦系統各元件之間實作獨立性或鬆散耦合的方式之一。

為了更好地理解鬆散耦合對程式設計師的意義,讓我們考慮以下情況。在先前的段落中,我舉了一個使用 Web 框架作為使用者介面和關聯式資料函式庫作為資料來源的系統示例,但如果前端部分是命令列介面,會發生什麼變化?如果用另一種型別的資料來源(例如一組文字檔案)代替關聯式資料函式庫,又會發生什麼變化?

以下圖表說明瞭 Web 框架被命令列介面 (CLI) 取代的情況:

  graph LR
    A[使用者] --> B(命令列介面 CLI);
    B --> C{使用案例};
    C --> D(資料存取層);
    D --> E[資料儲存];

下圖則顯示了資料函式庫被更簡單的根據檔案的儲存取代的情況:

  graph LR
    A[使用者] --> B(網頁框架);
    B --> C{使用案例};
    C --> D(資料存取層);
    D --> E[檔案儲存];

如您所見,這兩種更改都需要替換某些元件。畢竟,我們需要不同的程式碼來管理命令列而不是網頁。但是,系統的外部形狀沒有改變,資料流的方式也沒有改變。我們建立了一個系統,其中使用者介面(Web 框架、命令列介面)和資料來源(關聯式資料函式庫、文字檔案)是實作的細節,而不是其核心部分。

然而,分層架構的主要直接優勢是可測試性。當您明確區分元件時,您就明確地建立了每個元件必須接收和產生的資料,因此理想情況下您可以斷開單個元件的連線並單獨對其進行測試。讓我們以我們新增的 Web 框架元件為例,並暫時忘記架構的其餘部分。理想情況下,我們可以將測試器連線到其輸入和輸出,如下圖所示:

  graph LR
    A[HTTP 請求] --> B(網頁框架);
    B --> C[使用案例];
    C --> D[資料];
    B --> E[HTTP 回應];

我們知道 Web 框架會收到一個 HTTP 請求,其中包含特定的目標和特定的查詢字串,並且它必須呼叫使用案例上的方法並傳遞特定的引數。當使用案例傳回資料時,Web 框架必須將其轉換為 HTTP 回應。

由於這是一個測試,我們可以有一個虛擬使用案例,即一個物件,它只是模仿使用案例的功能,而沒有真正實作業務邏輯。然後,我們將測試 Web 框架是否使用正確的引數呼叫方法,以及 HTTP 回應是否包含正確格式的正確資料,所有這些都將在不涉及系統任何其他部分的情況下發生。

現在我們已經對系統進行了概覽,接下來讓我們探討其元件及其背後的概念。在下一篇文章中,我將詳細介紹稱為「乾淨架構」的設計原則如何幫助有效地實作和使用關注點分離、抽象、實作和控制反轉等概念。

乾淨架構的組成部分

在本文中,我將分析統稱為「乾淨架構」的一組軟體設計原則。雖然這個特定名稱是由 Robert Martin 引入的,但它所推崇的概念是軟體工程的一部分,並且已經成功地使用了數十年。

在探討它們的可能實作(這是本文的核心)之前,我們需要更深入地分析乾淨架構的結構以及您可以根據其設計的系統中找到的元件。

分而治之

精心設計的系統的主要目標之一是實作控制。從這個角度來看,軟體系統與人類工作社群(如辦公室或工廠)沒有什麼不同。在這樣的環境中,工作人員交換資料或實體物件來建立和交付最終產品,無論是物件還是服務。工作人員需要資訊和資源來執行自己的工作,但最重要的是,他們需要清楚地瞭解自己的職責。

雖然在人類社會中,我們重視主動性和創造力,但在軟體系統等機器中,元件不應該能夠執行在系統設計時未明確說明的任何操作。軟體不是活的,儘管近年來人工智慧取得了令人印象深刻的成就,但我仍然相信人類身上有一種火花是單靠程式碼無法複製的。

無論我們對人工智慧的立場如何,我認為我們都同意,如果職責明確,系統會更好地工作。無論我們處理的是軟體還是人類社群,不清楚元件可以或應該做什麼總是很危險的,因為影響和控制的範圍自然會重疊。這可能會導致各種問題,從簡單的效率低下到完全的僵局。

提高系統秩序和控制力的一個好方法是將其拆分為子系統,在它們之間建立清晰而嚴格的邊界,以規範資料交換。這是政治概念(分而治之)的延伸,它指出,治理一組相互連線的小型系統比治理單個複雜系統更簡單。

在我們在上一篇文章中設計的系統中,元件在被呼叫時期望接收什麼總是清楚的,並且以破壞系統結構的方式交換資料也是不可能的(或至少是禁止的)。

您必須記住,軟體系統與工廠或辦公室並不完全相同。每當我們討論機器時,我們都必須考慮它們的工作方式(執行時)和它們的構建方式或將被修改的方式(開發時)。原則上,電腦不在乎資料來自哪裡以及資料去向何方。另一方面,必須構建和維護系統的人員需要清楚地瞭解資料流,以避免引入錯誤或降低效能。

資料型別

資料型別在系統中扮演著重要的角色,它是我們封裝和傳輸資訊的方式。特別是當我們討論軟體系統時,我們需要確保不同系統共用的型別對所有系統都是已知的。實際上,對資料型別和格式的瞭解是一種耦合形式。想想人類語言:如果你必須與聽眾交談,你必須使用他們理解的語言,這使你與你的聽眾耦合。這本章(暫定)是用英文寫的,這意味著我與講英語的讀者是耦合的。如果世界上所有講英語的人都突然決定忘記英語,並用義大利語代替它,我應該從頭開始寫這本章(但肯定會更輕鬆)。

因此,當我們考慮一個軟體系統時,我們需要了解哪一部分定義了型別和資料格式(「語言」),並確保產生的依賴關係不會妨礙實作者。在上一篇文章中,我們發現系統中有一些元件應被視為最重要的,並且代表系統的核心(使用案例),而其他元件則不太重要,通常被視為實作細節。再次提醒,稱它們為「細節」並不意味著它們不重要或它們的實作微不足道,而是用不同的實作替換它們不會影響系統的核心(業務邏輯)。

因此,存在一個由元件之間的依賴關係產生的元件層次結構。一些元件在設計之初就已定義,並且不依賴於任何其他元件,而其他元件則會在稍後出現並依賴於它們。當涉及資料型別時,產生的依賴關係不能破壞此層次結構,因為這會重新引入元件之間的耦合。 讓我們回到商店從批發商購買商品、陳列在貨架上並銷售給顧客的例子。這裡有兩個元件之間明顯的依賴關係:「商店」元件依賴於「批發商」元件,因為資料(「商品」)是從後者流向前者的。商店貨架的大小又取決於商品(種類別)的大小,而這是由批發商定義的,這也遵循了我們已經建立的依賴關係。

如果商品的大小是由商店定義的,那麼就會出現另一個與我們已建立的依賴關係相反的依賴關係,使得批發商依賴於商店。請注意,在軟體系統中,這並不是迴圈依賴,因為第一個是概念上的依賴,而第二個是在編譯時發生的語言級別的依賴。無論如何,擁有兩個相反的依賴關係肯定會造成混淆,並且使得替換「周邊」元件(例如商店)變得困難。

乾淨架構的主要層次

乾淨架構試圖透過分層方法來捕捉元件的概念層次結構和型別層次結構。在乾淨架構中,系統的元件被分類別並屬於特定的層次,並具有關於屬於相同或不同層次的元件之間的通訊規則。特別是,乾淨架構是一個球形結構,內層(較低層)完全被外層(較高層)包圍,而前者不知道後者的存在。

在電腦科學中,「較低」和「較高」幾乎總是指的是抽象級別,而不是元件對系統的重要性。系統的每個部分都很重要,否則它就不會存在。

讓我們看一下圖中所示的主要層次,同時記住特定的實作可能需要建立新的層次或將其中一些層次拆分為多個層次。

實體層

乾淨架構的這一層包含領域模型的表示,即系統需要與之互動的所有內容,並且足夠複雜以至於需要特定的表示。例如,Python 中的字串是複雜與非常強大的物件。它們提供了許多現成的方法,因此通常不需要為它們建立領域模型。但是,如果您的專案是分析中世紀手稿的工具,您可能需要隔離句子及其特徵,此時定義特定實體可能是合理的。

由於我們使用 Python,這一層可能包含類別,以及簡化與它們互動的方法。然而,理解這一層中的模型與 Django 等框架的常用模型不同,這一點非常重要。這些模型沒有連線到儲存系統,因此無法使用它們自己的方法直接儲存或查詢,它們不包含將自身轉儲為 JSON 字串的方法,它們也沒有連線到任何表示層。它們是所謂的輕量級模型。

這是最內層。實體之間相互瞭解,因為它們位於同一層,因此架構允許它們直接互動。這意味著表示實體的 Python 類別之一可以直接使用另一個,例項化它並呼叫它的方法。但是,實體不知道外層中的任何內容。它們無法呼叫資料函式庫、存取表示框架提供的方法或例項化使用案例。

實體層提供了一個堅實的型別基礎,外層可以使用它來交換資料,它們可以被視為業務的詞彙。

使用案例層

正如我們之前所說,乾淨系統中最重要的部分是使用案例,因為它們實作了業務規則,這是系統本身存在的主要原因。使用案例是應用程式中發生的流程,您可以在其中使用領域模型來處理實際資料。例如,使用者登入、執行帶有特定篩選條件的搜尋,或使用者想要購買購物車內容時發生的銀行交易。

使用案例應該盡可能小。將小動作隔離到單獨的使用案例中非常重要,因為這會使整個系統更容易測試、理解和維護。使用案例可以完全存取實體層,因此它們可以直接例項化和使用它們。它們也可以相互呼叫,並且通常透過組合簡單的使用案例來建立複雜的使用案例。

閘道器層

這一層包含為外部系統定義介面的元件,這是對不實作業務規則的服務的通用存取模型。典型的例子是資料儲存,不同實作的內部細節可能非常不同。這些實作分享一個共同的介面,否則它們就不是相同概念的實作,而閘道器的任務就是公開它。

如果您還記得我開始的簡單例子,這就是資料函式庫介面所在的位置。閘道器可以存取實體,因此介面可以自由地接收和傳回在該層中定義型別的物件,因為它們可以自由地存取使用案例。然而,閘道器用於掩蓋外部系統的實作,因此閘道器很少呼叫使用案例,因為這可以由外部系統本身完成。閘道器層與外部系統層緊密相連,這就是為什麼兩者之間用虛線隔開的原因。

外部系統層

架構的這一部分由實作上一層中定義的介面的元件組成。相同的介面可能由一個或多個具體元件實作,因為您的系統可能希望同時支援該介面的多個實作。例如,您可能希望透過 HTTP API 和命令列介面公開一些使用案例,或者您希望根據某些組態值提供對不同型別儲存的支援。

請記住,「外部」形容詞並不總是意味著系統是由其他人開發的,或者它是一個像 Web 框架或資料函式庫這樣的複雜系統。這個詞具有拓撲含義,它表明我們正在談論的系統是架構核心的外圍,也就是說它不實作業務邏輯。因此,我們可能希望使用內部開發的訊息傳遞系統向特定服務的客戶傳送通知,但這又只是一個表示層,除非我們的業務專注於建立通知系統。

外部系統可以完全存取閘道器、使用案例和實體。雖然很容易理解與閘道器的關係(閘道器是用於包裝特定系統的),但外部系統應該如何處理使用案例和實體可能不太清楚。對於使用案例,外部系統通常是觸發它們的系統部分,是使用者執行業務邏輯的方式。使用者點選按鈕、存取 URL 或執行命令,是與直接執行使用案例的外部系統互動的典型例子。至於實體,外部系統可以直接處理它們,例如將它們傳回 JSON 有效負載中,或將輸入資料對映到領域模型中。

我想指出使用案例使用的外部系統和想要呼叫使用案例的外部系統之間的區別。在第一種情況下,通訊方向是向外的,我們知道在乾淨架構中,沒有介面就無法向外。因此,當我們從使用案例存取外部系統時,我們總是需要一個介面。相反,當外部系統想要呼叫使用案例時,通訊方向是向內的,這是允許的。

  graph LR
    subgraph 實體層["實體層 (Entities)"]
        A[實體 A] --> B[實體 B]
    end
    subgraph 使用案例層["使用案例層 (Use Cases)"]
        UC1[使用案例 1] --> UC2[使用案例 2]
        UC1 --> 實體層
        UC2 --> 實體層
    end
    subgraph 閘道器層["閘道器層 (Gateways)"]
        G1[閘道器 1] --> 使用案例層
        G1 --> 實體層
    end
    subgraph 外部系統層["外部系統層 (External Systems)"]
        ES1[外部系統 1] --> 閘道器層
        ES1 --> 使用案例層
        ES1 --> 實體層
    end

    閘道器層 -.-> 外部系統實作["外部系統實作"]

內容解密: 上圖展示了乾淨架構的四個主要層次以及它們之間的依賴關係。實體層位於最內層,定義了系統的核心業務模型。使用案例層圍繞實體層,定義了系統的核心業務邏輯。閘道器層則定義了與外部系統互動的介面,而外部系統層則包含了這些介面的具體實作。箭頭方向表示依賴關係,例如,使用案例層依賴於實體層,表示使用案例可以使用實體層定義的模型。虛線表示閘道器層和外部系統實作之間的緊密聯絡,但外部系統實作並不屬於乾淨架構的核心部分。