Skip to content

后端分层架构:Controller / Service / Repository / Domain

💡 学习指南:分层架构就像组织一家餐厅——每个人都有明确的职责,前厅接待(Controller)、厨师做菜(Service)、仓管取货(Repository)、菜谱标准(Domain)。本文将带你从零理解后端代码的"层"到底是怎么回事,以及为什么要这样分层。

在开始之前,建议你先有简单的后端开发经验,至少写过几个接口,踩过一些坑。


0. 引言:为什么代码越写越乱?

🌐
客户端
Web / App / 小程序
⬇️ HTTP/HTTPS
🎮Controller入口
职责:接收请求、参数校验、调用 Service
技术:Spring MVC / Gin / Echo
⬇️ 调用
⚙️Service业务核心
职责:业务逻辑编排、事务管理、跨模块协调
技术:纯代码逻辑 / 无框架依赖
⬇️ 调用
🗄️Repository数据访问
职责:数据持久化、查询封装、ORM 映射
技术:MyBatis / GORM / Hibernate
⬇️ SQL
📦Domain / Model领域模型
职责:实体定义、业务规则、值对象
技术:POJO / Struct / Class
⬇️ 持久化
💾
数据库
MySQL / PostgreSQL / MongoDB
💡 点击各层查看详细说明 | 实际调用流向:从上到下,依赖从下到上

很多初学者在刚开始写后端代码时,都会遇到这样的困惑:

  • 刚开始:写一个用户注册接口,100 行代码搞定,感觉挺简单
  • 三个月后:业务越来越复杂,一个文件 500 行,改一行代码怕影响其他地方
  • 半年后:来了新同事,看着代码发愁:"这个接口到底干了多少事?"

问题的本质:代码没有"章法",所有的逻辑都堆在一起,就像把食材、厨具、调料都扔在一个抽屉里。

分层的思想:把抽屉换成橱柜

想象一下厨房的组织方式:

区域存放物品特点
吊柜不常用的锅具、囤货取用最不方便
台面正在处理的食材临时操作区
抽屉分类摆放的餐具按需取用
冰箱生鲜食材有保鲜条件

分层架构就是把代码也这样组织:每一层只关心自己的职责,层与层之间通过明确的"接口"交互,而不是随意互相调用。


1. 核心概念:四层架构的职责划分

1.1 四层架构概览

典型的后端分层架构包含四个核心层次:

┌─────────────────────────────────────┐
│  Controller 层(控制器层)             │  ← 接待员:接收请求,初步检查
│  - 接收 HTTP 请求                     │
│  - 参数校验                          │
│  - 调用 Service                      │
│  - 返回响应                          │
├─────────────────────────────────────┤
│  Service 层(业务逻辑层)              │  ← 厨师:处理核心业务
│  - 业务逻辑编排                       │
│  - 事务管理                          │
│  - 调用 Repository                   │
│  - 跨模块协调                        │
├─────────────────────────────────────┤
│  Repository 层(数据访问层)           │  ← 仓管员:管理数据存取
│  - 数据库操作                        │
│  - ORM 映射                          │
│  - 查询封装                          │
├─────────────────────────────────────┤
│  Domain 层(领域模型层)               │  ← 菜谱标准:定义业务概念
│  - 实体(Entity)                    │
│  - 值对象(Value Object)             │
│  - 业务规则                          │
└─────────────────────────────────────┘

1.2 Controller 层:请求的"接待员"

🎮 Controller 层:请求的"接待员"

点击流程节点查看 Controller 如何接收和处理请求

🌐
客户端发起请求
POST /api/users/register Content-Type: application/json { "username": "张三", "email": "zhangsan@example.com", "password": "123456" }
⬇️ 请求到达
🎮
Controller 接收并解析请求
@RestController @RequestMapping("/api/users") public class UserController { @PostMapping("/register") public ResponseEntity<UserDTO> register( @RequestBody @Valid UserRegisterRequest request ) { // 调用 Service 处理业务 UserDTO user = userService.register(request); return ResponseEntity.ok(user); } }
⬇️ 参数校验 + 调用
参数校验(Controller 的职责之一)
public class UserRegisterRequest { @NotBlank(message = "用户名不能为空") @Size(min = 2, max = 20, message = "用户名长度2-20") private String username; @Email(message = "邮箱格式不正确") private String email; @Size(min = 6, message = "密码至少6位") private String password; }
⬇️ 返回结果
📤
Controller 封装响应返回给客户端
HTTP/1.1 200 OK Content-Type: application/json { "code": 200, "message": "注册成功", "data": { "id": 10001, "username": "张三", "email": "zhangsan@example.com", "createdAt": "2024-01-15T10:30:00Z" } }
🎯 Controller 的核心职责
📡
接收请求
映射 HTTP 请求到方法
参数校验
基础格式和必填校验
🔄
调用 Service
将请求转发给业务层
📦
封装响应
统一响应格式返回

职责

  • 接收 HTTP 请求,解析参数
  • 进行基础的参数校验(格式、必填等)
  • 调用 Service 层执行业务逻辑
  • 封装响应,返回给客户端

不该做的事

  • 不要在这里写业务逻辑
  • 不要直接操作数据库
  • 不要处理事务

类比:就像餐厅的门童,负责迎接客人、检查预约、引导入座,但不负责做菜。

1.3 Service 层:业务逻辑的"厨师"

⚙️ Service 层:业务逻辑的"指挥家"

Service 层编排业务逻辑,协调多个 Repository,管理事务边界

选择业务场景:
🛒 电商下单流程用户下单涉及库存扣减、订单创建、支付记录等多个操作,需要保证事务一致性
1
参数校验与DTO转换
Controller
@PostMapping("/orders")
public ResponseEntity&lt;OrderDTO&gt; createOrder(
    @RequestBody @Valid CreateOrderRequest request
) {
    // 调用 Service
    OrderDTO order = orderService.createOrder(request);
    return ResponseEntity.ok(order);
}
2
业务逻辑编排(事务管理)
Service
@Service
@Transactional  // 关键:事务管理
public class OrderService {

    public OrderDTO createOrder(CreateOrderRequest request) {
        // 1. 检查库存
        inventoryService.checkAndDeduct(request.getSkuId(),
                                      request.getQuantity());

        // 2. 创建订单
        Order order = new Order();
        order.setUserId(request.getUserId());
        order.setTotalAmount(calculateTotal(request));
        orderRepository.save(order);

        // 3. 创建支付记录
        Payment payment = createPayment(order);
        paymentRepository.save(payment);

        // 任一失败都会回滚
        return convertToDTO(order);
    }
}
3
数据持久化
Repository
public interface OrderRepository extends JpaRepository&lt;Order, Long&gt; {
    // 基本的 CRUD 已内置
}

// 实际执行:INSERT INTO orders (...) VALUES (...)
🎯 Service 层设计原则
🎯
单一职责
一个 Service 类只负责一块业务领域
UserService 只管用户,OrderService 只管订单
🔄
事务边界
在 Service 层声明式管理事务
@Transactional 放在 Service 方法上
🔗
避免循环依赖
Service 之间不要互相调用
A 调用 B,B 又调用 A 会导致循环
📦
DTO 转换
返回前转换为 DTO,不暴露实体
return new UserDTO(user)

职责

  • 实现核心业务逻辑
  • 编排多个 Repository 的操作
  • 管理事务边界(@Transactional)
  • 处理跨模块的业务协调

不该做的事

  • 不要直接写 SQL(交给 Repository)
  • 不要处理 HTTP 相关的事情
  • 不要返回数据库实体给 Controller

类比:就像厨师按照菜谱做菜,需要协调各种食材(数据),把控菜品质量(业务正确性)。

1.4 Repository 层:数据的"仓管员"

🗄️ Repository 层:数据的"仓库管理员"

Repository 封装数据访问逻辑,让上层无需关心数据库细节

😊 使用 Repository 封装数据访问清晰解耦
// ========== 1. 实体定义(Domain) ==========
@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "user_id", nullable = false)
    private Long userId;

    @Column(name = "total_amount")
    private BigDecimal totalAmount;

    @Enumerated(EnumType.STRING)
    private OrderStatus status;

    @Column(name = "deleted")
    private Boolean deleted = false;

    // getters and setters...
}

// ========== 2. Repository 接口定义 ==========
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {

    // ✅ Spring Data 自动生成查询 - 简单查询
    List<Order> findByUserIdAndDeletedFalse(Long userId);

    // ✅ 自定义 JPQL 查询 - 复杂统计
    @Query("""
        SELECT new com.example.OrderStatistics(
            o.userId,
            COUNT(o),
            SUM(o.totalAmount),
            MAX(o.createdAt)
        )
        FROM Order o
        WHERE o.createdAt BETWEEN :startDate AND :endDate
          AND o.deleted = false
        GROUP BY o.userId
        HAVING COUNT(o) >= :minOrderCount
        ORDER BY SUM(o.totalAmount) DESC
        """)
    List<OrderStatistics> findUserOrderStatistics(
        @Param("startDate") LocalDateTime startDate,
        @Param("endDate") LocalDateTime endDate,
        @Param("minOrderCount") Long minOrderCount,
        Pageable pageable
    );

    // ✅ 批量更新 - 修改状态
    @Modifying
    @Query("UPDATE Order o SET o.status = :newStatus, " +
           "o.updatedAt = CURRENT_TIMESTAMP WHERE o.id IN :ids")
    int batchUpdateStatus(
        @Param("ids") List<Long> orderIds,
        @Param("newStatus") OrderStatus newStatus
    );
}

// ========== 3. Service 层(纯业务逻辑) ==========
@Service
public class OrderService {

    @Autowired
    private OrderRepository orderRepository;  // ✅ 依赖接口,不依赖具体实现

    @Autowired
    private UserRepository userRepository;

    // ✅ 业务方法清晰简洁,不关心数据怎么存
    public List<OrderDTO> getUserOrders(Long userId) {
        // 可以在这里加业务校验
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new UserNotFoundException(userId));

        // 直接调用 Repository,SQL 藏在后面
        List<Order> orders = orderRepository.findByUserIdAndDeletedFalse(userId);

        // 转换为 DTO 返回
        return orders.stream()
            .map(OrderDTO::from)
            .collect(Collectors.toList());
    }
}
🎉 这样做的好处
  • 关注点分离:Service 专注于业务,Repository 专注于数据,各管一摊
  • 可测试性高:Service 只依赖 Repository 接口,单元测试可以用 Mock 对象替代真实数据库
  • 代码复用:通用的查询方法(如 findByUserId)定义一次,到处复用
  • 切换成本低:从 MySQL 换成 MongoDB,只需改 Repository 实现,Service 完全不动
📊 不同 Repository 实现方式对比
实现方式
优点
缺点
适用场景
Spring Data JPA
主流方案
  • 方法名自动推导查询
  • 分页排序内置支持
  • 事务管理集成
  • 复杂查询性能一般
  • 学习曲线较陡
快速开发、标准 CRUD 业务
MyBatis / MyBatis-Plus
国内主流
  • SQL 完全可控
  • 复杂查询性能优
  • 动态 SQL 强大
  • 需要手写 SQL
  • 样板代码较多
复杂查询、性能敏感业务
Spring Data JDBC
轻量
  • 简单轻量
  • 无延迟加载
  • 启动快速
  • 无复杂映射
  • 功能较简单
微服务、简单聚合根场景

职责

  • 封装所有数据访问逻辑
  • 执行 CRUD 操作
  • 处理 ORM 映射
  • 封装查询条件

不该做的事

  • 不要写业务逻辑
  • 不要处理事务(Service 层管理)
  • 不要依赖上层模块

类比:就像餐厅的仓管员,负责从仓库取食材、存放剩余食材。厨师只需要告诉仓管员要什么,不需要知道仓库在哪、怎么取。

1.5 Domain 层:领域模型的"蓝图"

📦 Domain 层:领域模型设计

Domain 是业务概念的载体,所有层的依赖基础

📄贫血模型 (Anemic)传统做法
Entity(只有 getter/setter)
@Entity
public class Order {
    @Id
    private Long id;
    private Long userId;
    private BigDecimal totalAmount;
    private OrderStatus status;
    private LocalDateTime createdAt;

    // 只有 getter/setter,没有业务逻辑
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    // ... 其他 getter/setter
}
Service(所有业务逻辑都在这里)
@Service
public class OrderService {

    public void cancelOrder(Long orderId) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow();

        // 贫血模型:业务逻辑散落在 Service 里
        if (order.getStatus() == OrderStatus.SHIPPED) {
            throw new IllegalStateException("已发货订单不能取消");
        }
        if (order.getStatus() == OrderStatus.CANCELLED) {
            throw new IllegalStateException("订单已取消");
        }

        // 修改状态
        order.setStatus(OrderStatus.CANCELLED);
        orderRepository.save(order);
    }
}
😫 贫血模型的问题
  • 违背面向对象:对象只有数据没有行为,变成了 "数据结构"
  • 逻辑分散:同样的业务规则可能在多个 Service 重复
  • 难以维护:改一个规则要找所有用到的地方
🧠充血模型 (Rich Domain)推荐做法
Entity(包含业务逻辑)
@Entity
public class Order {
    @Id
    private Long id;
    private Long userId;
    private BigDecimal totalAmount;
    private OrderStatus status;
    private LocalDateTime createdAt;

    // 🎯 业务行为封装在实体里

    /**
     * 取消订单
     */
    public void cancel() {
        // 状态校验内聚在方法里
        if (this.status == OrderStatus.SHIPPED) {
            throw new IllegalStateException("已发货订单不能取消");
        }
        if (this.status == OrderStatus.CANCELLED) {
            throw new IllegalStateException("订单已取消");
        }

        this.status = OrderStatus.CANCELLED;
        // 可以触发领域事件
        registerEvent(new OrderCancelledEvent(this.id));
    }

    /**
     * 支付订单
     */
    public void pay(Payment payment) {
        if (this.status != OrderStatus.PENDING_PAYMENT) {
            throw new IllegalStateException("订单状态不正确");
        }
        if (!payment.getAmount().equals(this.totalAmount)) {
            throw new IllegalArgumentException("支付金额不匹配");
        }

        this.status = OrderStatus.PAID;
        this.paymentTime = LocalDateTime.now();
    }

    // ... 其他业务方法
}
Service(只做协调,不做业务判断)
@Service
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepository;
    private final DomainEventPublisher eventPublisher;

    @Transactional
    public void cancelOrder(Long orderId) {
        // 1. 加载聚合根
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new OrderNotFoundException(orderId));

        // 2. 💡 调用领域对象的业务方法
        // 业务规则封装在 Order 里,Service 只做协调
        order.cancel();

        // 3. 保存变更
        orderRepository.save(order);

        // 4. 发布领域事件
        order.getDomainEvents().forEach(eventPublisher::publish);
        order.clearDomainEvents();
    }
}
😊 充血模型的优势
  • 符合面向对象:数据和行为封装在一起,是真正的 "对象"
  • 业务内聚:规则跟着对象走,改一处处处生效
  • 可复用可测试:领域对象是纯内存对象,单元测试不需要数据库
  • 表达力强:order.cancel() 比 orderService.cancel(order) 更自然

职责

  • 定义业务实体(Entity)
  • 定义值对象(Value Object)
  • 封装业务规则
  • 作为所有层的共同依赖

重要特性

  • Domain 层不依赖任何其他层
  • 所有层都依赖 Domain 层
  • 是分层架构的基础

类比:就像餐厅的菜单和菜品标准,定义了什么是"宫保鸡丁"、用什么食材、什么口味。所有厨师都要按照这个标准来做。


2. DTO:层与层之间的"翻译官"

🔄 DTO 流转:数据在不同层之间的转换

DTO(Data Transfer Object)是层与层之间传递数据的载体

Controller 层
// 接收 Request DTO
public ResponseEntity<UserDTO> createUser(
  @RequestBody @Valid UserCreateRequest request
) { ... }
⬇️ 转换为 Service 需要的参数
Service 层
// 业务处理
public UserDTO createUser(UserCreateParam param) {
  // 转换为 Entity
  User user = param.toEntity();
  userRepository.save(user);
  return UserDTO.from(user);
}
⬇️ 转换为 Repository 需要的 Entity
Repository 层
// 数据持久化
public interface UserRepository
  extends JpaRepository<User, Long> {
}
⬆️ 返回 Entity,转换为 DTO
返回给客户端
// Response DTO
{
  "id": 10001,
  "username": "张三",
  "email": "zhangsan@example.com",
  "createdAt": "2024-01-15T10:30:00Z"
}
📋 不同层的 DTO 职责
层级
DTO 类型
职责
示例
Controller
Request / Response DTO
定义 API 契约、参数校验、序列化
UserCreateRequest
Service
Param / Result DTO
封装业务方法参数,解耦 Controller 与 Service
UserCreateParam
Repository
Entity / DO
映射数据库表结构,ORM 映射
UserEntity

2.1 为什么需要 DTO?

想象一下:如果 Controller 直接把数据库实体(Entity)返回给前端,会发生什么?

java
// ❌ 错误的做法
@Entity
public class User {
    @Id
    private Long id;
    private String username;
    private String password;        // 敏感信息!
    private String phone;
    private String email;
    private LocalDateTime createdAt;
    private Boolean isDeleted;    // 内部字段!
}

// 如果直接返回这个实体...
// 前端会收到 password、isDeleted 等不应该暴露的字段

DTO 的作用

  • 解耦:隔离数据库实体和 API 契约
  • 安全:控制暴露的字段,避免泄露敏感信息
  • 灵活:可以为不同场景定义不同的 DTO
  • 性能:避免加载不必要的数据

2.2 不同层的 DTO 职责

层级DTO 类型职责示例
ControllerRequest / Response DTO定义 API 契约、参数校验、序列化UserCreateRequest
ServiceParam / Result DTO封装业务方法参数,解耦 Controller 与 ServiceUserCreateParam
RepositoryEntity / DO映射数据库表结构,ORM 映射UserEntity

2.3 DTO 转换实战

java
// ========== Controller 层:Request DTO ==========
@Data
public class UserCreateRequest {
    @NotBlank(message = "用户名不能为空")
    @Size(min = 3, max = 20, message = "用户名长度3-20")
    private String username;

    @NotBlank(message = "密码不能为空")
    @Size(min = 6, message = "密码至少6位")
    private String password;

    @Email(message = "邮箱格式不正确")
    private String email;
}

// ========== Controller 层:Response DTO ==========
@Data
@Builder
public class UserDTO {
    private Long id;
    private String username;
    private String email;
    private LocalDateTime createdAt;
    // ❌ 注意:不包含 password 字段!
}

// ========== Service 层:Param DTO ==========
@Data
@Builder
public class UserCreateParam {
    private String username;
    private String password;  // 已加密的密码
    private String email;
}

// ========== 转换逻辑 ==========
@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    @PostMapping
    public ResponseEntity<UserDTO> createUser(
            @RequestBody @Valid UserCreateRequest request) {
        // 1. Request DTO -> Param DTO
        UserCreateParam param = UserCreateParam.builder()
                .username(request.getUsername())
                .password(encryptPassword(request.getPassword()))
                .email(request.getEmail())
                .build();

        // 2. 调用 Service
        User user = userService.createUser(param);

        // 3. Entity -> Response DTO
        UserDTO response = UserDTO.builder()
                .id(user.getId())
                .username(user.getUsername())
                .email(user.getEmail())
                .createdAt(user.getCreatedAt())
                .build();

        return ResponseEntity.ok(response);
    }
}

3. 依赖方向:分层架构的铁律

3.1 依赖倒置原则(DIP)

分层架构的核心规则:上层模块不应该依赖下层模块的具体实现,而应该依赖于抽象。

❌ 错误的依赖方式(直接依赖实现):

Controller -> UserServiceImpl -> UserDaoImpl -> UserEntity

问题:
1. 每层都耦合了具体实现
2. 换实现要改很多代码
3. 测试困难

✅ 正确的依赖方式(依赖抽象):

Controller -> IUserService (接口) -> IUserDao (接口) -> UserEntity

实现:
UserServiceImpl -> UserDaoImpl

好处:
1. 上层只依赖接口
2. 换实现只需改配置
3. 容易 Mock 测试

3.2 正确的依赖方向

┌─────────────────────────────────────────────────────────────┐
│  Controller 层                                               │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  UserController                                       │  │
│  │  - @Autowired private IUserService userService;      │  │
│  │  ✅ 依赖接口,不依赖实现                              │  │
│  └──────────────────────────────────────────────────────┘  │
│                            │                                 │
│                            ▼ 依赖(Dependency)               │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  Service 层                                           │  │
│  │  ┌────────────────────────────────────────────────┐  │  │
│  │  │  UserServiceImpl                                │  │  │
│  │  │  - @Autowired private UserRepository repository;  │  │  │
│  │  │  ✅ 依赖 Repository 接口                         │  │  │
│  │  └────────────────────────────────────────────────┘  │  │
│  │                      │                              │  │
│  │                      ▼ 依赖                          │  │
│  │  ┌────────────────────────────────────────────────┐  │  │
│  │  │  Repository 层                                  │  │  │
│  │  │  ┌──────────────────────────────────────────┐   │  │  │
│  │  │  │  UserRepository                          │   │  │  │
│  │  │  │  - extends JpaRepository<User, Long>    │   │  │  │
│  │  │  └──────────────────────────────────────────┘   │  │  │
│  │  │                      │                         │  │  │
│  │  │                      ▼ 依赖                     │  │  │
│  │  │  ┌──────────────────────────────────────────┐   │  │  │
│  │  │  │  Domain 层 (核心领域)                   │   │  │  │
│  │  │  │  ┌────────────────────────────────────┐  │   │  │  │
│  │  │  │  │  User (Entity)                     │  │   │  │  │
│  │  │  │  │  - 不包含任何层依赖              │  │   │  │  │
│  │  │  │  │  - 被所有层依赖                  │  │   │  │  │
│  │  │  │  └────────────────────────────────────┘  │   │  │  │
│  │  │  └──────────────────────────────────────────┘   │  │  │
│  │  └────────────────────────────────────────────────┘  │  │
│  └──────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

4. 实战案例:电商订单系统的分层实现

4.1 需求场景

实现一个电商订单创建功能:

  • 用户选择商品,确认订单信息
  • 系统检查库存
  • 计算订单金额(商品价格 + 运费 - 优惠)
  • 创建订单记录
  • 扣减库存
  • 返回订单信息

4.2 完整的分层代码

java
// =====================================================
// 1. Domain 层:领域模型
// =====================================================

// 订单实体
@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "user_id")
    private Long userId;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "order_id")
    private List<OrderItem> items = new ArrayList<>();

    @Embedded
    private Money totalAmount;

    @Embedded
    private Address shippingAddress;

    @Enumerated(EnumType.STRING)
    private OrderStatus status = OrderStatus.PENDING_PAYMENT;

    @Column(name = "created_at")
    private LocalDateTime createdAt = LocalDateTime.now();

    // 业务方法:计算订单总金额
    public void calculateTotal() {
        Money total = Money.zero();
        for (OrderItem item : items) {
            total = total.add(item.getSubTotal());
        }
        this.totalAmount = total;
    }

    // 业务方法:添加订单项
    public void addItem(OrderItem item) {
        items.add(item);
        item.setOrder(this);
    }

    // 业务方法:取消订单
    public void cancel() {
        if (this.status != OrderStatus.PENDING_PAYMENT) {
            throw new IllegalStateException("只有待支付订单可以取消");
        }
        this.status = OrderStatus.CANCELLED;
    }

    // Getters...
}

// 订单项实体
@Entity
@Table(name = "order_items")
public class OrderItem {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "product_id")
    private Long productId;

    @Column(name = "product_name")
    private String productName;

    @Embedded
    private Money unitPrice;

    @Column(name = "quantity")
    private Integer quantity;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "order_id")
    private Order order;

    // 计算小计
    public Money getSubTotal() {
        return unitPrice.multiply(quantity);
    }

    // Getters and Setters...
}

// 值对象:金钱
@Embeddable
public class Money {
    @Column(name = "amount")
    private BigDecimal amount;

    @Column(name = "currency")
    private String currency;

    public static Money zero() {
        return new Money(BigDecimal.ZERO, "CNY");
    }

    public Money add(Money other) {
        return new Money(this.amount.add(other.amount), this.currency);
    }

    public Money multiply(int factor) {
        return new Money(this.amount.multiply(BigDecimal.valueOf(factor)), this.currency);
    }

    // Constructor, Getters...
}

// 枚举:订单状态
public enum OrderStatus {
    PENDING_PAYMENT,   // 待支付
    PAID,              // 已支付
    SHIPPED,           // 已发货
    COMPLETED,         // 已完成
    CANCELLED          // 已取消
}

// =====================================================
// 2. Repository 层:数据访问
// =====================================================

@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {

    // 根据用户查询订单
    List<Order> findByUserIdOrderByCreatedAtDesc(Long userId);

    // 根据状态查询订单
    List<Order> findByStatus(OrderStatus status);

    // 复杂的 JPQL 查询
    @Query("""
        SELECT o FROM Order o
        WHERE o.userId = :userId
          AND o.status IN :statuses
          AND o.createdAt BETWEEN :startDate AND :endDate
        ORDER BY o.createdAt DESC
        """)
    List<Order> findUserOrdersWithConditions(
        @Param("userId") Long userId,
        @Param("statuses") List<OrderStatus> statuses,
        @Param("startDate") LocalDateTime startDate,
        @Param("endDate") LocalDateTime endDate
    );
}

// =====================================================
// 3. Service 层:业务逻辑
// =====================================================

@Service
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepository;
    private final ProductService productService;
    private final InventoryService inventoryService;
    private final IdGenerator idGenerator;

    /**
     * 创建订单
     */
    @Transactional
    public OrderDTO createOrder(OrderCreateParam param) {
        // 1. 验证商品信息并扣减库存
        List<OrderItem> items = new ArrayList<>();
        for (OrderItemParam itemParam : param.getItems()) {
            // 查询商品
            Product product = productService.getProduct(itemParam.getProductId());

            // 检查库存
            boolean reserved = inventoryService.reserveStock(
                itemParam.getProductId(),
                itemParam.getQuantity()
            );
            if (!reserved) {
                throw new InsufficientStockException("商品库存不足: " + product.getName());
            }

            // 创建订单项
            OrderItem item = new OrderItem();
            item.setProductId(product.getId());
            item.setProductName(product.getName());
            item.setUnitPrice(product.getPrice());
            item.setQuantity(itemParam.getQuantity());
            items.add(item);
        }

        // 2. 创建订单
        Order order = new Order();
        order.setUserId(param.getUserId());
        order.setShippingAddress(param.getAddress());

        // 添加订单项
        for (OrderItem item : items) {
            order.addItem(item);
        }

        // 计算总价
        order.calculateTotal();

        // 3. 保存订单
        orderRepository.save(order);

        // 4. 转换为 DTO 返回
        return OrderDTO.from(order);
    }

    /**
     * 取消订单
     */
    @Transactional
    public void cancelOrder(Long orderId, Long userId) {
        // 1. 查询订单
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new OrderNotFoundException(orderId));

        // 2. 验证权限
        if (!order.getUserId().equals(userId)) {
            throw new AccessDeniedException("无权操作此订单");
        }

        // 3. 执行业务逻辑(在领域对象中封装)
        order.cancel();  // 这会检查状态是否允许取消

        // 4. 恢复库存
        for (OrderItem item : order.getItems()) {
            inventoryService.releaseStock(item.getProductId(), item.getQuantity());
        }

        // 5. 保存
        orderRepository.save(order);
    }
}

// =====================================================
// 4. Controller 层:API 入口
// =====================================================

@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
public class OrderController {

    private final OrderService orderService;

    /**
     * 创建订单
     */
    @PostMapping
    public ResponseEntity<OrderDTO> createOrder(
            @RequestBody @Valid OrderCreateRequest request,
            @AuthenticationPrincipal UserPrincipal user) {

        // 1. Request -> Param 转换
        OrderCreateParam param = OrderCreateParam.builder()
                .userId(user.getId())
                .address(request.getAddress())
                .items(request.getItems().stream()
                        .map(item -> OrderItemParam.builder()
                                .productId(item.getProductId())
                                .quantity(item.getQuantity())
                                .build())
                        .collect(Collectors.toList()))
                .build();

        // 2. 调用 Service
        OrderDTO order = orderService.createOrder(param);

        // 3. 返回
        return ResponseEntity.status(HttpStatus.CREATED).body(order);
    }

    /**
     * 取消订单
     */
    @PostMapping("/{orderId}/cancel")
    public ResponseEntity<Void> cancelOrder(
            @PathVariable Long orderId,
            @AuthenticationPrincipal UserPrincipal user) {

        orderService.cancelOrder(orderId, user.getId());

        return ResponseEntity.noContent().build();
    }
}

5. 分层架构的演进:从混乱到整洁

5.1 初学者常犯的错误

错误一:Controller 里写业务逻辑

java
// ❌ 错误:Controller 里写了太多业务逻辑
@RestController
public class OrderController {

    @Autowired private OrderRepository orderRepository;
    @Autowired private ProductRepository productRepository;
    @Autowired private InventoryRepository inventoryRepository;

    @PostMapping("/orders")
    public Order createOrder(@RequestBody CreateOrderRequest request) {
        // 太多的业务逻辑在这里...
        // 检查库存
        for (ItemRequest item : request.getItems()) {
            Product product = productRepository.findById(item.getProductId())
                .orElseThrow(() -> new RuntimeException("商品不存在"));

            if (product.getStock() < item.getQuantity()) {
                throw new RuntimeException("库存不足");
            }
        }

        // 扣减库存
        for (ItemRequest item : request.getItems()) {
            Product product = productRepository.findById(item.getProductId()).get();
            product.setStock(product.getStock() - item.getQuantity());
            productRepository.save(product);
        }

        // 创建订单...
        Order order = new Order();
        // ... 更多逻辑

        return orderRepository.save(order);
    }
}

错误二:Service 层直接操作数据库

java
// ❌ 错误:Service 里直接写 SQL
@Service
public class OrderService {

    @Autowired
    private JdbcTemplate jdbcTemplate;  // 直接依赖底层 JDBC

    public List<Order> getUserOrders(Long userId) {
        // SQL 硬编码在 Service 里
        String sql = "SELECT * FROM orders WHERE user_id = ? AND deleted = 0";

        return jdbcTemplate.query(sql, (rs, rowNum) -> {
            Order order = new Order();
            order.setId(rs.getLong("id"));
            // ... 更多字段映射
            return order;
        }, userId);
    }
}

错误三:循环依赖

java
// ❌ 错误:Service 之间相互调用,形成循环依赖
@Service
public class OrderService {
    @Autowired
    private PaymentService paymentService;  // A 依赖 B
}

@Service
public class PaymentService {
    @Autowired
    private OrderService orderService;  // B 又依赖 A - 循环!
}

5.2 如何重构?

重构一:提取 Service 层

java
// ✅ Controller 只负责接收请求和返回响应
@RestController
public class OrderController {

    @Autowired
    private OrderService orderService;  // 只依赖 Service 接口

    @PostMapping("/orders")
    public OrderDTO createOrder(@RequestBody @Valid CreateOrderRequest request) {
        // 1. Request -> Param
        CreateOrderParam param = CreateOrderParam.builder()
            .userId(getCurrentUserId())
            .items(request.getItems())
            .address(request.getAddress())
            .build();

        // 2. 调用 Service
        Order order = orderService.createOrder(param);

        // 3. Entity -> DTO
        return OrderDTO.from(order);
    }
}

// ✅ Service 封装业务逻辑
@Service
@Transactional
public class OrderService {

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private ProductService productService;

    @Autowired
    private InventoryService inventoryService;

    public Order createOrder(CreateOrderParam param) {
        // 1. 检查库存并扣减
        for (ItemParam item : param.getItems()) {
            boolean reserved = inventoryService.reserveStock(
                item.getProductId(),
                item.getQuantity()
            );
            if (!reserved) {
                throw new InsufficientStockException("库存不足");
            }
        }

        // 2. 创建订单
        Order order = new Order();
        order.setUserId(param.getUserId());
        // ... 设置其他属性

        // 3. 保存
        return orderRepository.save(order);
    }
}

重构二:提取 Repository 层

java
// ✅ Repository 接口:定义数据访问契约
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
    // Spring Data JPA 自动生成实现
    List<Order> findByUserIdOrderByCreatedAtDesc(Long userId);

    @Query("SELECT o FROM Order o WHERE o.status = :status AND o.createdAt < :date")
    List<Order> findByStatusAndCreatedAtBefore(
        @Param("status") OrderStatus status,
        @Param("date") LocalDateTime date
    );
}

// ✅ Service 只依赖 Repository 接口
@Service
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;  // 依赖接口,不依赖实现

    public Order getOrder(Long id) {
        return orderRepository.findById(id)
            .orElseThrow(() -> new OrderNotFoundException(id));
    }
}

重构三:打破循环依赖

java
// ✅ 方案一:抽取共同的依赖到 Domain 层

// 在 Domain 层定义领域事件
public class OrderPaidEvent {
    private final Long orderId;
    private final Long userId;
    private final Money amount;
    private final LocalDateTime paidAt;
    // ...
}

// OrderService 发布事件
@Service
public class OrderService {
    @Autowired
    private ApplicationEventPublisher eventPublisher;

    public void payOrder(Long orderId, PaymentParam param) {
        Order order = orderRepository.findById(orderId).orElseThrow();
        order.pay(param.getPaymentMethod());
        orderRepository.save(order);

        // 发布事件,而不是直接调用 PaymentService
        eventPublisher.publishEvent(new OrderPaidEvent(
            order.getId(),
            order.getUserId(),
            order.getTotalAmount(),
            LocalDateTime.now()
        ));
    }
}

// PaymentService 监听事件
@Service
public class PaymentService {
    @EventListener
    @Transactional
    public void handleOrderPaid(OrderPaidEvent event) {
        // 处理支付相关逻辑
        createPaymentRecord(event);
        // ...
    }
}

// ✅ 方案二:使用中间抽象

// 定义接口
public interface PaymentGateway {
    PaymentResult processPayment(PaymentRequest request);
}

// OrderService 依赖接口
@Service
public class OrderService {
    @Autowired
    private PaymentGateway paymentGateway;  // 依赖接口
}

// 实现类
@Service
public class AlipayGateway implements PaymentGateway {
    // 实现...
}

5. 分层架构 vs 整洁架构

5.1 两种架构的对比

特性传统分层架构整洁架构
依赖方向从上到下从外到内
核心业务位置Service 层Domain 层(中心)
框架依赖较深(如 Spring)较浅(通过接口隔离)
可测试性需要集成测试核心可单元测试
学习曲线平缓较陡
适用场景中小型项目、快速迭代大型复杂业务、长期维护

5.2 如何选择?

选择传统分层架构当...

  • 项目规模较小,业务相对简单
  • 团队对 DDD 不熟悉
  • 需要快速上线,验证市场
  • 技术栈相对固定

选择整洁架构当...

  • 业务复杂,领域模型丰富
  • 需要长期维护和演进
  • 需要频繁切换技术栈
  • 团队有较强的设计能力

6. 总结:分层架构的核心要点

6.1 四层职责速查表

层级主要职责不该做的事
Controller接收请求、参数校验、调用 Service、返回响应写业务逻辑、操作数据库、处理事务
Service业务逻辑编排、事务管理、协调 Repository直接写 SQL、处理 HTTP 细节、返回实体给 Controller
Repository数据访问、ORM 映射、查询封装写业务逻辑、管理事务、依赖上层
Domain实体定义、业务规则、值对象依赖其他层、处理持久化、处理 HTTP

6.2 依赖方向铁律

✅ 正确的依赖方向:

Controller → Service 接口 → Repository 接口 → Domain
    ↑           ↑                ↑              ↑
    └-----------└----------------└--------------┘
    所有层都依赖 Domain,Domain 不依赖任何层

❌ 禁止的做法:
- Service 直接依赖 Repository 实现
- Controller 直接操作数据库
- Domain 依赖 Service 或 Repository
- 层与层之间形成循环依赖

6.3 编码最佳实践

  1. 接口优先:Service 和 Repository 都定义接口,实现类通过 Spring 注入
  2. DTO 隔离:每层使用自己的 DTO,不要直接传递 Entity
  3. 事务在 Service:使用 @Transactional 在 Service 方法上控制事务
  4. 异常处理:Controller 统一处理异常,不要 try-catch 后吞掉异常
  5. 贫血模型 vs 充血模型:根据团队熟悉程度选择,但建议 Domain 有基本的行为方法

6.4 常见面试问题

Q1: 为什么要分层?不分层可以吗?

A: 分层的目的是解耦和关注点分离。小项目可以不分层,但随着业务复杂度的增加,不分层会导致代码难以维护、测试困难、团队协作效率低下。

Q2: Controller 层可以写业务逻辑吗?

A: 不可以。Controller 应该只负责接收请求、调用 Service、返回响应。业务逻辑应该封装在 Service 层,这样代码可以被复用,也更容易测试。

Q3: 什么是贫血模型和充血模型?

A: 贫血模型是指 Entity 只有 getter/setter,业务逻辑都在 Service 层。充血模型是指 Entity 包含业务方法(如 order.cancel()),封装了业务规则。DDD 推荐充血模型,但贫血模型更简单易懂。

Q4: 如何处理跨多个 Service 的事务?

A: 可以在上层 Service 中使用 @Transactional,调用多个下层 Service。或者使用分布式事务方案(如 Seata),但会增加系统复杂度。


7. 名词对照表

英文术语中文对照解释
Layered Architecture分层架构将系统划分为多个层次,每层有明确的职责
Controller控制器接收 HTTP 请求,调用 Service,返回响应
Service服务封装业务逻辑,协调多个 Repository
Repository仓储封装数据访问逻辑,执行 CRUD 操作
Domain领域定义业务实体、值对象和业务规则
DTO数据传输对象层与层之间传递数据的载体
Entity实体有唯一标识的领域对象,对应数据库表
Value Object值对象没有唯一标识,通过属性值判断相等的对象
Dependency Inversion依赖倒置高层模块不应依赖低层模块,都应依赖抽象
Transaction事务保证一组操作原子性的机制
Clean Architecture整洁架构以领域为核心的架构风格,强调依赖方向
Anemic Domain Model贫血模型实体只有数据没有行为的模型
Rich Domain Model充血模型实体包含数据和业务行为的模型

本文档示例代码基于 Java + Spring Boot,但分层架构的思想适用于任何后端技术栈(Node.js、Python、Go 等)。