golang实现Clean架构的服务模板插件库go-clean-template的使用
Golang实现Clean架构的服务模板插件库go-clean-template的使用
概述
go-clean-template是一个基于Clean架构的Golang服务模板库,主要目的是展示如何:
- 组织项目并防止其变成"意大利面条"代码
- 存储业务逻辑使其保持独立、干净且可扩展
- 在微服务增长时不失去控制
该模板遵循Robert Martin(Uncle Bob)提出的Clean Architecture原则。
快速开始
本地开发
# 启动Postgres和RabbitMQ
make compose-up
# 运行应用并执行迁移
make run
集成测试(可在CI中运行)
# 启动数据库、应用+迁移,然后运行集成测试
make compose-up-integration-test
完整Docker堆栈(含反向代理)
make compose-up-all
项目结构
cmd/app/main.go
配置和日志初始化,然后在internal/app/app.go
中继续主函数。
config
采用十二要素应用配置原则,将配置存储在环境变量中。
internal/app
包含app.go
文件,其中创建所有主要对象并通过"New…"构造函数进行依赖注入。
internal/controller
服务器处理层(MVC控制器),包含三种服务器实现:
- AMQP RPC(基于RabbitMQ)
- gRPC(基于protobuf)
- REST API(基于Fiber框架)
internal/entity
业务逻辑实体(模型),可在任何层使用。
internal/usecase
业务逻辑层,包含:
internal/usecase/repo
- 抽象存储仓库internal/usecase/webapi
- 抽象Web API
依赖注入
通过构造函数注入依赖,使业务逻辑独立于外部包:
package usecase
import (
// 无外部依赖
)
type Repository interface {
Get()
}
type UseCase struct {
repo Repository
}
func New(r Repository) *UseCase {
return &UseCase{
repo: r,
}
}
func (uc *UseCase) Do() {
uc.repo.Get()
}
Clean架构
关键原则
依赖反转(Dependency Inversion),依赖方向从外层指向内层,使业务逻辑和实体独立于系统其他部分。
示例交互流程
HTTP > usecase
usecase > repository (Postgres)
usecase < repository (Postgres)
HTTP < usecase
完整示例
以下是一个简单的REST API实现示例:
// internal/controller/http/v1/translation.go
package v1
import (
"net/http"
"github.com/gofiber/fiber/v2"
"github.com/evrone/go-clean-template/internal/entity"
"github.com/evrone/go-clean-template/internal/usecase"
"github.com/evrone/go-clean-template/pkg/logger"
)
type translationRoutes struct {
t usecase.Translation
l logger.Interface
}
func NewTranslationRoutes(handler fiber.Router, t usecase.Translation, l logger.Interface) {
r := &translationRoutes{t, l}
handler.Get("/history", r.history)
}
// @Summary Show history
// @Description Show all translation history
// @ID history
// @Tags translation
// @Accept json
// @Produce json
// @Success 200 {object} []entity.Translation
// @Failure 500 {object} response
// @Router /translation/history [get]
func (r *translationRoutes) history(c *fiber.Ctx) error {
translations, err := r.t.History(c.Context())
if err != nil {
r.l.Error(err, "http - v1 - history")
return c.Status(http.StatusInternalServerError).JSON(response{err.Error()})
}
return c.Status(http.StatusOK).JSON(translations)
}
// internal/usecase/translation.go
package usecase
import (
"context"
"github.com/evrone/go-clean-template/internal/entity"
)
type Translation interface {
History(context.Context) ([]entity.Translation, error)
}
type TranslationUseCase struct {
repo TranslationRepo
}
type TranslationRepo interface {
GetHistory(context.Context) ([]entity.Translation, error)
}
func NewTranslation(r TranslationRepo) *TranslationUseCase {
return &TranslationUseCase{r}
}
func (uc *TranslationUseCase) History(ctx context.Context) ([]entity.Translation, error) {
return uc.repo.GetHistory(ctx)
}
// internal/entity/translation.go
package entity
type Translation struct {
ID int `json:"id" db:"id"`
Source string `json:"source" db:"source"`
Destination string `json:"destination" db:"destination"`
Original string `json:"original" db:"original"`
Translation string `json:"translation" db:"translation"`
}
这个模板提供了清晰的架构分层和依赖管理方式,非常适合构建可维护的Golang微服务。
更多关于golang实现Clean架构的服务模板插件库go-clean-template的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang实现Clean架构的服务模板插件库go-clean-template的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
go-clean-template 使用指南
go-clean-template 是一个遵循 Clean 架构原则的 Golang 服务模板库,它提供了清晰的分层结构和标准化的项目组织方式。
核心概念
Clean 架构强调:
- 业务逻辑独立于框架、UI 和数据库
- 依赖关系向内指向核心业务逻辑
- 通过接口隔离实现细节
安装
go get github.com/evrone/go-clean-template
项目结构
典型的 go-clean-template 项目结构:
.
├── cmd/ # 应用入口
├── config/ # 配置文件
├── internal/ # 内部代码
│ ├── controller/ # 交付层(HTTP/gRPC等)
│ ├── entity/ # 业务实体
│ ├── repository/ # 数据访问接口
│ ├── usecase/ # 业务逻辑
│ └── service/ # 基础设施服务
├── pkg/ # 可复用的公共代码
└── migrations/ # 数据库迁移文件
基本使用示例
1. 定义实体
// internal/entity/user.go
package entity
type User struct {
ID int
Name string
Email string
Password string
}
2. 创建仓储接口
// internal/repository/user.go
package repository
import "github.com/your-project/internal/entity"
type UserRepository interface {
Create(user *entity.User) error
FindByID(id int) (*entity.User, error)
FindByEmail(email string) (*entity.User, error)
}
3. 实现业务用例
// internal/usecase/user.go
package usecase
import (
"github.com/your-project/internal/entity"
"github.com/your-project/internal/repository"
)
type UserUseCase struct {
userRepo repository.UserRepository
}
func NewUserUseCase(repo repository.UserRepository) *UserUseCase {
return &UserUseCase{userRepo: repo}
}
func (uc *UserUseCase) Register(user *entity.User) error {
// 业务逻辑验证
if user.Email == "" || user.Password == "" {
return entity.ErrInvalidCredentials
}
// 检查用户是否已存在
if _, err := uc.userRepo.FindByEmail(user.Email); err == nil {
return entity.ErrUserAlreadyExists
}
// 创建用户
return uc.userRepo.Create(user)
}
4. 实现控制器
// internal/controller/http/user.go
package http
import (
"net/http"
"github.com/your-project/internal/entity"
"github.com/your-project/internal/usecase"
"github.com/gin-gonic/gin"
)
type UserController struct {
userUseCase *usecase.UserUseCase
}
func NewUserController(uc *usecase.UserUseCase) *UserController {
return &UserController{userUseCase: uc}
}
func (c *UserController) Register(ctx *gin.Context) {
var user entity.User
if err := ctx.ShouldBindJSON(&user); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := c.userUseCase.Register(&user); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
ctx.JSON(http.StatusCreated, gin.H{"message": "User created successfully"})
}
5. 依赖注入
// cmd/main.go
package main
import (
"github.com/gin-gonic/gin"
"github.com/your-project/internal/controller/http"
"github.com/your-project/internal/repository/postgres"
"github.com/your-project/internal/usecase"
)
func main() {
// 初始化数据库连接
db := postgres.NewPostgresDB()
// 初始化仓储
userRepo := postgres.NewUserRepository(db)
// 初始化用例
userUseCase := usecase.NewUserUseCase(userRepo)
// 初始化HTTP控制器
userController := http.NewUserController(userUseCase)
// 设置路由
r := gin.Default()
r.POST("/register", userController.Register)
// 启动服务
r.Run(":8080")
}
高级特性
1. 中间件支持
// 添加认证中间件
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
return
}
// 验证token逻辑...
c.Next()
}
}
// 在路由中使用
r.POST("/profile", AuthMiddleware(), userController.GetProfile)
2. 日志集成
// 初始化日志
logger := logrus.New()
logger.SetFormatter(&logrus.JSONFormatter{})
// 在用例中使用
func (uc *UserUseCase) Register(user *entity.User) error {
logger.WithFields(logrus.Fields{
"email": user.Email,
}).Info("Attempting to register user")
// ...
}
3. 配置管理
// config/config.go
type Config struct {
DB struct {
Host string `env:"DB_HOST" env-default:"localhost"`
Port int `env:"DB_PORT" env-default:"5432"`
User string `env:"DB_USER" env-default:"postgres"`
Password string `env:"DB_PASSWORD" env-default:""`
Name string `env:"DB_NAME" env-default:"postgres"`
}
Server struct {
Port int `env:"SERVER_PORT" env-default:"8080"`
}
}
func Load() (*Config, error) {
var cfg Config
if err := env.Parse(&cfg); err != nil {
return nil, err
}
return &cfg, nil
}
最佳实践
- 保持层间依赖清晰:上层可以依赖下层,但下层不能依赖上层
- 使用接口隔离:所有外部依赖都应通过接口访问
- 测试友好:通过依赖注入可以轻松mock外部依赖
- 单一职责:每个文件/结构体/函数只做一件事
- 领域驱动:将业务逻辑集中在用例层,保持实体纯净
go-clean-template 提供了一种结构化的方式来组织Go项目,特别适合中大型项目开发。通过遵循其约定,你可以创建出易于维护、测试和扩展的应用程序。