Skip to content

第78课:企业模式

🎯 学习目标

  • 理解企业模式关注的是分层、边界、数据流和职责划分。
  • 掌握 Controller、Service、Repository、DTO、DAO、VO、Assembler 等常见角色。
  • 能设计清晰的请求模型、响应模型、领域对象和持久化对象。
  • 能识别贫血模型、上帝 Service、Entity 泄露、事务边界混乱等问题。
  • 能把前面学过的 Spring、Validation、JSON、Swagger、设计模式串成项目结构。

📖 一、企业项目为什么要分层

小程序可以把所有逻辑写在一个类里:

java
@RestController
public class UserController {
    @PostMapping("/users")
    public User create(@RequestBody User user) {
        // 校验参数
        // 查数据库
        // 加密密码
        // 保存用户
        // 返回结果
    }
}

项目变大后会出现:

text
Controller 过胖
数据库结构暴露给前端
业务逻辑难测试
事务边界混乱
重复转换代码到处都是
接口字段一改牵动数据库

分层的目标是让每一层只关心自己的职责。


📖 二、典型分层结构

text
interfaces/controller   接口入口:HTTP 参数、响应、状态码
application/service     应用服务:用例编排、事务边界
domain                  领域模型:核心业务规则
infrastructure          基础设施:数据库、Redis、消息、外部接口

传统 Spring Boot 项目也常见:

text
controller
service
repository/mapper
entity
dto
config
exception

两种不是互斥的。关键是职责清楚,而不是包名高级。


📖 三、Controller 模式

Controller 负责 HTTP 层:

text
接收参数
触发 Validation
调用应用服务
返回响应
不写复杂业务逻辑

示例:

java
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
    private final UserApplicationService userApplicationService;

    @PostMapping
    public UserResponse create(@Valid @RequestBody UserCreateRequest request) {
        return userApplicationService.create(request);
    }
}

Controller 不应该:

text
直接写 SQL
直接操作 Redis
编排复杂业务流程
处理大量 if-else 业务规则
返回数据库 Entity

📖 四、DTO / Request / Response

DTO 是跨层或跨进程传输数据的对象。

建议区分:

text
UserCreateRequest:创建用户请求
UserUpdateRequest:更新用户请求
UserQueryRequest :查询用户请求
UserResponse     :返回给前端的用户数据

示例:

java
public class UserCreateRequest {
    @NotBlank
    private String username;

    @NotBlank
    private String password;

    @Email
    private String email;
}

响应:

java
public class UserResponse {
    private Long id;
    private String username;
    private String email;
    private LocalDateTime createdAt;
}

不要用一个 UserDTO 承担所有场景。创建、更新、查询、响应的字段和校验规则通常不同。


📖 五、Entity 与领域对象

Entity 通常对应数据库表:

java
@Entity
@Table(name = "users")
public class UserEntity {
    @Id
    private Long id;

    private String username;
    private String passwordHash;
    private String email;
}

它不应该直接暴露给前端:

java
@GetMapping("/{id}")
public UserEntity getUser(@PathVariable Long id) {
    return userRepository.findById(id).orElseThrow();
}

问题:

text
密码哈希可能泄露
数据库字段变化影响接口
JPA 懒加载可能序列化失败
双向关联可能递归
接口文档混乱

推荐转换为 Response:

java
public UserResponse toResponse(UserEntity entity) {
    UserResponse response = new UserResponse();
    response.setId(entity.getId());
    response.setUsername(entity.getUsername());
    response.setEmail(entity.getEmail());
    response.setCreatedAt(entity.getCreatedAt());
    return response;
}

📖 六、Service 模式

Service 负责业务用例和事务边界。

java
@Service
@RequiredArgsConstructor
public class UserApplicationService {
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final UserAssembler userAssembler;

    @Transactional
    public UserResponse create(UserCreateRequest request) {
        if (userRepository.existsByUsername(request.getUsername())) {
            throw new BusinessException("用户名已存在");
        }

        UserEntity entity = new UserEntity();
        entity.setUsername(request.getUsername());
        entity.setPasswordHash(passwordEncoder.encode(request.getPassword()));
        entity.setEmail(request.getEmail());

        UserEntity saved = userRepository.save(entity);
        return userAssembler.toResponse(saved);
    }
}

Service 应该:

text
表达一个用例
控制事务
编排领域对象和基础设施
处理业务异常

Service 不应该:

text
包含大量格式转换细节
直接处理 HTTP 请求响应对象
无限膨胀成几千行上帝类

📖 七、Repository / DAO 模式

Repository 或 DAO 负责数据访问。

MyBatis 示例:

java
@Mapper
public interface UserMapper {
    UserEntity selectById(Long id);
    int insert(UserEntity user);
    boolean existsByUsername(String username);
}

Spring Data JPA 示例:

java
public interface UserRepository extends JpaRepository<UserEntity, Long> {
    boolean existsByUsername(String username);
}

Repository 不应该包含复杂业务流程。它的职责是查询和持久化。


📖 八、Assembler / Converter

对象转换代码如果散落在 Controller 和 Service,会很快失控。

java
@Component
public class UserAssembler {
    public UserResponse toResponse(UserEntity entity) {
        UserResponse response = new UserResponse();
        response.setId(entity.getId());
        response.setUsername(entity.getUsername());
        response.setEmail(entity.getEmail());
        response.setCreatedAt(entity.getCreatedAt());
        return response;
    }
}

复杂项目可以使用 MapStruct,但简单项目手写转换更直观。


📖 九、统一响应和异常

统一响应:

java
public class ApiResponse<T> {
    private String code;
    private String message;
    private T data;
}

统一异常:

java
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ApiResponse<Void>> handleBusiness(BusinessException e) {
        return ResponseEntity.badRequest()
            .body(ApiResponse.fail(e.getCode(), e.getMessage()));
    }
}

这样接口层能保持一致,Swagger 文档也更清晰。


📖 十、事务边界

事务通常放在应用服务层:

java
@Transactional
public OrderResponse createOrder(OrderCreateRequest request) {
    // 校验
    // 扣库存
    // 创建订单
    // 写订单明细
}

避免:

text
Controller 上开事务
Repository 上开大事务
事务内做远程 HTTP 调用
事务方法内部自调用导致注解失效

事务是企业模式里最容易被忽略的边界问题。


⚠️ 十一、常见陷阱

1. Controller 过胖

Controller 如果超过几百行,通常说明业务逻辑没有下沉。

2. Entity 泄露

数据库结构和 API 契约耦合,会让系统越来越难改。

3. 上帝 Service

一个 OrderService 什么都做,最终会变成无法测试和维护的大类。

4. DTO 复用过度

创建、更新、查询、响应强行共用一个 DTO,会导致字段含义混乱和校验规则冲突。

5. 包名高级但职责混乱

DDD 包名不能自动带来好设计。职责边界比术语更重要。


🆚 十二、Java vs C 对比

维度C 项目Java 企业项目
分层函数模块、头文件、源文件Controller/Service/Repository/DTO
数据结构struct 常跨层传递DTO/Entity/Domain 分离
事务手动控制连接和提交@Transactional 代理管理
对象转换手动字段赋值Assembler/MapStruct
Web 边界依赖具体框架Spring MVC 标准化

Java 企业模式的核心价值是把 Web、业务、持久化和基础设施分开,让复杂系统能长期演进。


💡 十三、最佳实践

  • Controller 保持薄,只处理 HTTP 边界。
  • Service 表达业务用例,控制事务边界。
  • Repository/DAO 只负责数据访问。
  • Request 和 Response DTO 分开建模。
  • Entity 不直接返回给前端。
  • 复杂转换集中到 Assembler/Converter。
  • 全局异常和统一响应要尽早建立。
  • 事务内不要做远程调用,必要时用事件或消息拆分。
  • 先保证职责清晰,再考虑是否引入 DDD 复杂结构。

🎓 小结

企业模式不是某个单独的设计模式,而是一组工程约定:分层、DTO 隔离、事务边界、统一异常、对象转换、接口契约和数据访问边界。

掌握这些模式后,你写出的 Spring Boot 项目才不会只是“能跑”,而是能维护、能测试、能扩展、能交给团队协作。