2026-03-26 21:17:49 +08:00
|
|
|
|
# 后端架构设计规范
|
|
|
|
|
|
|
|
|
|
|
|
## 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必须采用接口定义 + 私有实现结构体的模式:
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
// ✅ 正确:接口 + 私有实现
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
// ❌ 错误:直接暴露结构体指针
|
|
|
|
|
|
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流程
|
|
|
|
|
|
|
|
|
|
|
|
1. 在`repository/`目录下创建`xxx_repo.go`文件
|
|
|
|
|
|
2. 定义`XxxRepository`接口,包含所有数据访问方法
|
|
|
|
|
|
3. 创建私有实现结构体`xxxRepository`
|
|
|
|
|
|
4. 创建构造函数`NewXxxRepository(db *gorm.DB) XxxRepository`
|
|
|
|
|
|
5. 在`wire/repository.go`的`RepositorySet`中注册
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 3. Service 层规范
|
|
|
|
|
|
|
|
|
|
|
|
### 3.1 接口 + 私有实现模式
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
// ✅ 正确
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
// ✅ 正确
|
|
|
|
|
|
type UserHandler struct {
|
|
|
|
|
|
userService service.UserService // 仅依赖Service接口
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func NewUserHandler(userService service.UserService) *UserHandler {
|
|
|
|
|
|
return &UserHandler{userService: userService}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
// ❌ 错误:直接依赖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类型定义示例
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
// 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函数设计
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
// ✅ 正确: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构造函数签名
|
|
|
|
|
|
|
|
|
|
|
|
- **构造函数参数使用接口类型**
|
|
|
|
|
|
- **返回值使用接口类型**
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
// ✅ 正确
|
|
|
|
|
|
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层
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
// ✅ 正确:事务封装在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调用事务方法
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
// ✅ 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
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
// ❌ 错误
|
|
|
|
|
|
type PostHandler struct {
|
|
|
|
|
|
postRepo *repository.PostRepository
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 正确
|
|
|
|
|
|
type PostHandler struct {
|
|
|
|
|
|
postService service.PostService
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 错误2:Service直接使用gorm.DB
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
// ❌ 错误
|
|
|
|
|
|
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返回指针类型
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
// ❌ 错误
|
|
|
|
|
|
func NewUserRepository(db *gorm.DB) *UserRepository {
|
|
|
|
|
|
return &UserRepository{db: db}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 正确
|
|
|
|
|
|
func NewUserRepository(db *gorm.DB) UserRepository {
|
|
|
|
|
|
return &userRepository{db: db}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 错误4:Filter类型放在repository包
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
// ❌ 错误: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/` |
|
2026-03-30 04:49:35 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 11. Wire 生成的代码规范
|
|
|
|
|
|
|
|
|
|
|
|
### 11.1 禁止直接修改生成的文件
|
|
|
|
|
|
|
|
|
|
|
|
以下文件由 Wire 工具自动生成,**禁止直接修改**:
|
|
|
|
|
|
|
|
|
|
|
|
- `cmd/server/wire_gen.go`
|
|
|
|
|
|
- `cmd/server/wire.go` 中的 Provider 函数签名由 Wire 维护
|
|
|
|
|
|
|
|
|
|
|
|
如果需要修改这些文件:
|
|
|
|
|
|
1. **修改源文件**(如 `wire.go` 中的结构)
|
|
|
|
|
|
2. **运行 `wire` 命令**重新生成
|
|
|
|
|
|
|
|
|
|
|
|
### 11.2 正确的修改流程
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 1. 修改 wire.go 或 wire/*.go
|
|
|
|
|
|
# 2. 运行 wire 重新生成
|
|
|
|
|
|
go run -mod=mod github.com/google/wire/cmd/wire ./cmd/server
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 11.3 Provider 依赖添加
|
|
|
|
|
|
|
|
|
|
|
|
新增依赖时,必须同步修改:
|
|
|
|
|
|
|
|
|
|
|
|
1. `wire/service.go` 中的 Provider 函数
|
|
|
|
|
|
2. `wire/handler.go` 中的 Handler Provider 函数
|
|
|
|
|
|
3. `cmd/server/wire.go` 中的 `ProvideRouter` 函数
|
|
|
|
|
|
4. `wire/repository.go`(如需新增 Repository)
|
|
|
|
|
|
5. 重新运行 `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
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
带以上标记的文件均为自动生成文件。
|