說明
The codebase follows a strict three-layer architecture:
- Controller — handles HTTP requests, delegates to Service
- Service — contains business logic, delegates to Repository
- Repository — handles data access
Dependencies must flow downward only. A Controller may never import a Repository class directly. A Service may never import a Controller class.
為什麼重要
- Testability — Services can be tested without HTTP; Repositories can be tested without business logic
- Replaceability — Swapping a REST Controller for a gRPC Controller only touches the Controller layer
- Cognitive load — Developers know exactly where to put new code and where to look for existing logic
Enforcement
@ArchTest
static final ArchRule layeringRule = layered()
.consideringAllDependencies()
.that().resideInAPackage("..controller..")
.should().onlyDependOnClassesThat()
.resideInAnyPackage(
"..service..",
"..model..",
"java..",
"javax..",
"org.springframework.."
);
Rule: java:S2583 (Make "class diamond" visits only through "interface" types)
This rule catches direct Repository references from Controller classes.
正例
@RestController
public class OrderController {
private final OrderService orderService; // ✅ depends on Service layer
@GetMapping("/orders/{id}")
public ResponseEntity<OrderDto> getOrder(@PathVariable Long id) {
return ResponseEntity.ok(orderService.getOrder(id));
}
}
反例
@RestController
public class OrderController {
private final OrderRepository orderRepo; // ❌ bypasses Service layer
@GetMapping("/orders/{id}")
public ResponseEntity<OrderDto> getOrder(@PathVariable Long id) {
return ResponseEntity.ok(orderRepo.findById(id));
}
}
相關規則
- ARCH-002: Dependency Inversion — Services should depend on abstractions, not concrete implementations