2026-06-060

Go 语言仓储模式(Repository Pattern)落地指南

一、 核心架构图

仓储模式的核心思想是解耦业务底层存储。业务层通过接口与数据层交互,实现“铁打的业务,流水的技术”。

二、 标准目录结构

在 Go 项目中,通常结合 internal 目录限制外部非法引入,结构如下:

text
my-project/ ├── domain/ # 【领域层】最核心,完全纯粹,无外部技术/数据库依赖 │ ├── user.go # 用户实体、业务方法、以及 UserRepository 接口 │ └── errors.go # 领域层的通用业务错误(如 ErrUserNotFound) │ ├── internal/ # 【实现层】私有封装,防依赖污染,允许大胆重构 │ ├── repository/ # 仓储实现层 │ │ └── mysql_user.go # 实现 domain.UserRepository 接口,内含 GORM/SQL 逻辑 │ │ │ ├── service/ # 业务逻辑/应用层 │ │ └── user_service.go # 组合各种 domain 接口完成具体的业务流水线 │ │ │ └── delivery/http/ # 传输层/接口层(如 Gin 路由、HTTP Handler) │ └── handler.go │ └── main.go # 【入口】组装全局依赖(依赖注入),启动服务

三、 核心代码实现模版

  • 领域层(Domain Layer)
    • 文件位置:domain/user.go
    • 规范: 保持干净,严禁包含 gorm 或 json 等任何第三方技术标签
js
package domain import ( "context" "errors" "time" ) // 领域通用错误 var ErrUserNotFound = errors.New("user not found") // User 领域模型(面向业务逻辑) type User struct { ID int64 Name string Email string CreatedAt time.Time } // CanLogin 领域内聚的行为/业务规则 func (u *User) IsValidEmail() bool { return u.Email != "" // 示例:实际可写复杂的正则验证 } // UserRepository 仓储接口契约(向外宣告:我需要这些数据操作) type UserRepository interface { Create(ctx context.Context, user *User) error FindByID(ctx context.Context, id int64) (*User, error) }
  • 仓储层(Repository Layer)
    • 文件位置:internal/repository/mysql_user.go
    • 规范: 在内部定义私有 dbUser 模型,通过转换函数(Mapping)与 domain.User 进行隔离,从而实现数据库字段变更不影响业务层
js
package repository import ( "context" "domain" "errors" "time" "gorm.io/gorm" ) // dbUser 数据库模型(私有,专为 GORM/MySQL 映射设计) type dbUser struct { ID int64 `gorm:"primaryKey;autoIncrement"` Username string `gorm:"column:username;type:varchar(50);not null"` Email string `gorm:"column:email;type:varchar(100);uniqueIndex"` CreatedAt time.Time `gorm:"column:created_at"` UpdatedAt time.Time `gorm:"column:updated_at"` DeletedAt gorm.DeletedAt `gorm:"index"` } // TableName 显式指定表名 func (dbUser) TableName() string { return "users" } // ============================================================================= // 数据转换对象(Mapping) // ============================================================================= // toDomain: DB 模型 ➡️ Domain 模型 func (db *dbUser) toDomain() *domain.User { return &domain.User{ ID: db.ID, Name: db.Username, // 物理字段映射:数据库存 username,业务用 Name Email: db.Email, CreatedAt: db.CreatedAt, } } // toDB: Domain 模型 ➡️ DB 模型 func toDB(d *domain.User) *dbUser { return &dbUser{ ID: d.ID, Username: d.Name, Email: d.Email, CreatedAt: d.CreatedAt, } } // ============================================================================= // 接口实现 // ============================================================================= type mysqlUserRepository struct { db *gorm.DB } // NewMySQLUserRepository 构造函数(返回接口类型) func NewMySQLUserRepository(db *gorm.DB) domain.UserRepository { return &mysqlUserRepository{db: db} } func (r *mysqlUserRepository) Create(ctx context.Context, u *domain.User) error { dbData := toDB(u) if err := r.db.WithContext(ctx).Create(dbData).Error; err != nil { return err } u.ID = dbData.ID // 回填自增 ID return nil } func (r *mysqlUserRepository) FindByID(ctx context.Context, id int64) (*domain.User, error) { var dbData dbUser err := r.db.WithContext(ctx).First(&dbData, id).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, domain.ErrUserNotFound } return nil, err } return dbData.toDomain(), nil }
  • 业务应用层(Service Layer)
    • 文件位置:internal/service/user_service.go
    • 规范: 只注入接口 domain.UserRepository,不关心具体的数据库
js
package service import ( "context" "domain" "errors" ) type UserService struct { repo domain.UserRepository } func NewUserService(repo domain.UserRepository) *UserService { return &UserService{repo: repo} } func (s *UserService) Register(ctx context.Context, name, email string) error { user := &domain.User{ Name: name, Email: email, } // 调用领域自带的方法验证业务规则 if !user.IsValidEmail() { return errors.New("invalid email address") } // 通过接口持久化,屏蔽技术细节 return s.repo.Create(ctx, user) }
  • 入口组装(Main)
    • 文件位置:main.go
    • 规范: 负责全局依赖实例化并进行“泼水式”注入
js
package main import ( "context" "internal/repository" "internal/service" "gorm.io/driver/mysql" "gorm.io/gorm" ) func main() { // 1. 初始化底座(数据库连接) dsn := "root:secret@tcp(127.0.0.1:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local" db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) // 2. 初始化仓储实现 (传递真实的 DB) userRepo := repository.NewMySQLUserRepository(db) // 3. 初始化服务 (注入仓储接口) userService := service.NewUserService(userRepo) // 4. 运行业务 _ = userService.Register(context.Background(), "John Doe", "john@example.com") }

本文作者:曹子昂

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!