关于 layered-architecture
为 Spring 应用开发者提供分层架构实践指南,说明控制器、服务、仓库及 DTO 的职责边界。还涵盖映射、配置和横切关注点的组织方式。
主要功能
- 01控制器、服务与仓库职责划分
- 02请求和响应 DTO 分离
- 03独立映射器与静态工厂方法
- 04配置层组织规则
- 05事务、验证与异常处理规范
适用场景
- 01规划 Spring 应用的层级职责
- 02避免控制器承载业务逻辑
- 03设计实体到响应 DTO 的映射
- 04整理事务与异常处理位置
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ rrezartprebreza/ spring-boot-skills --skill layered-architecture安装后在 Claude Code 中输入/layered-architecture确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlexamples/bad-service.javagood-service.javatemplates/CreateOrderRequest.javaOrderMapper.javaOrderResponse.java详细介绍
分层架构
分层规则
@RestController ← HTTP only. No business logic. No JPA entities in responses.
↓ DTOs
@Service ← All business logic lives here. Orchestrates repositories.
↓ Domain objects / Entities
@Repository ← Data access only. No business logic. Returns entities or projections.
↓ JPA / JDBC
Database
控制器层
- 处理 HTTP:解析请求、验证输入(
@Valid)、返回响应 - 每个端点只调用一个服务方法——不在控制器中编排流程
- 绝不直接返回
@Entity类——始终映射为响应 DTO - 绝不注入
@Repository——始终通过@Service访问 - 通过
@ControllerAdvice处理异常,绝不在控制器中使用 try/catch
// ✅ GOOD
@PostMapping("/orders")
public ResponseEntity<OrderResponse> createOrder(@Valid @RequestBody CreateOrderRequest request) {
Order order = orderService.createOrder(request);
return ResponseEntity.status(HttpStatus.CREATED).body(OrderResponse.from(order));
}
// ❌ BAD — business logic in controller
@PostMapping("/orders")
public ResponseEntity<Order> createOrder(@RequestBody CreateOrderRequest request) {
if (request.getItems().isEmpty()) throw new RuntimeException("No items");
Order order = orderRepository.save(new Order(request)); // direct repo access
return ResponseEntity.ok(order); // returning entity
}
服务层
- 包含所有业务逻辑、验证规则和流程编排
@Transactional应放在此处,而非控制器或仓库中- 仅使用构造函数注入——绝不使用
@Autowired字段注入 - 每个聚合根对应一个服务(使用 OrderService,而不是 OrderAndPaymentService)
- 返回领域对象或 DTO——绝不返回
HttpServletRequest/HttpServletResponse
// ✅ GOOD
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
private final InventoryService inventoryService;
@Transactional
public Order createOrder(CreateOrderRequest request) {
inventoryService.reserve(request.items()); // records use accessor names, not getX()
Order order = Order.from(request);
return orderRepository.save(order);
}
}
// ❌ BAD — field injection, HTTP concern in service
@Service
public class OrderService {
@Autowired private OrderRepository orderRepository;
public ResponseEntity<Order> createOrder(...) { ... } // HTTP type in service
}
仓库层
- 继承
JpaRepository<Entity, ID>或CrudRepository - 通过
@Query或查询派生定义自定义查询——除非无法避免,否则不使用原始 SQL - 返回实体或 Spring Data 投影——绝不返回原始
Object[] - 不包含业务逻辑——仅负责数据访问
DTO
- 分别创建请求 / 响应 DTO——绝不在两者中使用同一个类
- 仅在请求 DTO 上添加验证注解(
@NotNull、@Size等) - 使用静态工厂方法
ResponseDto.from(Entity entity)进行映射 - 不可变 DTO 使用 record(Java 16+)
// ✅ GOOD
public record OrderResponse(UUID id, String status, List<LineItemResponse> items, Instant createdAt) {
public static OrderResponse from(Order order) {
return new OrderResponse(order.getId(), order.getStatus().name(),
order.getItems().stream().map(LineItemResponse::from).toList(),
order.getCreatedAt());
}
}
映射器模式
- 不要在控制器和服务中编写映射逻辑——使用专用映射器类或静态工厂方法
- 映射器应为普通类或工具类——除非需要注入依赖,否则不应设为 Spring Bean
- 实体 → 响应 DTO:在响应 DTO 上定义静态方法(
OrderResponse.from(order)) - 请求 DTO → 实体:在实体上定义静态工厂方法(
Order.from(request)),或使用映射器类 - 集合映射:使用
.stream().map(OrderResponse::from).toList()——绝不使用手动循环
// ✅ GOOD — dedicated mapper for complex mappings
public class OrderMapper {
public static OrderResponse toResponse(Order order) {
return new OrderResponse(
order.getId(),
order.getStatus().name(),
order.getItems().stream().map(OrderMapper::toLineItem).toList(),
order.getCreatedAt()
);
}
public static Order toEntity(CreateOrderRequest request, User user) {
Order order = Order.create(request.customerEmail(), user);
request.items().forEach(item ->
order.addItem(item.productId(), item.quantity()));
return order;
}
private static LineItemResponse toLineItem(OrderItem item) {
return new LineItemResponse(item.getProductId(), item.getQuantity(), item.getPrice());
}
}
配置层
@Configuration类放在config/包中——绝不放在service/或controller/中- 配置类绝不导入服务类或控制器类
- 使用
@ConfigurationProperties实现类型安全的配置——绝不使用原始@Value来配置一组相关设置 - Bean 定义仅用于基础设施相关事项(RestTemplate、ObjectMapper、SecurityFilterChain)
横切关注点
- 日志:使用
@Slf4j——绝不使用System.out.println - 验证:在控制器参数上使用
@Valid,自定义验证器使用@Component - 异常处理:使用单个
@RestControllerAdvice类,绝不在控制器中使用 try/catch - 审计:搭配
@EnableJpaAuditing使用@CreatedDate/@LastModifiedDate
常见问题
- Agent 倾向于在控制器上添加
@Transactional——将其移到服务中 - Agent 使用
@Autowired字段注入——始终使用构造函数注入(@RequiredArgsConstructor) - Agent 从控制器返回
List<Entity>——始终映射为List<ResponseDto> - Agent 创建
OrderAndInventoryService这样的臃肿类——按聚合拆分 - Agent 将映射逻辑写在控制器中——提取到映射器类或 DTO 工厂方法中
- Agent 创建依赖
@ServiceBean 的@Configuration类——配置类应只负责装配基础设施
评论
支持 Markdown