Skip to content

分层架构设计

概述

分层架构是一种将应用程序按职责划分为多个水平层次的设计模式。本规范定义了基于 Go、Echo 和 GORM 的三层架构模式,适用于中小型 Web 应用的后端开发。

架构分层

分层架构图

分层职责

层级组件职责
表示层Handler接收 HTTP 请求、参数校验、调用 Service、返回响应
业务层Service业务逻辑实现、事务管理、调用 GORM 操作数据
数据层GORMORM 映射、数据库操作封装
基础设施层Config/Logger/Tracing配置管理、日志、可观测性等横切关注点

核心设计原则

原则 1:单一职责

每一层只负责其职责范围内的工作,不跨越层级边界。

  • ✅ Handler 只处理 HTTP 请求和响应,不包含业务逻辑
  • ✅ Service 只实现业务逻辑,不直接处理 HTTP 请求细节
  • ✅ GORM 只负责数据访问,不包含业务规则

原则 2:单向依赖

上层依赖下层,下层不依赖上层

Handler → Service → GORM → Database
(不允许反向依赖)
  • ✅ Handler 可以调用 Service
  • ✅ Service 可以调用 GORM
  • ❌ Service 禁止调用 Handler
  • ❌ GORM 禁止调用 Service

原则 3:接口隔离

Handler 通过明确定义的接口与 Service 交互

  • Handler 使用 Service 提供的公共方法
  • Service 返回业务数据或错误,不返回 HTTP 状态码
  • 跨 Service 调用通过依赖注入实现

详细规范

Handler 层规范

职责

Handler 层负责以下工作:

  1. 接收和解析 HTTP 请求(参数绑定)
  2. 校验请求参数格式
  3. 调用 Service 层方法
  4. 封装 Service 返回的数据为 HTTP 响应
  5. 处理 HTTP 状态码和错误响应

禁止事项

Handler 层禁止执行以下操作:

  • 禁止直接调用 GORM 操作数据库
  • 禁止实现业务逻辑(如计算、数据转换、业务规则判断)
  • 禁止直接操作 *gorm.DB 实例

代码示例

正确示例

go
// Handler 只负责请求处理和响应封装
func (h *UserHandler) Create(c echo.Context) error {
    // 1. 参数绑定
    req := new(CreateUserRequest)
    if err := c.Bind(req); err != nil {
        return c.JSON(http.StatusBadRequest, response.Fail(1000, "参数格式错误"))
    }

    // 2. 参数校验
    if err := c.Validate(req); err != nil {
        return c.JSON(http.StatusBadRequest, response.Fail(1001, err.Error()))
    }

    // 3. 调用 Service(不包含业务逻辑)
    user, err := h.userService.Create(c.Request().Context(), req.Name, req.Email, req.Password)
    if err != nil {
        return c.JSON(http.StatusInternalServerError, response.Fail(5000, "创建用户失败"))
    }

    // 4. 返回响应
    return c.JSON(http.StatusCreated, response.Success(user))
}

错误示例

go
// 错误:Handler 直接操作数据库
func (h *UserHandler) Create(c echo.Context) error {
    req := new(CreateUserRequest)
    c.Bind(req)

    // ❌ 错误:Handler 直接使用 GORM
    user := &models.User{
        Name:  req.Name,
        Email: req.Email,
    }
    if err := h.db.Create(user).Error; err != nil {
        return c.JSON(500, map[string]string{"error": err.Error()})
    }

    return c.JSON(200, user)
}
go
// 错误:Handler 包含业务逻辑
func (h *ProductHandler) UpdatePrice(c echo.Context) error {
    req := new(UpdatePriceRequest)
    c.Bind(req)

    // ❌ 错误:价格计算属于业务逻辑,应在 Service 层
    discount := 0.1
    if req.IsVIP {
        discount = 0.2
    }
    finalPrice := req.Price * (1 - discount)

    // ...
}

Service 层规范

职责

Service 层负责以下工作:

  1. 实现核心业务逻辑
  2. 管理数据库事务
  3. 协调多个数据模型的操作
  4. 调用 GORM 进行数据访问
  5. 返回业务数据或业务错误

禁止事项

Service 层禁止执行以下操作:

  • 禁止直接访问 HTTP 请求对象(如 echo.Context
  • 禁止返回 HTTP 状态码
  • 禁止处理 HTTP 响应格式

代码示例

正确示例

go
type UserService struct {
    db     *gorm.DB
    config *config.Config
}

// Service 方法只接受业务参数,返回业务数据或错误
func (s *UserService) Create(ctx context.Context, name, email, password string) (*models.User, error) {
    // 1. 业务参数校验
    if name == "" || email == "" || password == "" {
        return nil, errors.New("参数不完整")
    }

    // 2. 业务逻辑处理
    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    if err != nil {
        return nil, fmt.Errorf("密码加密失败: %w", err)
    }

    // 3. 构造数据模型
    user := &models.User{
        Name:     name,
        Email:    email,
        Password: string(hashedPassword),
        IsActive: true,
    }

    // 4. 数据库操作
    if err := s.db.WithContext(ctx).Create(user).Error; err != nil {
        return nil, fmt.Errorf("创建用户失败: %w", err)
    }

    return user, nil
}

事务管理示例

go
// Service 层管理事务
func (s *OrderService) CreateOrder(ctx context.Context, userID uint, items []OrderItem) (*models.Order, error) {
    var order *models.Order

    // 使用事务确保数据一致性
    err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
        // 1. 创建订单
        order = &models.Order{
            UserID: userID,
            Status: "pending",
        }
        if err := tx.Create(order).Error; err != nil {
            return err
        }

        // 2. 创建订单项
        for _, item := range items {
            orderItem := &models.OrderItem{
                OrderID:   order.ID,
                ProductID: item.ProductID,
                Quantity:  item.Quantity,
            }
            if err := tx.Create(orderItem).Error; err != nil {
                return err
            }
        }

        // 3. 更新库存(业务逻辑)
        for _, item := range items {
            if err := tx.Model(&models.Product{}).
                Where("id = ?", item.ProductID).
                Update("stock", gorm.Expr("stock - ?", item.Quantity)).
                Error; err != nil {
                return err
            }
        }

        return nil
    })

    if err != nil {
        return nil, err
    }

    return order, nil
}

错误示例

go
// 错误:Service 访问 HTTP 请求对象
func (s *UserService) Create(c echo.Context) error {  // ❌ 不应接受 echo.Context
    // ...
    return c.JSON(200, user)  // ❌ Service 不应处理 HTTP 响应
}

数据层(GORM)规范

职责

GORM 层负责以下工作:

  1. 执行 SQL 查询和数据操作
  2. 提供 ORM 映射功能
  3. 处理数据库连接和查询优化

使用方式

Service 层直接使用 GORM 进行数据操作:

go
// Service 直接调用 GORM 方法
func (s *UserService) GetByID(ctx context.Context, id uint) (*models.User, error) {
    var user models.User
    if err := s.db.WithContext(ctx).First(&user, id).Error; err != nil {
        if errors.Is(err, gorm.ErrRecordNotFound) {
            return nil, errors.New("用户不存在")
        }
        return nil, err
    }
    return &user, nil
}

注意事项

  • 必须使用 WithContext(ctx) 传递上下文
  • 应该处理 gorm.ErrRecordNotFound 等特定错误
  • 避免在 Handler 层直接使用 *gorm.DB

调用规范

调用链路

标准调用链路

HTTP请求 → Handler → Service → GORM → Database

调用约束

  1. Handler → Service

    • Handler 必须调用 Service 方法
    • Handler 禁止跳过 Service 直接调用 GORM
  2. Service → GORM

    • Service 直接使用 GORM 操作数据库
    • Service 禁止调用 Handler
  3. 跨 Service 调用

    • 通过依赖注入实现
    • 在 Service 构造函数中注入其他 Service
    • 详见 依赖注入规范

跨 Service 调用示例

go
type OrderService struct {
    db             *gorm.DB
    productService *ProductService  // 注入其他 Service
    userService    *UserService
}

func NewOrderService(db *gorm.DB, productService *ProductService, userService *UserService) *OrderService {
    return &OrderService{
        db:             db,
        productService: productService,
        userService:    userService,
    }
}

func (s *OrderService) Create(ctx context.Context, userID uint, productID uint) (*models.Order, error) {
    // 1. 调用 UserService 验证用户
    user, err := s.userService.GetByID(ctx, userID)
    if err != nil {
        return nil, fmt.Errorf("用户验证失败: %w", err)
    }

    // 2. 调用 ProductService 检查库存
    product, err := s.productService.GetByID(ctx, productID)
    if err != nil {
        return nil, fmt.Errorf("商品查询失败: %w", err)
    }
    if product.Stock <= 0 {
        return nil, errors.New("商品库存不足")
    }

    // 3. 创建订单(本 Service 的核心逻辑)
    order := &models.Order{
        UserID:    user.ID,
        ProductID: product.ID,
        Amount:    product.Price,
    }
    if err := s.db.WithContext(ctx).Create(order).Error; err != nil {
        return nil, err
    }

    return order, nil
}

数据流转

请求流转

1. HTTP 请求到达 Handler

2. Handler 绑定和校验参数

3. Handler 调用 Service 方法

4. Service 执行业务逻辑

5. Service 调用 GORM 操作数据库

6. GORM 执行 SQL 并返回结果

7. Service 处理结果并返回给 Handler

8. Handler 封装响应并返回 HTTP 响应

数据转换

遵循 DTO 模式进行数据转换:

  1. Handler 绑定结构体 → Handler 接收 HTTP 请求数据
  2. Service 请求结构体 → Service 方法参数
  3. 数据库模型 → GORM 操作的数据结构
  4. 响应结构体 → Handler 返回给客户端

详见 DTO 分离模式规范

目录组织

推荐的目录结构:

backend/
├── main.go                  # 应用入口
├── httpapi/                 # Handler 层
│   ├── handlers/            # 各业务 Handler
│   │   ├── user.go
│   │   ├── product.go
│   │   └── order.go
│   ├── middleware/          # 中间件
│   ├── response/            # 统一响应体
│   └── router.go            # 路由注册
├── services/                # Service 层
│   ├── user.go
│   ├── product.go
│   └── order.go
├── models/                  # 数据模型(GORM)
│   ├── user.go
│   ├── product.go
│   └── order.go
└── app/                     # 依赖注入容器
    └── app.go

优势与权衡

优势

  • 职责清晰:每层职责明确,易于理解和维护
  • 易于测试:Service 层可独立测试,不依赖 HTTP 框架
  • 可复用性:Service 层可被多个 Handler 或其他入口复用
  • 易于扩展:新增功能只需在对应层添加代码

适用场景

适用于

  • ✅ 中小型 Web 应用
  • ✅ CRUD 为主的业务系统
  • ✅ 团队熟悉分层架构模式

不适用于

  • ❌ 超大型复杂系统(建议使用 DDD)
  • ❌ 微服务架构(每个服务可独立设计)

相关规范

检查清单

实施分层架构时,确保以下要点:

  • [ ] Handler 只调用 Service,不直接操作 GORM
  • [ ] Service 包含所有业务逻辑,Handler 不包含业务逻辑
  • [ ] Service 方法不接受 echo.Context 参数
  • [ ] Service 方法返回业务数据或错误,不返回 HTTP 状态码
  • [ ] 跨 Service 调用通过依赖注入实现
  • [ ] 事务管理 Service 层实现
  • [ ] Handler 使用统一响应体格式