Go项目结构整洁实现|GitHub 3.5k

2024-04-03 00:12

本文主要是介绍Go项目结构整洁实现|GitHub 3.5k,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、前言

hi,大家好,这里是白泽。今天给大家分享一个GitHub 🌟 3.5k 的 Go项目:go-backend-clean-arch

https://github.com/amitshekhariitbhu/go-backend-clean-architecture

这个项目是一位老外写的,通过一个 HTTP demo 介绍了一个优雅的项目结构。

我也在b站出了一期30多分钟的视频,讲解了这个仓库,欢迎你的关注 📺 B站:白泽talk,qq交流群:622383022。

image-20240401202825006

🌟 当然,如果您是一位 Go 学习的新手,您可以在我开源的学习仓库:https://github.com/BaiZe1998/go-learning 中,找到我往期翻译的英文书籍,或者Go学习路线。

image-20240401222006030

二、项目架构

image-20240401202921385

三、目录详解

.
├── Dockerfile # 镜像文件
├── api
│   ├── controller # 接口
│   ├── middleware # 中间件(JWT鉴权)
│   └── route # 路由绑定
├── bootstrap
│   ├── app.go # 核心类
│   ├── database.go # 数据库
│   └── env.go # 配置类
├── cmd
│   └── main.go # 启动命令
├── docker-compose.yaml
├── domain # 实例层
├── go.mod
├── go.sum
├── internal # 内部工具
│   └── tokenutil
├── mongo # mongodb
│   └── mongo.go
├── repository # 仓储层
└── usecase # 业务层

3.1 参数配置 & 项目启动

./cmd/main.go

type Application struct {Env   *EnvMongo mongo.Client
}
​
type Env struct {AppEnv                 string `mapstructure:"APP_ENV"`ServerAddress          string `mapstructure:"SERVER_ADDRESS"`ContextTimeout         int    `mapstructure:"CONTEXT_TIMEOUT"`DBHost                 string `mapstructure:"DB_HOST"`DBPort                 string `mapstructure:"DB_PORT"`...
}
​
func main() {// app 是整个应用的实例,管理生命周期中的重要资源app := bootstrap.App()// 配置变量env := app.Env// 数据库实例db := app.Mongo.Database(env.DBName)defer app.CloseDBConnection()
​timeout := time.Duration(env.ContextTimeout) * time.Second// gin 实例创建gin := gin.Default()// 路由绑定route.Setup(env, timeout, db, gin)// 运行服务gin.Run(env.ServerAddress)
}

🌟 接下来的讲解将以登陆逻辑为例,讲解三层架构。

3.2 接口层

./api/controller/login_controller.go

LoginController 持有配置类,以及 LoginUsecase 接口(定义了业务层的行为)

// 业务层接口
type SignupUsecase interface {Create(c context.Context, user *User) errorGetUserByEmail(c context.Context, email string) (User, error)CreateAccessToken(user *User, secret string, expiry int) (accessToken string, err error)CreateRefreshToken(user *User, secret string, expiry int) (refreshToken string, err error)
}
​
type LoginController struct {LoginUsecase domain.LoginUsecaseEnv          *bootstrap.Env
}
​
func (lc *LoginController) Login(c *gin.Context) {var request domain.LoginRequest
​err := c.ShouldBind(&request)if err != nil {c.JSON(http.StatusBadRequest, domain.ErrorResponse{Message: err.Error()})return}
​user, err := lc.LoginUsecase.GetUserByEmail(c, request.Email)if err != nil {c.JSON(http.StatusNotFound, domain.ErrorResponse{Message: "User not found with the given email"})return}
​if bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(request.Password)) != nil {c.JSON(http.StatusUnauthorized, domain.ErrorResponse{Message: "Invalid credentials"})return}
​accessToken, err := lc.LoginUsecase.CreateAccessToken(&user, lc.Env.AccessTokenSecret, lc.Env.AccessTokenExpiryHour)if err != nil {c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()})return}
​refreshToken, err := lc.LoginUsecase.CreateRefreshToken(&user, lc.Env.RefreshTokenSecret, lc.Env.RefreshTokenExpiryHour)if err != nil {c.JSON(http.StatusInternalServerError, domain.ErrorResponse{Message: err.Error()})return}
​loginResponse := domain.LoginResponse{AccessToken:  accessToken,RefreshToken: refreshToken,}
​c.JSON(http.StatusOK, loginResponse)
}

3.3 业务层

./usecase/login_usecase.go

loginUsecase 结构实现 LoginUsecase 接口,同时在 loginUsecase 结构中,持有了 UserRepository 接口(定义了仓储层的行为)。

// 数据防腐层接口
type UserRepository interface {Create(c context.Context, user *User) errorFetch(c context.Context) ([]User, error)GetByEmail(c context.Context, email string) (User, error)GetByID(c context.Context, id string) (User, error)
}
​
type loginUsecase struct {userRepository domain.UserRepositorycontextTimeout time.Duration
}
​
func NewLoginUsecase(userRepository domain.UserRepository, timeout time.Duration) domain.LoginUsecase {return &loginUsecase{userRepository: userRepository,contextTimeout: timeout,}
}
​
func (lu *loginUsecase) GetUserByEmail(c context.Context, email string) (domain.User, error) {ctx, cancel := context.WithTimeout(c, lu.contextTimeout)defer cancel()return lu.userRepository.GetByEmail(ctx, email)
}
​
func (lu *loginUsecase) CreateAccessToken(user *domain.User, secret string, expiry int) (accessToken string, err error) {return tokenutil.CreateAccessToken(user, secret, expiry)
}
​
func (lu *loginUsecase) CreateRefreshToken(user *domain.User, secret string, expiry int) (refreshToken string, err error) {return tokenutil.CreateRefreshToken(user, secret, expiry)
}

3.4 防腐层

./repository/user_repository.go

userRepository 结构实现了 UserRepository 接口,内部持有 mongo.Database 接口(定义数据层行为),以及 collection 实例的名称。

// 数据操作层接口
type Database interface {Collection(string) CollectionClient() Client
}
​
type userRepository struct {database   mongo.Databasecollection string
}
​
func NewUserRepository(db mongo.Database, collection string) domain.UserRepository {return &userRepository{database:   db,collection: collection,}
}
​
func (ur *userRepository) Create(c context.Context, user *domain.User) error {collection := ur.database.Collection(ur.collection)
​_, err := collection.InsertOne(c, user)
​return err
}
​
func (ur *userRepository) Fetch(c context.Context) ([]domain.User, error) {collection := ur.database.Collection(ur.collection)
​opts := options.Find().SetProjection(bson.D{{Key: "password", Value: 0}})cursor, err := collection.Find(c, bson.D{}, opts)
​if err != nil {return nil, err}
​var users []domain.User
​err = cursor.All(c, &users)if users == nil {return []domain.User{}, err}
​return users, err
}
​
func (ur *userRepository) GetByEmail(c context.Context, email string) (domain.User, error) {collection := ur.database.Collection(ur.collection)var user domain.Usererr := collection.FindOne(c, bson.M{"email": email}).Decode(&user)return user, err
}
​
func (ur *userRepository) GetByID(c context.Context, id string) (domain.User, error) {collection := ur.database.Collection(ur.collection)
​var user domain.User
​idHex, err := primitive.ObjectIDFromHex(id)if err != nil {return user, err}
​err = collection.FindOne(c, bson.M{"_id": idHex}).Decode(&user)return user, err
}

3.5 数据层

./mongo/mongo.go

实现了 mongo.Database 接口,通过 mongoDatabase 结构体的两个方法可以获取对应的 Client 实例和 Collection 实例,从而操作数据库。

type mongoDatabase struct {db *mongo.Database
}
​
func (md *mongoDatabase) Collection(colName string) Collection {collection := md.db.Collection(colName)return &mongoCollection{coll: collection}
}
​
func (md *mongoDatabase) Client() Client {client := md.db.Client()return &mongoClient{cl: client}
}

四、单例与封装

查看 ./cmd/main.go 的路由绑定逻辑:route.Setup(env, timeout, db, gin)。

func Setup(env *bootstrap.Env, timeout time.Duration, db mongo.Database, gin *gin.Engine) {publicRouter := gin.Group("")// All Public APIsNewSignupRouter(env, timeout, db, publicRouter)NewLoginRouter(env, timeout, db, publicRouter)NewRefreshTokenRouter(env, timeout, db, publicRouter)
​protectedRouter := gin.Group("")// Middleware to verify AccessTokenprotectedRouter.Use(middleware.JwtAuthMiddleware(env.AccessTokenSecret))// All Private APIsNewProfileRouter(env, timeout, db, protectedRouter)NewTaskRouter(env, timeout, db, protectedRouter)
}

进一步查看 NewLoginRouter,会发现,在注册路由触发的 controller 方法的时候,已经将所需要的 db 创建出来,并且在数据层共享,同时防腐层、业务层、控制层的实例,在服务启动前创建,依次嵌套持有,因此所有的结构都是单例,且类似树形结构,依次串联。

func NewLoginRouter(env *bootstrap.Env, timeout time.Duration, db mongo.Database, group *gin.RouterGroup) {ur := repository.NewUserRepository(db, domain.CollectionUser)lc := &controller.LoginController{LoginUsecase: usecase.NewLoginUsecase(ur, timeout),Env:          env,}group.POST("/login", lc.Login)
}

通过这种方式,实现了资源的约束,使得开发者无法跨模块调用实例,导致循环依赖等安全问题。

这篇关于Go项目结构整洁实现|GitHub 3.5k的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/871433

相关文章

基于Python实现高效PPT转图片工具

《基于Python实现高效PPT转图片工具》在日常工作中,PPT是我们常用的演示工具,但有时候我们需要将PPT的内容提取为图片格式以便于展示或保存,所以本文将用Python实现PPT转PNG工具,希望... 目录1. 概述2. 功能使用2.1 安装依赖2.2 使用步骤2.3 代码实现2.4 GUI界面3.效

MySQL更新某个字段拼接固定字符串的实现

《MySQL更新某个字段拼接固定字符串的实现》在MySQL中,我们经常需要对数据库中的某个字段进行更新操作,本文就来介绍一下MySQL更新某个字段拼接固定字符串的实现,感兴趣的可以了解一下... 目录1. 查看字段当前值2. 更新字段拼接固定字符串3. 验证更新结果mysql更新某个字段拼接固定字符串 -

java实现延迟/超时/定时问题

《java实现延迟/超时/定时问题》:本文主要介绍java实现延迟/超时/定时问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java实现延迟/超时/定时java 每间隔5秒执行一次,一共执行5次然后结束scheduleAtFixedRate 和 schedu

Java Optional避免空指针异常的实现

《JavaOptional避免空指针异常的实现》空指针异常一直是困扰开发者的常见问题之一,本文主要介绍了JavaOptional避免空指针异常的实现,帮助开发者编写更健壮、可读性更高的代码,减少因... 目录一、Optional 概述二、Optional 的创建三、Optional 的常用方法四、Optio

在Android平台上实现消息推送功能

《在Android平台上实现消息推送功能》随着移动互联网应用的飞速发展,消息推送已成为移动应用中不可或缺的功能,在Android平台上,实现消息推送涉及到服务端的消息发送、客户端的消息接收、通知渠道(... 目录一、项目概述二、相关知识介绍2.1 消息推送的基本原理2.2 Firebase Cloud Me

Spring Boot项目中结合MyBatis实现MySQL的自动主从切换功能

《SpringBoot项目中结合MyBatis实现MySQL的自动主从切换功能》:本文主要介绍SpringBoot项目中结合MyBatis实现MySQL的自动主从切换功能,本文分步骤给大家介绍的... 目录原理解析1. mysql主从复制(Master-Slave Replication)2. 读写分离3.

Redis实现延迟任务的三种方法详解

《Redis实现延迟任务的三种方法详解》延迟任务(DelayedTask)是指在未来的某个时间点,执行相应的任务,本文为大家整理了三种常见的实现方法,感兴趣的小伙伴可以参考一下... 目录1.前言2.Redis如何实现延迟任务3.代码实现3.1. 过期键通知事件实现3.2. 使用ZSet实现延迟任务3.3

基于Python和MoviePy实现照片管理和视频合成工具

《基于Python和MoviePy实现照片管理和视频合成工具》在这篇博客中,我们将详细剖析一个基于Python的图形界面应用程序,该程序使用wxPython构建用户界面,并结合MoviePy、Pill... 目录引言项目概述代码结构分析1. 导入和依赖2. 主类:PhotoManager初始化方法:__in

springboot filter实现请求响应全链路拦截

《springbootfilter实现请求响应全链路拦截》这篇文章主要为大家详细介绍了SpringBoot如何结合Filter同时拦截请求和响应,从而实现​​日志采集自动化,感兴趣的小伙伴可以跟随小... 目录一、为什么你需要这个过滤器?​​​二、核心实现:一个Filter搞定双向数据流​​​​三、完整代码

SpringBoot利用@Validated注解优雅实现参数校验

《SpringBoot利用@Validated注解优雅实现参数校验》在开发Web应用时,用户输入的合法性校验是保障系统稳定性的基础,​SpringBoot的@Validated注解提供了一种更优雅的解... 目录​一、为什么需要参数校验二、Validated 的核心用法​1. 基础校验2. php分组校验3