- Replace `interface{}` with `any` type alias across all packages
- Use built-in `min()`/`max()` for parameter clamping
- Use `slices.SortFunc`, `slices.Min`, `slices.Max` for cleaner code
- Use `strings.Cut()` for simpler string parsing in auth middleware
- Use `errors.Is()` for proper error comparison in handlers
- Update dependencies: golang.org/x/image 0.37.0 -> 0.38.0
- Add Wire code generation guidelines to ARCHITECTURE.md
- Disable Go cache in CI build workflow
9.4 KiB
9.4 KiB
后端架构设计规范
1. 分层架构原则
层级定义
Handler (控制器层) → Service (业务逻辑层) → Repository (数据访问层) → Infrastructure (基础设施层)
层级职责
| 层级 | 职责 | 依赖关系 |
|---|---|---|
| Handler | 接收HTTP请求、参数校验、调用Service、返回响应 | 仅依赖Service |
| Service | 实现业务逻辑、事务管理、组合多个Repository | 仅依赖Repository接口 |
| Repository | 数据CRUD操作、数据库事务管理 | 仅依赖Model和GORM |
| Infrastructure | 缓存、消息队列、RPC等外部服务 | 被Service调用 |
关键约束
- Handler禁止直接依赖Repository或直接操作数据库
- Service禁止直接依赖
gorm.DB,必须通过Repository间接操作 - 所有外部依赖通过接口抽象
2. Repository 层规范
2.1 接口 + 私有实现模式
所有Repository必须采用接口定义 + 私有实现结构体的模式:
// ✅ 正确:接口 + 私有实现
type UserRepository interface {
Create(user *model.User) error
GetByID(id string) (*model.User, error)
}
type userRepository struct {
db *gorm.DB
}
func NewUserRepository(db *gorm.DB) UserRepository {
return &userRepository{db: db}
}
func (r *userRepository) Create(user *model.User) error {
return r.db.Create(user).Error
}
// ❌ 错误:直接暴露结构体指针
type UserRepository struct {
db *gorm.DB
}
func NewUserRepository(db *gorm.DB) *UserRepository {
return &UserRepository{db: db}
}
2.2 Repository方法设计原则
- 方法签名中使用接口类型而非指针:
func (r *userRepository) GetByID(...) (*model.User, error) - 避免在Repository层直接返回指针切片,使用值切片更安全
- 事务操作在Repository内部封装,Service不应直接调用
db.Transaction()
2.3 新增Repository流程
- 在
repository/目录下创建xxx_repo.go文件 - 定义
XxxRepository接口,包含所有数据访问方法 - 创建私有实现结构体
xxxRepository - 创建构造函数
NewXxxRepository(db *gorm.DB) XxxRepository - 在
wire/repository.go的RepositorySet中注册
3. Service 层规范
3.1 接口 + 私有实现模式
// ✅ 正确
type UserService interface {
CreateUser(ctx context.Context, req *dto.CreateUserRequest) (*model.User, error)
GetUserByID(ctx context.Context, id string) (*model.User, error)
}
type userService struct {
userRepo repository.UserRepository
// 其他Repo都使用接口类型
}
func NewUserService(userRepo repository.UserRepository) UserService {
return &userService{userRepo: userRepo}
}
3.2 Service依赖管理
- 所有Repository依赖必须使用接口类型,禁止使用
*repository.XxxRepository指针 - 禁止在Service中直接持有
gorm.DB,如有数据库操作必须下沉到Repository - 如需事务支持,在Repository中封装事务方法,Service调用封装好的方法
3.3 Service间依赖
- Service之间通过接口依赖,避免循环依赖
- 如Service A需要调用Service B的方法,确保该方法在接口中定义
4. Handler 层规范
4.1 Handler只依赖Service
// ✅ 正确
type UserHandler struct {
userService service.UserService // 仅依赖Service接口
}
func NewUserHandler(userService service.UserService) *UserHandler {
return &UserHandler{userService: userService}
}
// ❌ 错误:直接依赖Repository或Cache
type UserHandler struct {
userRepo *repository.UserRepository // 禁止
cache cache.Cache // 禁止
}
4.2 参数和类型分离
- Query参数和Filter类型应定义在
dto/包中,而非repository/包 - Handler负责将HTTP参数转换为DTO后传递给Service
5. DTO/Filter 类型规范
5.1 放置位置
- 所有查询参数、过滤条件、分页参数定义在
internal/dto/目录下 - 禁止在
repository/包中定义供Handler使用的Filter类型
5.2 文件组织
internal/dto/
├── filter.go # 通用过滤条件(LogFilter, LoginFilter等)
├── user_converter.go # 用户相关转换
└── ...
5.3 Filter类型定义示例
// internal/dto/filter.go
type LogFilter struct {
UserID string
Operation string
TargetType string
StartTime string
EndTime string
}
type PaginationParams struct {
Page int
PageSize int
}
6. Wire 依赖注入规范
6.1 Provider函数设计
// ✅ 正确:Provider返回接口类型
func ProvideUserService(
userRepo repository.UserRepository, // 接口类型
cacheBackend cache.Cache,
) service.UserService {
return service.NewUserService(userRepo, cacheBackend)
}
// ❌ 错误:返回指针类型
func ProvideUserService(userRepo *repository.UserRepository) *service.UserService
6.2 NewXxxService构造函数签名
- 构造函数参数使用接口类型
- 返回值使用接口类型
// ✅ 正确
func NewUserService(
userRepo repository.UserRepository, // 接口
cache cache.Cache,
) UserService { // 返回接口
return &userService{userRepo: userRepo}
}
6.3 禁止的直接注入
- 禁止在Handler的Provider中直接注入Repository和Cache,应仅注入Service
- 禁止在Service的Provider中直接注入
gorm.DB
7. 事务管理规范
7.1 事务边界在Repository层
// ✅ 正确:事务封装在Repository内部
func (r *userRepository) CreateUserWithProfile(user *model.User, profile *model.Profile) error {
return r.db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(user).Error; err != nil {
return err
}
profile.UserID = user.ID
return tx.Create(profile).Error
})
}
7.2 Service调用事务方法
// ✅ Service通过调用Repository的封装方法使用事务
func (s *userService) CreateUser(ctx context.Context, req *dto.CreateUserRequest) error {
user := &model.User{Name: req.Name}
profile := &model.Profile{Bio: req.Bio}
return s.userRepo.CreateUserWithProfile(user, profile)
}
8. 常见错误示例
错误1:Handler直接依赖Repository
// ❌ 错误
type PostHandler struct {
postRepo *repository.PostRepository
}
// ✅ 正确
type PostHandler struct {
postService service.PostService
}
错误2:Service直接使用gorm.DB
// ❌ 错误
type postService struct {
db *gorm.DB
}
func (s *postService) CreatePost(post *model.Post) error {
return s.db.Create(post).Error
}
// ✅ 正确:使用Repository
func (s *postService) CreatePost(post *model.Post) error {
return s.postRepo.Create(post)
}
错误3:Repository返回指针类型
// ❌ 错误
func NewUserRepository(db *gorm.DB) *UserRepository {
return &UserRepository{db: db}
}
// ✅ 正确
func NewUserRepository(db *gorm.DB) UserRepository {
return &userRepository{db: db}
}
错误4:Filter类型放在repository包
// ❌ 错误:repository包不应该被handler直接引用
package repository
type LogFilter struct {
UserID string
}
// ✅ 正确:放在dto包
package dto
type LogFilter struct {
UserID string
}
9. 新功能开发 Checklist
开发新功能时,确保满足以下规范:
-
Repository定义
- 接口 + 私有实现结构体
- 构造函数返回接口类型
- 方法参数使用值类型而非指针
- 在
wire/repository.go中注册
-
Service定义
- 接口 + 私有实现结构体
- 所有Repository依赖使用接口类型
- 不直接持有
gorm.DB - 事务操作下沉到Repository
-
Handler定义
- 仅依赖Service接口
- 不直接依赖Repository或Cache
- Query/Filter参数使用dto包类型
-
Wire Provider
- 参数使用接口类型
- 返回值使用接口类型
- 不直接注入
gorm.DB到Service
10. 相关文件位置参考
| 类别 | 目录 |
|---|---|
| Handler | internal/handler/ |
| Service | internal/service/ |
| Repository | internal/repository/ |
| DTO/Filter | internal/dto/ |
| Model | internal/model/ |
| Wire | internal/wire/ |
| Cache | internal/cache/ |
11. Wire 生成的代码规范
11.1 禁止直接修改生成的文件
以下文件由 Wire 工具自动生成,禁止直接修改:
cmd/server/wire_gen.gocmd/server/wire.go中的 Provider 函数签名由 Wire 维护
如果需要修改这些文件:
- 修改源文件(如
wire.go中的结构) - 运行
wire命令重新生成
11.2 正确的修改流程
# 1. 修改 wire.go 或 wire/*.go
# 2. 运行 wire 重新生成
go run -mod=mod github.com/google/wire/cmd/wire ./cmd/server
11.3 Provider 依赖添加
新增依赖时,必须同步修改:
wire/service.go中的 Provider 函数wire/handler.go中的 Handler Provider 函数cmd/server/wire.go中的ProvideRouter函数wire/repository.go(如需新增 Repository)- 重新运行
wire ./cmd/server生成wire_gen.go
11.4 生成文件识别
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:build !wireinject
带以上标记的文件均为自动生成文件。