您的位置:首页 > 汽车 > 时评 > 网站数据分析平台_宠物网站页面设计理念_竞价交易_国际时事新闻最新消息

网站数据分析平台_宠物网站页面设计理念_竞价交易_国际时事新闻最新消息

2024/11/15 8:32:47 来源:https://blog.csdn.net/sydhappy/article/details/143757002  浏览:    关键词:网站数据分析平台_宠物网站页面设计理念_竞价交易_国际时事新闻最新消息
网站数据分析平台_宠物网站页面设计理念_竞价交易_国际时事新闻最新消息

今天分享一个github 4.7k star的go整洁架构的项目go-backend-clean-architecture,项目技术栈:GinMongoDBJWT Authentication MiddlewareTestDocker

317922caaf72856598dc24b4f4fc5015.png

项目通过一个HTTP服务的demo来做演示。

工程结构

7fc520d099baadc0e31edbd00508ec21.png

目录结构

整个工程的目录结构大致包含了:启动入口、环境变量配置、控制器层、业务层、反腐层、数据存储层。接下来我们逐层展开介绍。

.
├── Dockerfile
├── api
│   ├── controller
│   │   ├── login_controller.go
│   │   ├── profile_controller.go
│   │   ├── profile_controller_test.go
│   │   ├── refresh_token_controller.go
│   │   ├── signup_controller.go
│   │   └── task_controller.go
│   ├── middleware
│   │   └── jwt_auth_middleware.go
│   └── route
│       ├── login_route.go
│       ├── profile_route.go
│       ├── refresh_token_route.go
│       ├── route.go
│       ├── signup_route.go
│       └── task_route.go
├── bootstrap
│   ├── app.go
│   ├── database.go
│   └── env.go
├── cmd
│   └── main.go
├── docker-compose.yaml
├── domain
│   ├── error_response.go
│   ├── jwt_custom.go
│   ├── login.go
│   ├── profile.go
│   ├── refresh_token.go
│   ├── signup.go
│   ├── success_response.go
│   ├── task.go
│   └── user.go
├── go.mod
├── go.sum
├── internal
│   └── tokenutil
│       └── tokenutil.go
├── mongo
│   └── mongo.go
├── repository
│   ├── task_repository.go
│   ├── user_repository.go
│   └── user_repository_test.go
└── usecase├── login_usecase.go├── profile_usecase.go├── refresh_token_usecase.go├── signup_usecase.go├── task_usecase.go└── task_usecase_test.go

系统参数配置、启动入口

接触一个新工程,从入口开始: ./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)// 启动servergin.Run(env.ServerAddress)
}

下面我们通过登录逻辑为例来详细介绍一下整洁架构的三层架构。

控制器层

以登录逻辑的controller层为例:

./api/controller/login_controller.go

LoginController持有Configuration类和LoginUsecase接口,LoginUsecase定义了登录的具体业务。

type LoginController struct {LoginUsecase domain.LoginUsecaseEnv          *bootstrap.Env
}func (lc *LoginController) Login(c *gin.Context) {var request domain.LoginRequesterr := 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)
}

业务层

./usecase/login_usecase.go

loginUsecase实现了LoginUsecase接口,完成了具体的登录逻辑。

//数据反腐层接口
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)
}

反腐层

反腐层(Anti-corruption layer,简称 ACL)介于新应用和旧应用之间,用于确保新应用的设计不受老应用的限制。是一种在不同应用间转换的机制。创建一个反腐层,以根据客户端自己的域模型为客户提供功能。该层通过其现有接口与另一个系统进行通信,几乎不需要对其进行任何修改。因此,反腐层隔离不仅是为了保护你的系统免受异常代码的侵害,还在于分离不同的域并确保它们在将来保持分离。反腐层是将一个域映射到另一个域,这样使用第二个域的服务就不必被第一个域的概念“破坏”。

这里引入防腐层主要是为了应对未来可能存在的多种存储方式。

./repository/user_repository.go

userRepository实现了UserRepository接口。并且它的内部持有了mongo.Database接口,mongo.Database定义了数据存储层的一系列操作。

//数据存储层接口
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.Usererr = 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.UseridHex, err := primitive.ObjectIDFromHex(id)if err != nil {return user, err}err = collection.FindOne(c, bson.M{"_id": idHex}).Decode(&user)return user, err
}

数据存储层

./mongo/mongo.go

数据存储层实现了mongo.Database接口,通过mongoDatabase,可以获取ClientCollection实例,来操作数据库。

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 发现,在注册由路由触发的控制器方法时,所需的数据库已在数据层内创建并共享。

此外,反腐层、业务层和控制器层的实例在服务启动之前创建,并按顺序嵌套和保存。

因此,所有结构都是单例,类似于树状结构,按顺序链接。

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)
}

这种方法强制实施资源限制,阻止开发人员跨模块调用实例,从而尽可能规避循环依赖和其他安全问题。

题外话

这种三层结构应对业务不是那么复杂的小型项目,比较游刃有余。一旦出现业务比较复杂的大型项目,你会发现业务层会极其复杂,业务逻辑盘根错节。如果继续采用三层结构,维护难度会指数攀升。

如何解决?大家可以评论区说说你们的解法,我们之后再单起一篇单独聊聊我们的解法

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com