Go 应用程序设计标准

2022-05-17 11:47:50 浏览数 (1)

01

介绍

众所周知 Go 语言官方成员 Russ Cox 曾向 Go 社区回应并没有 Go 应用程序设计标准。但是,为什么本文还要使用这个标题呢?

因为团队达成一个共识(标准),制定一些团队成员都要遵循的规则,可以使我们的应用程序更容易维护。本文介绍一下我们应该怎么组织我们的代码,制定团队的 Go 应用程序设计标准。

需要注意的是,它不是核心 Go 开发团队制定的官方标准。

02

定义 domain 包

为什么需要定义 domain 包?因为我们开发的 Go 应用程序,可能不只是包含一个功能模块,并且可能不同的功能模块之间还需要互相调用,所以,我们需要 domain(领域)包,例如我们开发一个博客应用程序,我们的 domain 包括用户、文章、评论等。这些不依赖我们使用的底层技术。

需要注意的是,domain 包不应该包含方法的实现细节,比如操作数据库或调用其他微服务,并且 domain 包不可以依赖应用程序中的其他包。

我们可以定义 domain 包,把结构体和接口放在 domain 包,例如:

代码语言:javascript复制
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 包。

示例代码:

代码语言:javascript复制
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 包将这些彼此孤立的包连接起来,将对象需要的依赖注入到对象中。

代码语言:javascript复制
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

0 人点赞