仓储模式的核心思想是解耦业务与底层存储。业务层通过接口与数据层交互,实现“铁打的业务,流水的技术”。
在 Go 项目中,通常结合 internal 目录限制外部非法引入,结构如下:
textmy-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 # 【入口】组装全局依赖(依赖注入),启动服务
jspackage 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)
}
jspackage 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
}
jspackage 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)
}
jspackage 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 许可协议。转载请注明出处!