# DDD领域开发指南 ## 概述 本文档描述了在tyapi-server项目中如何按照DDD(领域驱动设计)架构模式新增领域或领域内功能的标准流程和最佳实践。 ## 目录结构 ``` tyapi-server-gin/ ├── internal/ │ ├── domains/ # 领域层 │ │ └── {domain}/ # 具体领域 │ │ ├── entities/ # 实体 │ │ ├── repositories/ # 仓储接口 │ │ └── services/ # 领域服务 │ ├── application/ # 应用层 │ │ └── {domain}/ # 具体领域 │ │ ├── dto/ # 数据传输对象 │ │ └── {service}_application_service.go │ ├── infrastructure/ # 基础设施层 │ │ ├── database/repositories/ # 仓储实现 │ │ └── http/ # HTTP层 │ │ ├── handlers/ # 处理器 │ │ └── routes/ # 路由 │ └── container/ # 依赖注入容器 └── docs/ # 文档 ``` ## 新增领域的完整流程 ### 第一步:创建实体 (Entities) #### 1.1 主实体文件 ```go // internal/domains/{domain}/entities/{entity}.go package entities import ( "time" "gorm.io/gorm" ) // {Entity} 实体 type {Entity} struct { ID string `gorm:"primaryKey;type:varchar(36)" comment:"ID"` Name string `gorm:"type:varchar(100);not null" comment:"名称"` Description string `gorm:"type:text" comment:"描述"` IsEnabled bool `gorm:"default:true" comment:"是否启用"` IsVisible bool `gorm:"default:true" comment:"是否可见"` CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"` UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"` DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"` } // 业务方法 func (e *{Entity}) IsValid() bool { return e.DeletedAt.Time.IsZero() && e.IsEnabled } func (e *{Entity}) Enable() { e.IsEnabled = true } func (e *{Entity}) Disable() { e.IsEnabled = false } ``` #### 1.2 枚举文件(如需要) ```go // internal/domains/{domain}/entities/{entity}_enums.go package entities // {Entity}Status 状态枚举 type {Entity}Status string const ( {Entity}StatusActive {Entity}Status = "active" {Entity}StatusInactive {Entity}Status = "inactive" {Entity}StatusPending {Entity}Status = "pending" ) ``` ### 第二步:创建仓储接口 (Repository Interfaces) #### 2.1 仓储接口定义 ```go // internal/domains/{domain}/repositories/{entity}_repository_interface.go package repositories import ( "context" "tyapi-server/internal/domains/{domain}/entities" "tyapi-server/internal/shared/interfaces" ) // {Entity}Repository 仓储接口 type {Entity}Repository interface { // 继承基础Repository接口 interfaces.Repository[entities.{Entity}] // 业务特定方法 FindByStatus(ctx context.Context, status entities.{Entity}Status) ([]*entities.{Entity}, error) FindByUserID(ctx context.Context, userID string) ([]*entities.{Entity}, error) CountByStatus(ctx context.Context, status entities.{Entity}Status) (int64, error) } ``` #### 2.2 查询对象 ```go // internal/domains/{domain}/repositories/queries/{entity}_queries.go package queries import "tyapi-server/internal/domains/{domain}/entities" // List{Entity}sQuery 列表查询 type List{Entity}sQuery struct { Page int `form:"page" binding:"min=1" comment:"页码"` PageSize int `form:"page_size" binding:"min=1,max=100" comment:"每页数量"` Status entities.{Entity}Status `form:"status" comment:"状态"` UserID string `form:"user_id" comment:"用户ID"` SortBy string `form:"sort_by" comment:"排序字段"` SortOrder string `form:"sort_order" comment:"排序方向"` } // Get{Entity}Query 获取详情查询 type Get{Entity}Query struct { ID string `uri:"id" binding:"required" comment:"ID"` } ``` ### 第三步:创建应用服务 (Application Services) #### 3.1 应用服务接口 ```go // internal/application/{domain}/{entity}_application_service.go package {domain} import ( "context" "tyapi-server/internal/application/{domain}/dto/commands" "tyapi-server/internal/application/{domain}/dto/queries" "tyapi-server/internal/application/{domain}/dto/responses" ) // {Entity}ApplicationService 应用服务接口 type {Entity}ApplicationService interface { // 基础CRUD操作 Create{Entity}(ctx context.Context, cmd *commands.Create{Entity}Command) error Update{Entity}(ctx context.Context, cmd *commands.Update{Entity}Command) error Delete{Entity}(ctx context.Context, cmd *commands.Delete{Entity}Command) error Get{Entity}ByID(ctx context.Context, query *queries.Get{Entity}Query) (*responses.{Entity}InfoResponse, error) List{Entity}s(ctx context.Context, query *queries.List{Entity}sQuery) (*responses.{Entity}ListResponse, error) // 业务操作 Enable{Entity}(ctx context.Context, cmd *commands.Enable{Entity}Command) error Disable{Entity}(ctx context.Context, cmd *commands.Disable{Entity}Command) error // 统计 Get{Entity}Stats(ctx context.Context) (*responses.{Entity}StatsResponse, error) } ``` #### 3.2 应用服务实现 ```go // internal/application/{domain}/{entity}_application_service_impl.go package {domain} import ( "context" "fmt" "tyapi-server/internal/application/{domain}/dto/commands" "tyapi-server/internal/application/{domain}/dto/queries" "tyapi-server/internal/application/{domain}/dto/responses" "tyapi-server/internal/domains/{domain}/entities" "tyapi-server/internal/domains/{domain}/repositories" repoQueries "tyapi-server/internal/domains/{domain}/repositories/queries" "go.uber.org/zap" ) // {Entity}ApplicationServiceImpl 应用服务实现 type {Entity}ApplicationServiceImpl struct { {entity}Repo repositories.{Entity}Repository logger *zap.Logger } // New{Entity}ApplicationService 创建应用服务 func New{Entity}ApplicationService( {entity}Repo repositories.{Entity}Repository, logger *zap.Logger, ) {Entity}ApplicationService { return &{Entity}ApplicationServiceImpl{ {entity}Repo: {entity}Repo, logger: logger, } } // Create{Entity} 创建实体 func (s *{Entity}ApplicationServiceImpl) Create{Entity}(ctx context.Context, cmd *commands.Create{Entity}Command) error { // 1. 参数验证 if err := s.validateCreate{Entity}(cmd); err != nil { return err } // 2. 创建实体 {entity} := entities.{Entity}{ Name: cmd.Name, Description: cmd.Description, IsEnabled: cmd.IsEnabled, IsVisible: cmd.IsVisible, } // 3. 保存到仓储 _, err := s.{entity}Repo.Create(ctx, {entity}) if err != nil { s.logger.Error("创建{Entity}失败", zap.Error(err)) return fmt.Errorf("创建{Entity}失败: %w", err) } s.logger.Info("创建{Entity}成功", zap.String("name", cmd.Name)) return nil } // 其他方法实现... ``` ### 第四步:创建DTO (Data Transfer Objects) #### 4.1 命令DTO ```go // internal/application/{domain}/dto/commands/{entity}_commands.go package commands // Create{Entity}Command 创建命令 type Create{Entity}Command struct { Name string `json:"name" binding:"required" comment:"名称"` Description string `json:"description" comment:"描述"` IsEnabled bool `json:"is_enabled" comment:"是否启用"` IsVisible bool `json:"is_visible" comment:"是否可见"` } // Update{Entity}Command 更新命令 type Update{Entity}Command struct { ID string `json:"-"` Name string `json:"name" comment:"名称"` Description string `json:"description" comment:"描述"` IsEnabled *bool `json:"is_enabled" comment:"是否启用"` IsVisible *bool `json:"is_visible" comment:"是否可见"` } // Delete{Entity}Command 删除命令 type Delete{Entity}Command struct { ID string `json:"-"` } // Enable{Entity}Command 启用命令 type Enable{Entity}Command struct { ID string `json:"-"` } // Disable{Entity}Command 禁用命令 type Disable{Entity}Command struct { ID string `json:"-"` } ``` #### 4.2 查询DTO ```go // internal/application/{domain}/dto/queries/{entity}_queries.go package queries import "tyapi-server/internal/domains/{domain}/entities" // List{Entity}sQuery 列表查询 type List{Entity}sQuery struct { Page int `form:"page" binding:"min=1" comment:"页码"` PageSize int `form:"page_size" binding:"min=1,max=100" comment:"每页数量"` Status entities.{Entity}Status `form:"status" comment:"状态"` UserID string `form:"user_id" comment:"用户ID"` SortBy string `form:"sort_by" comment:"排序字段"` SortOrder string `form:"sort_order" comment:"排序方向"` } // Get{Entity}Query 获取详情查询 type Get{Entity}Query struct { ID string `uri:"id" binding:"required" comment:"ID"` } ``` #### 4.3 响应DTO ```go // internal/application/{domain}/dto/responses/{entity}_responses.go package responses import "time" // {Entity}InfoResponse 详情响应 type {Entity}InfoResponse struct { ID string `json:"id" comment:"ID"` Name string `json:"name" comment:"名称"` Description string `json:"description" comment:"描述"` IsEnabled bool `json:"is_enabled" comment:"是否启用"` IsVisible bool `json:"is_visible" comment:"是否可见"` CreatedAt time.Time `json:"created_at" comment:"创建时间"` UpdatedAt time.Time `json:"updated_at" comment:"更新时间"` } // {Entity}ListResponse 列表响应 type {Entity}ListResponse struct { Total int64 `json:"total" comment:"总数"` Page int `json:"page" comment:"页码"` Size int `json:"size" comment:"每页数量"` Items []{Entity}InfoResponse `json:"items" comment:"列表"` } // {Entity}StatsResponse 统计响应 type {Entity}StatsResponse struct { Total{Entity}s int64 `json:"total_{entity}s" comment:"总数"` Enabled{Entity}s int64 `json:"enabled_{entity}s" comment:"启用数"` Visible{Entity}s int64 `json:"visible_{entity}s" comment:"可见数"` } ``` ### 第五步:实现仓储 (Repository Implementation) #### 5.1 GORM仓储实现 ```go // internal/infrastructure/database/repositories/{domain}/gorm_{entity}_repository.go package repositories import ( "context" "tyapi-server/internal/domains/{domain}/entities" "tyapi-server/internal/domains/{domain}/repositories" "tyapi-server/internal/domains/{domain}/repositories/queries" "tyapi-server/internal/shared/interfaces" "go.uber.org/zap" "gorm.io/gorm" ) // Gorm{Entity}Repository GORM仓储实现 type Gorm{Entity}Repository struct { db *gorm.DB logger *zap.Logger } // 编译时检查接口实现 var _ repositories.{Entity}Repository = (*Gorm{Entity}Repository)(nil) // NewGorm{Entity}Repository 创建GORM仓储 func NewGorm{Entity}Repository(db *gorm.DB, logger *zap.Logger) repositories.{Entity}Repository { return &Gorm{Entity}Repository{ db: db, logger: logger, } } // Create 创建实体 func (r *Gorm{Entity}Repository) Create(ctx context.Context, entity entities.{Entity}) (entities.{Entity}, error) { r.logger.Info("创建{Entity}", zap.String("id", entity.ID)) err := r.db.WithContext(ctx).Create(&entity).Error return entity, err } // GetByID 根据ID获取实体 func (r *Gorm{Entity}Repository) GetByID(ctx context.Context, id string) (entities.{Entity}, error) { var entity entities.{Entity} err := r.db.WithContext(ctx).Where("id = ?", id).First(&entity).Error return entity, err } // Update 更新实体 func (r *Gorm{Entity}Repository) Update(ctx context.Context, entity entities.{Entity}) error { r.logger.Info("更新{Entity}", zap.String("id", entity.ID)) return r.db.WithContext(ctx).Save(&entity).Error } // Delete 删除实体 func (r *Gorm{Entity}Repository) Delete(ctx context.Context, id string) error { r.logger.Info("删除{Entity}", zap.String("id", id)) return r.db.WithContext(ctx).Delete(&entities.{Entity}{}, "id = ?", id).Error } // List{Entity}s 获取列表 func (r *Gorm{Entity}Repository) List{Entity}s(ctx context.Context, query *queries.List{Entity}sQuery) ([]*entities.{Entity}, int64, error) { var {entity}s []entities.{Entity} var total int64 dbQuery := r.db.WithContext(ctx).Model(&entities.{Entity}{}) // 应用筛选条件 if query.Status != "" { dbQuery = dbQuery.Where("status = ?", query.Status) } if query.UserID != "" { dbQuery = dbQuery.Where("user_id = ?", query.UserID) } // 获取总数 if err := dbQuery.Count(&total).Error; err != nil { return nil, 0, err } // 应用排序 if query.SortBy != "" { order := query.SortBy if query.SortOrder == "desc" { order += " DESC" } else { order += " ASC" } dbQuery = dbQuery.Order(order) } else { dbQuery = dbQuery.Order("created_at DESC") } // 应用分页 if query.Page > 0 && query.PageSize > 0 { offset := (query.Page - 1) * query.PageSize dbQuery = dbQuery.Offset(offset).Limit(query.PageSize) } // 获取数据 if err := dbQuery.Find(&{entity}s).Error; err != nil { return nil, 0, err } // 转换为指针切片 result := make([]*entities.{Entity}, len({entity}s)) for i := range {entity}s { result[i] = &{entity}s[i] } return result, total, nil } // 基础Repository接口方法 func (r *Gorm{Entity}Repository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) { var count int64 query := r.db.WithContext(ctx).Model(&entities.{Entity}{}) // 应用筛选条件 if options.Filters != nil { for key, value := range options.Filters { query = query.Where(key+" = ?", value) } } // 应用搜索条件 if options.Search != "" { query = query.Where("name LIKE ? OR description LIKE ?", "%"+options.Search+"%", "%"+options.Search+"%") } err := query.Count(&count).Error return count, err } // 其他基础方法实现... ``` ### 第六步:创建HTTP处理器 (HTTP Handlers) #### 6.1 HTTP处理器 ```go // internal/infrastructure/http/handlers/{entity}_handler.go package handlers import ( "tyapi-server/internal/application/{domain}" "tyapi-server/internal/application/{domain}/dto/commands" "tyapi-server/internal/application/{domain}/dto/queries" "tyapi-server/internal/shared/interfaces" "github.com/gin-gonic/gin" "go.uber.org/zap" ) // {Entity}Handler HTTP处理器 type {Entity}Handler struct { appService {domain}.{Entity}ApplicationService responseBuilder interfaces.ResponseBuilder logger *zap.Logger } // New{Entity}Handler 创建HTTP处理器 func New{Entity}Handler( appService {domain}.{Entity}ApplicationService, responseBuilder interfaces.ResponseBuilder, logger *zap.Logger, ) *{Entity}Handler { return &{Entity}Handler{ appService: appService, responseBuilder: responseBuilder, logger: logger, } } // List{Entity}s 获取列表 // @Summary 获取{Entity}列表 // @Description 分页获取{Entity}列表,支持筛选 // @Tags {Entity}管理 // @Accept json // @Produce json // @Param page query int false "页码" default(1) // @Param page_size query int false "每页数量" default(10) // @Param status query string false "状态" // @Param sort_by query string false "排序字段" // @Param sort_order query string false "排序方向" Enums(asc, desc) // @Success 200 {object} responses.{Entity}ListResponse "获取列表成功" // @Failure 400 {object} map[string]interface{} "请求参数错误" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/{entity}s [get] func (h *{Entity}Handler) List{Entity}s(c *gin.Context) { var query queries.List{Entity}sQuery if err := c.ShouldBindQuery(&query); err != nil { h.responseBuilder.BadRequest(c, "请求参数错误") return } // 设置默认值 if query.Page <= 0 { query.Page = 1 } if query.PageSize <= 0 { query.PageSize = 10 } if query.PageSize > 100 { query.PageSize = 100 } result, err := h.appService.List{Entity}s(c.Request.Context(), &query) if err != nil { h.logger.Error("获取{Entity}列表失败", zap.Error(err)) h.responseBuilder.InternalError(c, "获取{Entity}列表失败") return } h.responseBuilder.Success(c, result, "获取{Entity}列表成功") } // Get{Entity}Detail 获取详情 // @Summary 获取{Entity}详情 // @Description 根据ID获取{Entity}详细信息 // @Tags {Entity}管理 // @Accept json // @Produce json // @Param id path string true "{Entity}ID" // @Success 200 {object} responses.{Entity}InfoResponse "获取详情成功" // @Failure 400 {object} map[string]interface{} "请求参数错误" // @Failure 404 {object} map[string]interface{} "{Entity}不存在" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/{entity}s/{id} [get] func (h *{Entity}Handler) Get{Entity}Detail(c *gin.Context) { var query queries.Get{Entity}Query query.ID = c.Param("id") if query.ID == "" { h.responseBuilder.BadRequest(c, "{Entity}ID不能为空") return } result, err := h.appService.Get{Entity}ByID(c.Request.Context(), &query) if err != nil { h.logger.Error("获取{Entity}详情失败", zap.Error(err), zap.String("{entity}_id", query.ID)) h.responseBuilder.NotFound(c, "{Entity}不存在") return } h.responseBuilder.Success(c, result, "获取{Entity}详情成功") } // Create{Entity} 创建{Entity} // @Summary 创建{Entity} // @Description 创建新的{Entity} // @Tags {Entity}管理 // @Accept json // @Produce json // @Param request body commands.Create{Entity}Command true "创建请求" // @Success 200 {object} map[string]interface{} "创建成功" // @Failure 400 {object} map[string]interface{} "请求参数错误" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/{entity}s [post] func (h *{Entity}Handler) Create{Entity}(c *gin.Context) { var cmd commands.Create{Entity}Command if err := c.ShouldBindJSON(&cmd); err != nil { h.responseBuilder.BadRequest(c, "请求参数错误") return } if err := h.appService.Create{Entity}(c.Request.Context(), &cmd); err != nil { h.logger.Error("创建{Entity}失败", zap.Error(err)) h.responseBuilder.BadRequest(c, err.Error()) return } h.responseBuilder.Success(c, nil, "创建{Entity}成功") } // Update{Entity} 更新{Entity} // @Summary 更新{Entity} // @Description 更新{Entity}信息 // @Tags {Entity}管理 // @Accept json // @Produce json // @Param id path string true "{Entity}ID" // @Param request body commands.Update{Entity}Command true "更新请求" // @Success 200 {object} map[string]interface{} "更新成功" // @Failure 400 {object} map[string]interface{} "请求参数错误" // @Failure 404 {object} map[string]interface{} "{Entity}不存在" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/{entity}s/{id} [put] func (h *{Entity}Handler) Update{Entity}(c *gin.Context) { id := c.Param("id") if id == "" { h.responseBuilder.BadRequest(c, "{Entity}ID不能为空") return } var cmd commands.Update{Entity}Command if err := c.ShouldBindJSON(&cmd); err != nil { h.responseBuilder.BadRequest(c, "请求参数错误") return } cmd.ID = id if err := h.appService.Update{Entity}(c.Request.Context(), &cmd); err != nil { h.logger.Error("更新{Entity}失败", zap.Error(err), zap.String("{entity}_id", id)) h.responseBuilder.BadRequest(c, err.Error()) return } h.responseBuilder.Success(c, nil, "更新{Entity}成功") } // Delete{Entity} 删除{Entity} // @Summary 删除{Entity} // @Description 删除指定的{Entity} // @Tags {Entity}管理 // @Accept json // @Produce json // @Param id path string true "{Entity}ID" // @Success 200 {object} map[string]interface{} "删除成功" // @Failure 400 {object} map[string]interface{} "请求参数错误" // @Failure 404 {object} map[string]interface{} "{Entity}不存在" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/{entity}s/{id} [delete] func (h *{Entity}Handler) Delete{Entity}(c *gin.Context) { id := c.Param("id") if id == "" { h.responseBuilder.BadRequest(c, "{Entity}ID不能为空") return } var cmd commands.Delete{Entity}Command cmd.ID = id if err := h.appService.Delete{Entity}(c.Request.Context(), &cmd); err != nil { h.logger.Error("删除{Entity}失败", zap.Error(err), zap.String("{entity}_id", id)) h.responseBuilder.BadRequest(c, err.Error()) return } h.responseBuilder.Success(c, nil, "删除{Entity}成功") } // Enable{Entity} 启用{Entity} // @Summary 启用{Entity} // @Description 启用指定的{Entity} // @Tags {Entity}管理 // @Accept json // @Produce json // @Param id path string true "{Entity}ID" // @Success 200 {object} map[string]interface{} "启用成功" // @Failure 400 {object} map[string]interface{} "请求参数错误" // @Failure 404 {object} map[string]interface{} "{Entity}不存在" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/{entity}s/{id}/enable [post] func (h *{Entity}Handler) Enable{Entity}(c *gin.Context) { id := c.Param("id") if id == "" { h.responseBuilder.BadRequest(c, "{Entity}ID不能为空") return } var cmd commands.Enable{Entity}Command cmd.ID = id if err := h.appService.Enable{Entity}(c.Request.Context(), &cmd); err != nil { h.logger.Error("启用{Entity}失败", zap.Error(err), zap.String("{entity}_id", id)) h.responseBuilder.BadRequest(c, err.Error()) return } h.responseBuilder.Success(c, nil, "启用{Entity}成功") } // Disable{Entity} 禁用{Entity} // @Summary 禁用{Entity} // @Description 禁用指定的{Entity} // @Tags {Entity}管理 // @Accept json // @Produce json // @Param id path string true "{Entity}ID" // @Success 200 {object} map[string]interface{} "禁用成功" // @Failure 400 {object} map[string]interface{} "请求参数错误" // @Failure 404 {object} map[string]interface{} "{Entity}不存在" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/{entity}s/{id}/disable [post] func (h *{Entity}Handler) Disable{Entity}(c *gin.Context) { id := c.Param("id") if id == "" { h.responseBuilder.BadRequest(c, "{Entity}ID不能为空") return } var cmd commands.Disable{Entity}Command cmd.ID = id if err := h.appService.Disable{Entity}(c.Request.Context(), &cmd); err != nil { h.logger.Error("禁用{Entity}失败", zap.Error(err), zap.String("{entity}_id", id)) h.responseBuilder.BadRequest(c, err.Error()) return } h.responseBuilder.Success(c, nil, "禁用{Entity}成功") } // Get{Entity}Stats 获取统计信息 // @Summary 获取{Entity}统计信息 // @Description 获取{Entity}相关的统计信息 // @Tags {Entity}管理 // @Accept json // @Produce json // @Success 200 {object} responses.{Entity}StatsResponse "获取统计成功" // @Failure 500 {object} map[string]interface{} "服务器内部错误" // @Router /api/v1/{entity}s/stats [get] func (h *{Entity}Handler) Get{Entity}Stats(c *gin.Context) { result, err := h.appService.Get{Entity}Stats(c.Request.Context()) if err != nil { h.logger.Error("获取{Entity}统计失败", zap.Error(err)) h.responseBuilder.InternalError(c, "获取{Entity}统计失败") return } h.responseBuilder.Success(c, result, "获取{Entity}统计成功") } ``` ### 第七步:创建路由 (Routes) #### 7.1 路由配置 ```go // internal/infrastructure/http/routes/{entity}_routes.go package routes import ( "tyapi-server/internal/infrastructure/http/handlers" "tyapi-server/internal/shared/http" "github.com/gin-gonic/gin" ) // Register{Entity}Routes 注册{Entity}路由 func Register{Entity}Routes(router *gin.RouterGroup, handler *handlers.{Entity}Handler) { {entity}Group := router.Group("/{entity}s") { // 基础CRUD操作 {entity}Group.GET("", handler.List{Entity}s) // 获取列表 {entity}Group.GET("/:id", handler.Get{Entity}Detail) // 获取详情 {entity}Group.POST("", handler.Create{Entity}) // 创建 {entity}Group.PUT("/:id", handler.Update{Entity}) // 更新 {entity}Group.DELETE("/:id", handler.Delete{Entity}) // 删除 // 业务操作 {entity}Group.POST("/:id/enable", handler.Enable{Entity}) // 启用 {entity}Group.POST("/:id/disable", handler.Disable{Entity}) // 禁用 // 统计信息 {entity}Group.GET("/stats", handler.Get{Entity}Stats) // 统计 } } ``` ### 第八步:配置依赖注入 (Dependency Injection) #### 8.1 容器配置 ```go // internal/container/container.go 中添加 func (c *Container) register{Entity}Services() { // 注册仓储 c.{entity}Repository = database.NewGorm{Entity}Repository(c.db, c.logger) // 注册应用服务 c.{entity}ApplicationService = application.New{Entity}ApplicationService( c.{entity}Repository, c.logger, ) // 注册HTTP处理器 c.{entity}Handler = handlers.New{Entity}Handler( c.{entity}ApplicationService, c.responseBuilder, c.logger, ) } // 在registerAllServices方法中调用 func (c *Container) registerAllServices() { // ... 其他服务注册 c.register{Entity}Services() } // 在registerAllRoutes方法中注册路由 func (c *Container) registerAllRoutes() { // ... 其他路由注册 routes.Register{Entity}Routes(c.apiGroup, c.{entity}Handler) } ``` ## 测试指南 ### 单元测试 #### 1. 实体测试 ```go // internal/domains/{domain}/entities/{entity}_test.go package entities import ( "testing" "time" ) func Test{Entity}_IsValid(t *testing.T) { tests := []struct { name string {entity} {Entity} want bool }{ { name: "有效的{Entity}", {entity}: {Entity}{ ID: "test-id", Name: "测试{Entity}", IsEnabled: true, }, want: true, }, { name: "禁用的{Entity}", {entity}: {Entity}{ ID: "test-id", Name: "测试{Entity}", IsEnabled: false, }, want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.{entity}.IsValid(); got != tt.want { t.Errorf("{Entity}.IsValid() = %v, want %v", got, tt.want) } }) } } ``` #### 2. 应用服务测试 ```go // internal/application/{domain}/{entity}_application_service_test.go package {domain} import ( "context" "testing" "tyapi-server/internal/application/{domain}/dto/commands" "tyapi-server/internal/domains/{domain}/entities" "github.com/stretchr/testify/mock" "go.uber.org/zap" ) // Mock{Entity}Repository 模拟仓储 type Mock{Entity}Repository struct { mock.Mock } func (m *Mock{Entity}Repository) Create(ctx context.Context, entity entities.{Entity}) (entities.{Entity}, error) { args := m.Called(ctx, entity) return args.Get(0).(entities.{Entity}), args.Error(1) } // 其他方法实现... func Test{Entity}ApplicationService_Create{Entity}(t *testing.T) { mockRepo := new(Mock{Entity}Repository) logger := zap.NewNop() service := &{Entity}ApplicationServiceImpl{ {entity}Repo: mockRepo, logger: logger, } cmd := &commands.Create{Entity}Command{ Name: "测试{Entity}", Description: "测试描述", IsEnabled: true, } expected{Entity} := entities.{Entity}{ Name: cmd.Name, Description: cmd.Description, IsEnabled: cmd.IsEnabled, } mockRepo.On("Create", mock.Anything, expected{Entity}).Return(expected{Entity}, nil) err := service.Create{Entity}(context.Background(), cmd) assert.NoError(t, err) mockRepo.AssertExpectations(t) } ``` ### 集成测试 #### 1. HTTP处理器测试 ```go // internal/infrastructure/http/handlers/{entity}_handler_test.go package handlers import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "tyapi-server/internal/application/{domain}/dto/commands" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) func Test{Entity}Handler_Create{Entity}(t *testing.T) { gin.SetMode(gin.TestMode) mockAppService := new(Mock{Entity}ApplicationService) mockResponseBuilder := new(MockResponseBuilder) handler := &{Entity}Handler{ appService: mockAppService, responseBuilder: mockResponseBuilder, } cmd := commands.Create{Entity}Command{ Name: "测试{Entity}", Description: "测试描述", IsEnabled: true, } body, _ := json.Marshal(cmd) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Request = httptest.NewRequest("POST", "/{entity}s", bytes.NewBuffer(body)) c.Request.Header.Set("Content-Type", "application/json") mockAppService.On("Create{Entity}", mock.Anything, &cmd).Return(nil) mockResponseBuilder.On("Success", c, nil, "创建{Entity}成功").Return() handler.Create{Entity}(c) assert.Equal(t, http.StatusOK, w.Code) mockAppService.AssertExpectations(t) mockResponseBuilder.AssertExpectations(t) } ``` ## 最佳实践 ### 1. 命名规范 #### 实体命名 - 实体名称使用单数形式,首字母大写 - 文件名使用小写,下划线分隔 - 示例:`Product` → `product.go` #### 仓储命名 - 接口名称:`{Entity}Repository` - 实现名称:`Gorm{Entity}Repository` - 文件名:`{entity}_repository_interface.go`、`gorm_{entity}_repository.go` #### 应用服务命名 - 接口名称:`{Entity}ApplicationService` - 实现名称:`{Entity}ApplicationServiceImpl` - 文件名:`{entity}_application_service.go`、`{entity}_application_service_impl.go` #### HTTP处理器命名 - 结构体名称:`{Entity}Handler` - 文件名:`{entity}_handler.go` ### 2. 错误处理 #### 统一错误响应 ```go // 使用统一的响应构建器 h.responseBuilder.BadRequest(c, "请求参数错误") h.responseBuilder.NotFound(c, "{Entity}不存在") h.responseBuilder.InternalError(c, "服务器内部错误") ``` #### 业务错误处理 ```go // 在应用服务中抛出业务错误 if entity.IsDeleted() { return fmt.Errorf("{Entity}已被删除") } if !entity.IsEnabled { return fmt.Errorf("{Entity}已被禁用") } ``` ### 3. 日志记录 #### 结构化日志 ```go // 使用结构化字段 logger.Info("创建{Entity}成功", zap.String("id", entity.ID), zap.String("name", entity.Name), ) logger.Error("删除{Entity}失败", zap.Error(err), zap.String("id", id), ) ``` #### 日志级别 - `Info`: 业务操作成功 - `Warn`: 业务警告(如重复操作) - `Error`: 业务错误和系统错误 - `Debug`: 调试信息 ### 4. 参数验证 #### 请求参数验证 ```go // 使用binding标签进行验证 type Create{Entity}Command struct { Name string `json:"name" binding:"required,min=1,max=100" comment:"名称"` Description string `json:"description" binding:"max=500" comment:"描述"` IsEnabled bool `json:"is_enabled" comment:"是否启用"` } ``` #### 业务规则验证 ```go // 在应用服务中进行业务规则验证 func (s *{Entity}ApplicationServiceImpl) validateCreate{Entity}(cmd *commands.Create{Entity}Command) error { if cmd.Name == "" { return fmt.Errorf("名称不能为空") } if len(cmd.Name) > 100 { return fmt.Errorf("名称长度不能超过100个字符") } return nil } ``` ### 5. 数据库设计 #### 表结构规范 ```go type {Entity} struct { ID string `gorm:"primaryKey;type:varchar(36)" comment:"ID"` Name string `gorm:"type:varchar(100);not null;index" comment:"名称"` Description string `gorm:"type:text" comment:"描述"` IsEnabled bool `gorm:"default:true;index" comment:"是否启用"` IsVisible bool `gorm:"default:true" comment:"是否可见"` CreatedAt time.Time `gorm:"autoCreateTime;index" comment:"创建时间"` UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"` DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"` } ``` #### 索引设计 - 主键:`ID` - 业务索引:`Name`、`IsEnabled`、`CreatedAt` - 软删除索引:`DeletedAt` ### 6. API设计 #### RESTful规范 ``` GET /api/v1/{entity}s # 获取列表 GET /api/v1/{entity}s/:id # 获取详情 POST /api/v1/{entity}s # 创建 PUT /api/v1/{entity}s/:id # 更新 DELETE /api/v1/{entity}s/:id # 删除 POST /api/v1/{entity}s/:id/enable # 启用 POST /api/v1/{entity}s/:id/disable # 禁用 GET /api/v1/{entity}s/stats # 统计 ``` #### 查询参数 ``` GET /api/v1/{entity}s?page=1&page_size=10&status=active&sort_by=created_at&sort_order=desc ``` #### 响应格式 ```json { "code": 200, "message": "操作成功", "data": { "total": 100, "page": 1, "size": 10, "items": [...] } } ``` ### 7. 性能优化 #### 分页查询 ```go // 使用LIMIT和OFFSET进行分页 dbQuery = dbQuery.Offset(offset).Limit(pageSize) ``` #### 索引优化 ```go // 为常用查询字段添加索引 `gorm:"index"` // 普通索引 `gorm:"uniqueIndex"` // 唯一索引 `gorm:"index:idx_name_status"` // 复合索引 ``` #### 缓存策略 ```go // 对于频繁查询的数据使用缓存 func (r *Gorm{Entity}Repository) GetByID(ctx context.Context, id string) (entities.{Entity}, error) { // 先从缓存获取 if cached, err := r.cache.Get(ctx, "entity:"+id); err == nil { return cached, nil } // 从数据库获取 var entity entities.{Entity} err := r.db.WithContext(ctx).Where("id = ?", id).First(&entity).Error if err != nil { return entity, err } // 存入缓存 r.cache.Set(ctx, "entity:"+id, entity, time.Hour) return entity, nil } ``` ## 常见问题 ### 1. 循环依赖 **问题**: 领域层和应用层之间出现循环依赖 **解决**: 使用接口解耦,领域层定义接口,应用层实现接口 ### 2. 事务管理 **问题**: 跨仓储操作需要事务支持 **解决**: 在应用服务层使用事务装饰器 ```go func (s *{Entity}ApplicationServiceImpl) Create{Entity}WithTransaction(ctx context.Context, cmd *commands.Create{Entity}Command) error { return s.db.Transaction(func(tx *gorm.DB) error { // 事务操作 return nil }) } ``` ### 3. 并发控制 **问题**: 高并发场景下的数据一致性问题 **解决**: 使用乐观锁或悲观锁 ```go // 乐观锁 type {Entity} struct { Version int `gorm:"default:1" comment:"版本号"` } // 悲观锁 func (r *Gorm{Entity}Repository) GetByIDForUpdate(ctx context.Context, id string) (entities.{Entity}, error) { var entity entities.{Entity} err := r.db.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", id).First(&entity).Error return entity, err } ``` ### 4. 数据迁移 **问题**: 实体结构变更需要数据库迁移 **解决**: 使用GORM的AutoMigrate或手动编写迁移脚本 ```go // 自动迁移 db.AutoMigrate(&entities.{Entity}{}) // 手动迁移 db.Exec("ALTER TABLE {entity}s ADD COLUMN new_field VARCHAR(255)") ``` ## 总结 本文档提供了在tyapi-server项目中新增领域或领域内功能的完整指南。遵循DDD架构模式,确保代码的可维护性、可扩展性和可测试性。 关键要点: 1. **分层架构**: 严格遵循领域层、应用层、基础设施层的分层原则 2. **依赖倒置**: 通过接口实现依赖倒置,降低耦合度 3. **单一职责**: 每个组件只负责自己的职责 4. **开闭原则**: 对扩展开放,对修改关闭 5. **测试驱动**: 编写充分的单元测试和集成测试 通过遵循本指南,可以快速、规范地在项目中新增领域功能,保持代码的一致性和质量。