当前位置: 首页 > 科技观察

谈谈Go应用设计标准

时间:2023-03-19 00:13:34 科技观察

1.简介众所周知,Go语言的官方成员RussCox曾回应Go社区没有Go应用程序设计标准。但是这篇文章为什么要用这个标题呢?因为团队达成共识(标准),制定了一些团队成员必须遵守的规则,可以让我们的应用更容易维护。这篇文章介绍了我们应该如何组织我们的代码,制定团队的Go应用设计标准。需要注意的是,它不是Go核心开发团队制定的官方标准。2.定义领域包为什么要定义领域包?因为我们开发的Go应用可能不止包含一个功能模块,不同功能模块之间还需要相互调用,所以需要domain(域)包。比如我们开发一个博客应用,我们的域包括用户、文章、评论等,这些不依赖于我们使用的底层技术。需要注意的是,域包不应包含方法的实现细节,如操作数据库或调用其他微服务等,域包不应依赖应用中的其他包。我们可以定义义域包,把结构体和接口放在域包中,例如:packagedomainimport"context"typeUserstruct{idint64`json:"id"`UserNamestring`json:"user_name"xorm:"varchar(30)notnulldefault''uniquecomment('用户名')"`Emailstring`json:"email"xorm:"varchar(30)notnulldefault''indexcomment('邮箱')"`Passwordstring`json:"password"xorm:"varchar(60)notnulldefault''comment('密码')"`Createdint`json:"created"xorm:"indexcreated"`Updatedint`json:"updated"xorm:"updated"`Deletedint`json:"deleted"xorm:"deleted"`}typeUserUsecaseinterface{GetById(ctxcontext.Context,idint)(*User,error)GetByPage(ctxcontext.Context,count,offsetint)([]*User,int,error)Create(ctxcontext.Context,user*User)errorDelete(ctxcontext.Context,idint)errorUpdate(ctxcontext.Context,user*User)error}typeUserRepository接口{GetById(ctxcontext.Context,idint)(*User,error)GetByPage(ctxcontext.Context,count,offsetint)([]*User,int,error)Create(ctxcontext.Context,user*User)errorDelete(ctxcontext.Context,idint)errorUpdate(ctxcontext.Context,user*User)error}细心的读者可能已经在《Go语言整洁架构实战》一文中发现上述代码被划分到了models包中,因为我们当时的示例项目是TodoList,只包含一个功能模块。但是,当我们开发包含多个功能模块的应用程序时,为了方便功能模块之间的相互调用,建议将所有功能模块的结构体和接口存放在domain包中。3.根据依赖划分包在《Go语言整洁架构实践》一文中提到,操作数据库和调用微服务的代码存放在Repository层。我们可以在Repository层按照依赖来划分包。比如我们的应用需要操作MySQL数据库,我们可以定义一个mysql包。示例代码:(new(domain.User))return&mysqlUserRepository{Conn}}func(m*mysqlUserRepository)GetById(ctxcontext.Context,idint)(res*domain.User,errerror){//TODO::implementsitreturn}func(m*mysqlUserRepository)GetByPage(ctxcontext.Context,count,offsetint)(data[]*domain.User,nextOffsetint,errerror){//TODO::implements它返回}func(m*mysqlUserRepository)创建(ctxcontext.Context,user*domain.User)(errerror){//TODO::implements它返回}func(m*mysqlUserRepository)Delete(ctxcontext.Context,idint)(errerror){//TODO::implementsitreturn}func(m*mysqlUserRepository)Update(ctxcontext.Context,user*domain.User)(errerror){//TODO::implementsitreturn}阅读上面的代码,我们可以找到mysql包主要用作适配在域包和操作数据库的方法的实现之间Orchestrator,这种包的布局方式,隔离了我们MySQL的依赖,方便以后迁移到其他数据库的实现。比如我们以后想把数据库切换到PostgreSQL,我们可以定义一个postgresql包来提供PostgreSQL的支持。4.共享模拟包因为我们的依赖通过我们的域包定义的接口与其他依赖隔离,我们可以使用这些连接点来注入模拟实现。您可以使用mock库生成mock代码,也可以自己编写mock代码。5.使用主包连接依赖最后,我们使用主包连接这些孤立的包,将对象需要的依赖注入到对象中。packagemainimport("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")funcmain(){db,err:=xorm.NewEngine("mysql","root:root@/go_standard?charset=utf8mb4")iferr!=nil{return}r:=gin.Default()userRepo:=_userRepo.NewMysqlUserRepository(db)userUsecase:=_userUsecase.NewUserUsecase(userRepo)_userHttpDelivery.NewUserHandler(r,userUsecase)}6.总结我们按照以上4条规则来设计Go应用程序不仅可以有效帮助我们在编写代码时避免循环依赖,还可以提高应用程序的可读性、可维护性和可扩展性。值得一提的是,本文旨在建议团队制定所有成员必须遵循的规则作为团队的Go应用设计标准,而不是建议每个人都必须遵循本文介绍的4条规则。