Go 應用程序設計標準

01 介紹

衆所周知 Go 語言官方成員 Russ Cox 曾向 Go 社區迴應並沒有 Go 應用程序設計標準。但是,爲什麼本文還要使用這個標題呢?

因爲團隊達成一個共識(標準),制定一些團隊成員都要遵循的規則,可以使我們的應用程序更容易維護。本文介紹一下我們應該怎麼組織我們的代碼,制定團隊的 Go 應用程序設計標準。

需要注意的是,它不是核心 Go 開發團隊制定的官方標準。

02 定義 domain 包

爲什麼需要定義 domain 包?因爲我們開發的 Go 應用程序,可能不只是包含一個功能模塊,並且可能不同的功能模塊之間還需要互相調用,所以,我們需要 domain(領域)包,例如我們開發一個博客應用程序,我們的 domain 包括用戶、文章、評論等。這些不依賴我們使用的底層技術。

需要注意的是,domain 包不應該包含方法的實現細節,比如操作數據庫或調用其他微服務,並且 domain 包不可以依賴應用程序中的其他包。

我們可以定義 domain 包,把結構體和接口放在 domain 包,例如:

package domain

import "context"

type User struct {
 Id       int64  `json:"id"`
 UserName string `json:"user_name" xorm:"varchar(30) notnull default '' unique comment('用戶名')"`
 Email    string `json:"email" xorm:"varchar(30) not null default '' index comment('郵箱')"`
 Password string `json:"password" xorm:"varchar(60) not null default '' comment('密碼')"`
 Created  int    `json:"created" xorm:"index created"`
 Updated  int    `json:"updated" xorm:"updated"`
 Deleted  int    `json:"deleted" xorm:"deleted"`
}

type UserUsecase interface {
 GetById(ctx context.Context, id int) (*User, error)
 GetByPage(ctx context.Context, count, offset int) ([]*User, int, error)
 Create(ctx context.Context, user *User) error
 Delete(ctx context.Context, id int) error
 Update(ctx context.Context, user *User) error
}

type UserRepository interface {
 GetById(ctx context.Context, id int) (*User, error)
 GetByPage(ctx context.Context, count, offset int) ([]*User, int, error)
 Create(ctx context.Context, user *User) error
 Delete(ctx context.Context, id int) error
 Update(ctx context.Context, user *User) error
}

細心的讀者朋友們可能已經發現,以上代碼在「Go 語言整潔架構實踐」一文中,它是被劃分到 models 包。是的,因爲當時我們的示例項目是 TodoList,它僅包含一個功能模塊。

但是,當我們開發一個包含多個功能模塊的應用程序時,爲了方便功能模塊之間相互調用,更建議將所有功能模塊的結構體和接口存放到 domain 包。

03 按照依賴關係劃分包

在「Go 語言整潔架構實踐」一文中,提到在 Repository 層存放操作數據庫和調用微服務的代碼,我們可以在 Repository 層按照依賴關係劃分包,比如我們的應用程序需要操作 MySQL 數據庫,我們可以定義一個 mysql 包。

示例代碼:

package mysql

import (
 "context"
 "go_standard/domain"
 "xorm.io/xorm"
)

type mysqlUserRepository struct {
 Conn *xorm.Engine
}

func NewMysqlUserRepository(Conn *xorm.Engine) domain.UserRepository {
 _ = Conn.Sync2(new(domain.User))
 return &mysqlUserRepository{Conn}
}

func (m *mysqlUserRepository) GetById(ctx context.Context, id int) (res *domain.User, err error) {
 // TODO::implements it
 return
}

func (m *mysqlUserRepository) GetByPage(ctx context.Context, count, offset int) (data []*domain.User, nextOffset int, err error) {
 // TODO::implements it
 return
}

func (m *mysqlUserRepository) Create(ctx context.Context, user *domain.User) (err error) {
 // TODO::implements it
 return
}

func (m *mysqlUserRepository) Delete(ctx context.Context, id int) (err error) {
 // TODO::implements it
 return
}

func (m *mysqlUserRepository) Update(ctx context.Context, user *domain.User) (err error) {
 // TODO::implements it
 return
}

閱讀上面這段代碼,我們可以發現 mysql 包主要作爲 domain 包和操作數據庫的方法實現之間的適配器,這種包佈局方式,隔離了我們 MySQL 的依賴關係,從而方便了未來遷移到其他數據庫的實現。比如,我們未來想把數據庫切換爲 PostgreSQL,我們可以再定義一個 postgresql 包,提供 PostgreSQL 的支持。

04 共享 mock 包

因爲我們的依賴項通過我們的 domain 包定義的接口與其他依賴項隔離,所以我們可以使用這些連接點來注入 mock 實現。可以使用 mock 庫生成 mock 代碼,也可以自己編寫 mock 代碼。

05 使用 main 包將依賴關係連接起來

最後,我們使用 main 包將這些彼此孤立的包連接起來,將對象需要的依賴注入到對象中。

package main

import (
 "github.com/gin-gonic/gin"
 _ "github.com/go-sql-driver/mysql"
 _userHttpDelivery "go_standard/user/delivery/http"
 _userRepo "go_standard/user/repository/mysql"
 _userUsecase "go_standard/user/usecase"
 "xorm.io/xorm"
)

func main() {
 db, err := xorm.NewEngine("mysql", "root:root@/go_standard?charset=utf8mb4")
 if err != nil {
  return
 }
 r := gin.Default()
 userRepo := _userRepo.NewMysqlUserRepository(db)
 userUsecase := _userUsecase.NewUserUsecase(userRepo)
 _userHttpDelivery.NewUserHandler(r, userUsecase)
}

06 總結

我們遵循以上 4 個規則設計 Go 應用程序,不僅可以有效幫助我們在編寫代碼時避免循環依賴,還可以提升應用程序的可閱讀性、可維護性和可擴展性。

值得一提的是,本文旨在建議團隊制定成員都要遵循的規則,作爲團隊的 Go 應用程序設計標準,而不是建議大家必須遵循本文介紹的 4 個規則。

參考資料:

  1. https://medium.com/@benbjohnson/standard-package-layout-7cdbc8391fc1

  2. https://github.com/bxcodec/go-clean-arch/pull/21

  3. https://github.com/golang-standards/project-layout/issues/117

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/q6mFgLYt3hpBhXnyEcaXzQ