Skip to content

模块化单体 / Modular Monolith

结构1

user/        # 用户模块
  handler.go # HTTP API
  service.go # 业务逻辑
  repo.go    # DB 操作
  model.go   # 结构体
  router.go  # 路由注册

router 处理 1

/internal/
├── user/
│   └── handler.go
├── order/
│   └── handler.go
└── router.go
go
// router.go
func RegisterRoutes(r *gin.Engine) {
    user.RegisterUserRoutes(r)
    order.RegisterOrderRoutes(r)
}

每个模块有自己的注册方式

go
func RegisterUserRoutes(r *gin.Engine) {
    g := r.Group("/users")

    g.POST("/disable", DisableUserHandler)
}

init

go
func main() {
    // 1. 加载配置
    cfg := LoadConfig()

    // 2. 初始化基础设施
    db := db.NewMySQL(cfg.DB)
    redis := cache.NewRedis(cfg.Redis)
 
    modules := []Module{
        user.NewModule(db, redis),
        order.NewModule(db, redis),
    }

    r := gin.Default()

    for _, m := range modules {
        m.Register(r)
    }

    // 7. 启动服务
    
}

repo

✅ Domain 定义 repo 接口
✅ Service 使用 repo
❌ Domain 不直接操作 repo

Domain 应该“纯粹”

它不应该关心: 数据库、持久化、查询

domain.go 不是单纯的规则,而是“包含规则的业务对象”

domian.go 通常包括但不限yu

  • 数据(Entity)
  • 规则(Rules)
  • 行为(Behavior)

DDD

DDD 的 Domain 是基于业务边界形成的逻辑自治单元,而不是人为拆分的模块

❗ DDD 的核心不是“怎么拆”,而是“哪里是边界”

Domain = 一组“围绕同一业务概念”的规则 + 数据 +行为的集合

❗ “服务拆分”解决的是“团队协作问题”,不是“代码复用问题”

repo 处理方式

通过 service 来跨 Repo

go
// order/service.go
type OrderService struct {
  userRepo  *user.Repo  // 注入用户Repo
  orderRepo *order.Repo // 注入订单Repo
}

// 创建订单(跨2张表)
func (s *OrderService) CreateOrder(req *CreateOrderReq) error {
    // 1. 扣用户钱(调用 userRepo)
    err := s.userRepo.DeductBalance(req.UserID, req.Amount)
    if err != nil {
        return err
    }

    // 2. 创建订单(调用 orderRepo)
    err = s.orderRepo.Create(&model.Order{...})
    if err != nil {
        return err
    }

    return nil
}

项目结构1

project/
├── cmd/
│   └── server/
│       └── main.go              # 程序入口
├── internal/
│   ├── app/                    # 应用装配(依赖注入)
│   │   └── app.go
│   ├── transport/              # 接口层(HTTP / gRPC)
│   │   ├── http/
│   │   │   ├── router.go
│   │   │   ├── middleware/
│   │   │   └── handler/
│   │   │       ├── auth.go
│   │   │       ├── otp.go
│   │   │       └── user.go
│   ├── module/                 # ⭐ 核心:按领域划分
│   │   ├── auth/
│   │   │   ├── service/        # 流程(应用层)
│   │   │   │   └── login.go
│   │   │   ├── domain/         # 规则 + 实体
│   │   │   │   └── auth.go
│   │   │   ├── repository/     # 数据接口
│   │   │   │   └── repo.go
│   │   │   └── dto.go          # 输入输出结构(可选)
│   │   │
│   │   ├── otp/
│   │   │   ├── service/
│   │   │   │   ├── send.go
│   │   │   │   └── verify.go
│   │   │   ├── domain/
│   │   │   │   └── otp.go
│   │   │   ├── repository/
│   │   │   │   └── store.go
│   │   │   ├── provider/       # 外部服务(短信/邮件)
│   │   │   │   ├── sms.go
│   │   │   │   └── email.go
│   │   │   └── dto.go
│   │   │
│   │   ├── user/
│   │   │   ├── service/
│   │   │   │   └── user.go
│   │   │   ├── domain/
│   │   │   │   └── user.go
│   │   │   ├── repository/
│   │   │   │   └── repo.go
│   │   │   └── dto.go
│   ├── infra/                  # 基础设施实现
│   │   ├── db/
│   │   ├── redis/
│   │   ├── logger/
│   │   └── config/
│   ├── pkg/                    # 通用工具(可复用)
│   │   ├── jwt/
│   │   ├── utils/
│   │   └── errors/
├── configs/                    # 配置文件
├── deployments/                # docker / compose
├── go.mod
└── README.md
go
// domain
type OTP struct {
    Code string
    ExpireAt time.Time
    Used bool
}

func (o *OTP) Verify(input string) error {
    if o.Used { return errors.New("used") }
    if time.Now().After(o.ExpireAt) { return errors.New("expired") }
    if o.Code != input { return errors.New("invalid") }
    o.Used = true
    return nil
}

// service
func (s *AuthService) LoginWithOTP(...) {
    otp := repo.Get()

    otp.Verify(code)

    user := repo.FindUser()

    token := issueToken(user)

    return token
}