模块化单体 / 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.gogo
// 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.mdgo
// 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
}