Porto - 先進的軟體架構模式

dinghua發表於2017-07-18

Porto 是我在檢視 apiato 的文件時接觸到的,看到的第一眼就感覺“這是我找了很久的東西”。一直在思考複雜的專案應該怎麼架構,這個模式提供了很好的思路,從現實中的遠洋運輸船裡得到啟發,應用到軟體開發的思想裡,很有指導意義,翻譯了一些主要部分, 時間倉促,肯定很多地方不到位,只是希望對大家有一些幫助。

Github 地址 https://github.com/Mahmoudz/Porto

文章目錄

Porto 是一個現代化的軟體架構模式,旨在幫助開發人員以高度可維護的方式組織其程式碼。其主要目標是以可重用的方式組織業務邏輯。

Porto 是標準MVC的替代品,適用於大型和長期專案,因為它們往往隨著時間的推移具有更高的複雜性。

Porto 繼承了MVC,DDD,模組化和分層架構的概念。它堅持一系列方便的設計原則,如SOLID,OOP,LIFT,DRY,CoC,GRASP,泛化,高內聚和低耦合。

Porto 開始作為一個實驗架構,試圖解決在建造中大型網路專案時,開發者面臨的常見問題。儘管所有模組化架構都側重於框架通用元件的可重用性,但 Porto 專注於業務邏輯的可重用性。

  • 可跨多個類似的專案重複使用業務邏輯(容器)。
  • 任何開發人員易於理解(沒什麼神祕的)。
  • 易於維護(易於適應變化)。
  • 易於測試(測試驅動)。
  • 零技術債務(開發者之間的溝通不暢)。
  • 解耦程式碼(編輯 X 不會中斷 Y )。
  • 靈活的UI(從Web應用程式開始,隨後構建一個API,或者相反)
  • 非常有組織的程式碼庫。
  • 可擴充套件程式碼(易於修改和實現功能)。
  • 輕鬆的框架升級(應用程式和框架之間完全分離)。
  • 容易找到任何功能。
  • 內部元件之間有組織的聯絡。
  • 簡潔的類庫(遵循單一責任原則)。
  • 簡潔和清晰的開發工作流程。

Porto 包含2層(ContainersShip)和一套具有預定職責的“元件”。

這些圖層(資料夾)可以在您選擇的框架內的任何地方建立。

(例如:在Laravel PHP中,您可以將它們放在 app / 目錄下,或在根目錄下建立一個 src / 目錄)

頂層程式碼(業務邏輯)應該寫在 Container 層中。 而底層程式碼將存放在 Ship 層。

Container(頂層程式碼)間接依賴 Ship (底層程式碼)功能,不是相反。

層級圖

更形象一點:

file

Ship 層:

Ship 層包含 Ship 的引擎,該引擎會自動載入容器的所有元件。
它還包含所有 Container 元件可以使用的程式碼。

Ship 層在將應用程式程式碼與框架程式碼分離時起著重要作用。
因此,它有助於升級 “框架程式碼” 而不影響 “應用程式碼”。

PortoShip 層非常簡潔,它不包含常見的可重複使用的功能,如身份驗證或授權,所有這些功能都在自己分開的 Container 中。

Ship 檔案結構

Ship 層有以下資料夾:

  • Engine: 用在自動載入容器中的元件。
  • Parents:包含了容器中可能會用到的元件的父類。
  • 其他資料夾:提供了一些經常會用到的功能供 Container 呼叫。比如,全域性異常,應用中介軟體,全域性設定等等...

注意:所有 Container 的元件 必須 繼承自 Ship 層(在 Parents 資料夾)。

Container 層:

Containers 層用來放業務邏輯。

我們建議每一個 Container 只包含一個 Model,但有時你可能會需要更多。只是需要牢記兩個 Model 就需要兩個倉庫,兩個轉換器,等等...
除非你的兩個 Model 一定要同時使用,否則最好把他們分開放進2個 Container

Containers 檔案結構:

Container 1
    ├── Actions
    ├── Tasks
    ├── Models
    └── UI
        ├── WEB
        │   ├── Routes
        │   ├── Controllers
        │   └── Views
        ├── API
        │   ├── Routes
        │   ├── Controllers
        │   └── Transformers
        └── CLI
            ├── Routes
            └── Commands

Container 2
    ├── Actions
    ├── Tasks
    ├── Models
    └── UI
        ├── WEB
        │   ├── Routes
        │   ├── Controllers
        │   └── Views
        ├── API
        │   ├── Routes
        │   ├── Controllers
        │   └── Transformers
        └── CLI
            ├── Routes
            └── Commands

Containers 之間的互動

  • 一個 Container 可以依賴其他 Container
  • 一個控制器可以執行其他 Container 裡的任務。
  • 一個 Container 裡的模型可以和另一 Container 裡的模型有關聯關係.

一個 Container 可以包含很多的元件,元件一般被分為兩類:核心元件可選元件

核心元件:

幾乎所有的 Web 應用裡你都會用到以下元件:
路由 - 控制器 - 請求 - 動作 - 任務 - 模型 - 檢視 - 轉換器.

檢視: 應用裡有 html 頁面時會用到。

轉換器: App 提供 json 或 xml 資料時會用到。

核心控制元件互動圖

file

請求生命週期

這是一個典型的 API 請求呼叫

  1. User 通過 Route 檔案呼叫一個 Endpoint
  2. Endpoint 呼叫 Middleware 來處理身份認證。
  3. Endpoint 呼叫 Controller 方法.
  4. Request 注入 Controller 並自動執行表單驗證和許可權認證。
  5. Controller 呼叫 Action 並把請求的資料傳過去。
  6. Action 呼叫多個 Tasks 處理業務邏輯, {或者自己全部搞定}
  7. Tasks 執行業務邏輯 (每個 Task 只幹一件事)。
  8. ActionTasks 返回的資料再返回給 Controller
  9. ControllerView 或者 Transformer 組織響應並返回給 User

可選元件:

以下元件您可以按需選用,不過我們強烈你使用它們。

倉庫 - 異常 - Criterias - 策略 - 測試 - 中介軟體 - 服務提供者 - 事件 - 命令 - 資料遷移 - 資料 - 資料工廠 - Contracts - Traits - 任務...

路由(Routes)

路由是 HTTP 請求的第一個接收者。

路由的責任是把請求轉到集體的控制器方法去。

路由檔案應該包含 Endpoints(一種分辨 HTTP 請求的 URL 模式)。

當 HTTP 請求到達應用時,Endpoints會匹配 url,並呼叫對應的控制器方法。

原則:

  • 一共有三種理由:API 路由,Web 路由,命令列路由。
  • Web 路由和 API 路由應該分開,放在各自的資料夾裡。
  • Web 路由應該只包含 Web Endpoints (供瀏覽器請求),API 路由資料夾裡只包含 API Endpoint。
  • 每個 Container 應該有自己的路由。
  • 每個路由應該只有一個 Endpoint。
  • Endpoint 的功能只是呼叫控制器裡相應的方法,而不應該做其他任何事。

控制器(Controller)

控制器的責任是接受請求並提供正確的返回響應。

控制器的概念和 MVC(裡面的 C)是一樣的。但是有一些約束和約定。

原則:

  • 控制器不應該包含任何業務邏輯。
  • 控制器只做以下工作:
    1. 讀取請求資料
    2. 呼叫 Action
    3. 返回響應資料
  • 控制器不能呼叫 Task,只能呼叫 Action。
  • 控制器只能被路由呼叫。
  • 各個 Container 的 UI 資料夾都有各自的控制器。

請求(Requests)

Requests 是用來處理使用者的輸入,經常用來處理表單驗證和身份驗證。

Requests 是處理驗證的最佳地點,表單驗證的規則可以被應用到每個請求。

Request 還可以用來做許可權認證,比如判斷一個使用者是否有做某個操作的許可權。

原則:

  • Requests 可以放表單驗證/身份認證 規則。
  • Requests 只能在控制器裡被注入使用,在注入時自動執行驗證規則,如果不通過就立即丟擲異常。
  • Requests 還可以被用來做許可權認證,檢查使用者是否有許可權來執行某個請求。

動作(Actions)

Actions 的責任是執行應用裡的具體任務。

Actions 包含了業務邏輯。Actions 會通過呼叫 Task 來完成具體業務。

Actions 接受使用者輸入,按業務邏輯來處理,最終輸出處理結果。

Actions 不應該關心資料是怎麼來的,也不關心輸出的結果會用到哪裡。

檢視 Container 裡的 Actions 資料夾,就基本知道這個 Container 的功能了。看了所有的 Actions,你就能知道整個應用的功能了。

原則:

  • 每個 Action 只處理一個用例。
  • 一個 Action 可以從一個 Task 裡取到結果並傳入另一個 Task。
  • 一個 Action 可以呼叫多個 Task。
  • Action 的資料返回給控制器。
  • Actions不能直接返回請求響應,這是控制器的工作。
  • 一個 Action 可以呼叫另外一個 Action,但是不建議這麼做。
  • Action 主要供控制器呼叫,但是也可以用於 Event,Command 或者其他類。但是不應該被 Task 呼叫。
  • 每個 Action 只有一個名為 run 方法。

任務(Task)

Task 是用來存放 Action 之間共享的業務邏輯片段。

每個 Task 實現一小部分業務邏輯。

Task 不是必須的,但很多時候你會發現你需要它們。

比如說:我們在 Action 1 裡需要通過 ID 找到一條記錄,然後觸發事件。
在 Action 2 裡需要通過 ID 找到一條記錄,然後把它傳給一個 API。
在兩個 Action 裡,我們都需要“通過 ID 找到一條記錄”功能,我們就可以把這個做成一個 Task。

你發現你想要重用 Action 裡的程式碼時,就應該考慮是不是可以用 Task。

原則:

  • 每個 Task 只完成一個動作。
  • Task 不可以呼叫其他 Task,這樣會引發服務架構的混亂。
  • Task 不能呼叫 Action。
  • Task 應該只被 Action 呼叫(不一定是同一個 Container)
  • Task 一般只包含一個名為run 的方法,不過你要是願意也可以有多個。
  • Task 不能被控制器呼叫。

模型(Model)

Models 提供資料。 (MVC 裡的 M).

Model 負責處理資料,保證資料能被正確的存取。

原則:

  • Model 裡不能有業務邏輯。
  • 一個控制器可以有多個模型。
  • Model 可以定義和其它 Model 之間的關係。

檢視(Views)

View 裡包含了應用裡會用到的 HTML 部分。

View 最重要的功能是將資料和表現分離(MVC 裡的 V)。

原則:

  • Views應該只能用於 Web 控制器。
  • 根據實際情況,Views 可以被分開存放在多個檔案或資料夾。
  • 一個控制器可以有多個 Views 檔案。

轉換器(Transformers)

Transformers(是響應轉換器的縮寫)。

有點像是用於 JSON 響應的 Views,Views 是用來渲染 HTML,Transformers是用來格式化 JSON 資料。

Transformers 是把 Model轉換成陣列的類。

Transformers 把一個 Model 或者一組 Model 序列化成一個陣列。

原則:

  • 每個 API 請求都必須用 Transformer 來格式化響應資料。
  • 每個 Model 都應該有一個對應的 Transformer。
  • 一個 Container 可以有多個 Transformer。

子動作(Sub-Actions)

Sub-Action 是用來減少 Action 裡的重複程式碼。不要疑惑,Sub-Action 和 Task 不一樣。

Task 是 Action 之間共享的功能,SubActions 則是 Action 之間共享的一系列 Task。

Example: If an Action is calling Task1, Task2 and Task3. And another Action is calling Task2, Task3 and Task4. (And both Actions are throwing the same Exception when Task2 returns Foo). Would be very useful to extract that code into a SubAction, and make it reusable.

原則:

  • Sub-Action 必須呼叫 Task,如果 Sub-Action 不需要 Task 就完成了所有的業務邏輯,那它就不算是一個 Sub-Action。
  • Sub-Action 可以從一個 Task 裡獲取資料,再傳到另一個 Task。
  • 一個 Sub-Action 應該呼叫多個 Task。
  • Sub-Action 應該返回資料給 Action。
  • Sub-Action 不能直接返回請求響應。(那是控制器的活)
  • Sub-Action 不能呼叫其他的 Sub-Action。
  • Sub-Action 一般用於 Action,但是也可以用於 Event,Commands或者其他類。但是不能用於 Controller 和 Task。
  • 每個 Sub-Action 應該只有一個名為 run的方法。
  • 如果你能看到這一句,說明你真的認真看了,也不枉我打了那麼多字,謝謝,第一個看到這裡的@我,給你發紅包。

典型的 Conainer 示例:

Container
    ├── Actions
    ├── Tasks
    ├── Models
    ├── Events
    ├── Policies    
    ├── Exceptions
    ├── Contracts
    ├── Traits
    ├── Jobs
    ├── Notifications
    ├── Providers
    ├── Configs
    ├── Data
    │   ├── Migrations
    │   ├── Seeders
    │   ├── Factories
    │   ├── Criterias
    │   └── Repositories
    ├── Tests
    │   ├── Unit
    │   └── Traits
    └── UI
        ├── API
        │   ├── Routes
        │   ├── Controllers
        │   ├── Requests
        │   ├── Transformers
        │   └── Tests
        │       └── Functional
        ├── WEB
        │   ├── Routes
        │   ├── Controllers
        │   ├── Requests
        │   ├── Views
        │   └── Tests
        │       └── Acceptance
        └── CLI
            ├── Routes
            ├── Commands
            └── Tests
                └── Functional

相關文章