首頁  >  文章  >  後端開發  >  為什麼清潔架構在 Golang 中舉步維艱以及什麼更有效

為什麼清潔架構在 Golang 中舉步維艱以及什麼更有效

Mary-Kate Olsen
Mary-Kate Olsen原創
2024-11-07 01:59:02573瀏覽

Why Clean Architecture Struggles in Golang and What Works Better

Golang 作為一種優先考慮簡單性的快速、高效語言,贏得了良好的聲譽,這也是它如此普遍用於後端服務、微服務和基礎設施工具的原因之一。然而,隨著越來越多的開發人員從 Java 和 C# 等語言過渡到 Go,出現了關於實作清潔架構的問題。對於習慣使用 Clean Architecture 基於層的方法建立應用程式的人來說,將相同的原則應用於 Go 會感覺很直觀。然而,正如我們將要探討的那樣,嘗試在 Go 中實現簡潔架構通常會適得其反。相反,我們將研究一種針對 Go 的優勢量身定制的結構,該結構更直接、靈活,並且符合 Go 的「保持簡單」理念。

為什麼乾淨的架構在 Go 中感覺格格不入

由鮑伯叔叔(羅伯特·C·馬丁)倡導的清潔架構的目標是創建模組化、可測試且易於擴展的軟體。這是透過強制各層之間的關注點分離來實現的,同時核心業務邏輯與外部關注點保持隔離。雖然這在 Java 等高度物件導向的語言中效果很好,但在 Go 中卻引入了摩擦。原因如下:

1. Go 的極簡主義與過度抽像作鬥爭

在 Go 中,非常強調可讀性、簡單性和減少開銷。 Clean Architecture 引進了一層又一層的抽象:介面、依賴倒置、複雜的依賴注入和業務邏輯的服務層。然而,這些額外的層在 Go 中實現時往往會增加不必要的複雜性。

我們以 Kubernetes 為例。 Kubernetes 是一個用 Go 建構的大型項目,但它不依賴清潔架構原則。相反,它採用扁平的、面向功能的結構,重點圍繞包和子系統。您可以在 Kubernetes GitHub 儲存庫中看到這一點,其中套件是按功能而不是嚴格的層組織的。透過根據功能對程式碼進行分組,Kubernetes 無需複雜的抽象即可實現高度模組化。

Go 哲學優先考慮實用性和速度。該語言的創建者一直主張避免過度架構,傾向於簡單的實作。如果抽像不是絕對必要的,那麼它就不屬於 Go 程式碼。 Go 的創建者甚至設計了沒有繼承的語言,以避免過度設計的陷阱,鼓勵開發人員保持設計乾淨清晰。

2. 依賴注入受到設計的限制

清潔架構嚴重依賴依賴注入來解耦不同的層並使模組更易於測試。在 Java 等語言中,借助 Spring 等框架,DI 自然成為生態系統的一部分。這些框架可自動處理 DI,讓您可以輕鬆地將依賴項連接在一起,而不會使您的程式碼變得混亂。

然而,Go 缺乏原生 DI 系統,大多數 Go 的 DI 函式庫要么過於複雜,要么感覺不慣用。 Go 依賴透過建構函數或函數參數進行明確依賴注入,保持依賴關係清晰並避免「魔法」隱藏在 DI 容器中。 Go 的方法使程式碼更加明確,但這也意味著如果引入太多層,依賴關係管理就會變得難以管理且冗長。

例如,在 Kubernetes 中,您看不到複雜的 DI 框架或 DI 容器。相反,依賴項是使用建構函數以簡單的方式註入的。這種設計保持了程式碼的透明性,避免了 DI 框架的缺陷。 Golang 鼓勵僅在真正有意義的地方使用 DI,這就是為什麼 Kubernetes 避免僅僅為了遵循模式而創建不必要的介面和依賴項。

3. 層數過多,測試變得更複雜

Go 中乾淨架構的另一個挑戰是它會使測試變得不必要的複雜。例如,在 Java 中,清潔架構支援健壯的單元測試,並大量使用依賴項的模擬。模擬允許您隔離每一層並獨立測試它。然而,在 Go 中,建立模擬可能很麻煩,而 Go 社群通常傾向於整合測試或盡可能使用實際實作進行測試。

在生產級 Go 專案中,例如 Kubernetes,測試不是透過隔離每個元件來處理的,而是透過專注於涵蓋現實場景的整合和端到端測試來進行。透過減少抽象層,像 Kubernetes 這樣的 Go 專案可以實現較高的測試覆蓋率,同時保持測試接近實際行為,從而在生產中部署時更有信心。

Golang 的最佳架構方法

那麼,如果 Clean Architecture 不太適合 Go,那麼什麼才適合呢?答案在於更簡單、更實用的結構,強調包並專注於模組化而不是嚴格的分層。 Go 的有效架構模式是基於六角形架構,通常稱為連接埠和適配器。這種架構允許模組化和靈活性,而無需過多的分層。

Golang 標準專案佈局是在 Go 中建立生產就緒專案的一個很好的起點。這種結構為按目的和功能而不是按架構層組織代碼提供了基礎。

Go 專案結構:一個實際範例

你說得完全正確!使用以套件為中心的方法建立 Go 項目,其中功能按套件而不是分層資料夾結構進行分解,更符合 Go 的設計原則。在 Go 中,建立內聚的套件更慣用,每個套件都封裝自己的模型、服務和儲存庫,而不是逐層建立頂級目錄(例如控制器、服務、儲存庫)。這種基於套件的方法減少了耦合併保持程式碼模組化,這對於生產級 Go 應用程式至關重要。

讓我們來看看適合 Go 的精緻的、以包為中心的結構:

/myapp
   /cmd                   // Entrypoints for different executables (e.g., main.go)
      /myapp-api
         main.go          // Entrypoint for the main application
   /config                // Configuration files and setup
   /internal              // Private/internal packages (not accessible externally)
      /user               // Package focused on user-related functionality
         models.go        // Data models and structs specific to user functionality
         service.go       // Core business logic for user operations
         repository.go    // Database access methods for user data
      /order              // Package for order-related logic
         models.go        // Data models for orders
         service.go       // Core order-related logic
         repository.go    // Database access for orders
   /pkg                   // Shared, reusable packages across the application
      /auth               // Authorization and authentication package
      /logger             // Custom logging utilities
   /api                   // Package with REST or gRPC handlers
      /v1
         user_handler.go  // Handler for user-related endpoints
         order_handler.go // Handler for order-related endpoints
   /utils                 // General-purpose utility functions and helpers
   go.mod                 // Module file

基於封裝的結構中的關鍵組件

  1. /cmd

此資料夾是應用程式入口點的常規位置。這裡的每個子資料夾代表應用程式的不同可執行檔。例如,在微服務架構中,每個服務都可以擁有自己的目錄及其 main.go。這裡的程式碼應該是最少的,只負責引導和設定依賴項。

  1. /配置

儲存設定檔和設定邏輯,例如載入環境變數或外部配置。該套件還可以定義應用程式配置的結構。

  1. /內部

這是應用程式的核心邏輯所在,根據功能分為套件。 Go 限制從外部模組存取內部包,使這些包對應用程式保持私有。每個包(例如使用者、訂單)都是獨立的,具有自己的模型、服務和儲存庫。這是 Go 的封裝哲學、無需過多分層的關鍵。

  • /internal/user – 管理所有與使用者相關的功能,包括模型(資料結構)、服務(商業邏輯)和儲存庫(資料庫互動)。這將用戶相關的邏輯保留在一個套件中,使其易於維護。

  • /internal/order – 同樣,這個包裝了訂單相關的程式碼。每個功能區域都有自己的模型、服務和儲存庫。

  1. /pkg

pkg 包含可在應用程式中使用的可重複使用元件,但不特定於任何一個套件。可以獨立使用的庫或實用程序,例如用於身份驗證的 auth 或用於自訂日誌記錄的 logger,都保存在這裡。如果這些套件特別有用,以後也可以將它們提取到自己的模組中。

  1. /api

API 套件充當 HTTP 或 gRPC 處理程序的層。這裡的處理程序處理傳入的請求、呼叫服務並回傳回應。按 API 版本(例如 v1)對處理程序進行分組是版本控制的良好實踐,有助於隔離未來的變更。

  1. /utils

通用實用程序,不與任何特定套件綁定,但在程式碼庫中提供橫切用途(例如,日期解析、字串操作)。保持最小並專注於純粹的實用功能是有幫助的。

用戶包的範例程式碼佈局

為了說明結構,我們仔細看看使用者包的樣子:

模型.go

/myapp
   /cmd                   // Entrypoints for different executables (e.g., main.go)
      /myapp-api
         main.go          // Entrypoint for the main application
   /config                // Configuration files and setup
   /internal              // Private/internal packages (not accessible externally)
      /user               // Package focused on user-related functionality
         models.go        // Data models and structs specific to user functionality
         service.go       // Core business logic for user operations
         repository.go    // Database access methods for user data
      /order              // Package for order-related logic
         models.go        // Data models for orders
         service.go       // Core order-related logic
         repository.go    // Database access for orders
   /pkg                   // Shared, reusable packages across the application
      /auth               // Authorization and authentication package
      /logger             // Custom logging utilities
   /api                   // Package with REST or gRPC handlers
      /v1
         user_handler.go  // Handler for user-related endpoints
         order_handler.go // Handler for order-related endpoints
   /utils                 // General-purpose utility functions and helpers
   go.mod                 // Module file

服務.go

// models.go - Defines the data structures related to users

package user

type User struct {
    ID       int
    Name     string
    Email    string
    Password string
}

儲存庫.go

// service.go - Contains the core business logic for user operations

package user

type UserService struct {
    repo UserRepository
}

// NewUserService creates a new instance of UserService
func NewUserService(repo UserRepository) *UserService {
    return &UserService{repo: repo}
}

func (s *UserService) RegisterUser(name, email, password string) error {
    // Business logic for registering a user
    newUser := User{Name: name, Email: email, Password: password}
    return s.repo.Save(newUser)
}

為什麼這種基於套件的結構非常適合 Go

這個結構與 Go 的習慣用法非常吻合:

  1. 封裝

透過基於功能來組織包,程式碼自然地被封裝和模組化。每個包都擁有自己的模型、服務和儲存庫,從而保持程式碼的內聚性和高度模組化。這使得導航、理解和測試各個套件變得更加容易。

  1. 最小介面

介面僅在套件邊界(例如 UserRepository)使用,它們對於測試和靈活性最有意義。這種方法減少了不必要的介面的混亂,這會使 Go 程式碼更難維護。

  1. 明確依賴注入

相依性是透過建構函式(例如 NewUserService)注入的。這使得依賴關係保持明確,並避免了對複雜依賴注入框架的需要,忠於 Go 的簡單性設計。

  1. /pkg 中的可重複使用性

pkg 目錄中的 auth 和 logger 等元件可以跨套件共享,從而提高可重複使用性,而無需過度耦合。

  1. 清晰的API結構

透過將處理程序分組到 /api 下,可以輕鬆擴展 API 層並隨著應用程式的增長添加新版本或處理程序。每個處理程序都可以專注於處理請求並與服務協調,保持程式碼模組化和整潔。

這種以套件為中心的結構可讓您在新增更多網域(例如產品、庫存)時進行擴展,每個網域都有自己的模型、服務和儲存庫。按領域的分離符合 Go 組織代碼的慣用方式,在嚴格的分層上保持簡單性和清晰度。

意見和現實世界的經驗

根據我使用 Go 的經驗,乾淨的架構通常會使程式碼庫變得複雜,而不會增加顯著的價值。當使用 Java 等語言建立大型企業級應用程式時,清潔架構往往很有意義,因為 Java 中有很多對 DI 的內建支持,並且管理深層繼承結構是一種常見需求。然而,Go 的極簡主義、簡單優先的心態以及簡單的並發和錯誤處理方法完全創建了一個不同的生態系統。

結論:擁抱 Go 的慣用架構

如果您有 Java 背景,那麼將乾淨架構應用於 Go 可能很誘人。然而,Go 的優勢在於簡單、透明和模組化,沒有過多的抽象。 Go 的理想架構優先考慮按功能、最小介面、明確 DI、實際測試和靈活性適配器組織的套件。

設計 Go 專案時,請參考 Kubernetes、Vault 和 Golang 標準專案佈局等現實範例。這些展示了當架構擁抱簡單而不是嚴格的結構時,Go 的強大之處。與其試圖讓 Go 適應乾淨的架構模式,不如擁抱像 Go 本身一樣簡單且有效率的架構。透過這種方式,您建立的程式碼庫不僅是慣用的,而且更易於理解、維護和擴展。

以上是為什麼清潔架構在 Golang 中舉步維艱以及什麼更有效的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn