1232 lines
37 KiB
Markdown
1232 lines
37 KiB
Markdown
# 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. **测试驱动**: 编写充分的单元测试和集成测试
|
||
|
||
通过遵循本指南,可以快速、规范地在项目中新增领域功能,保持代码的一致性和质量。 |