rrezartprebreza/spring-boot-layered-architecture
> Use when generating or modifying any Spring Boot class — controllers, services, repositories, DTOs, mappers, or configuration. Enforces strict layer separation and prevents business logic from leaking across boundaries.
npx skills add https://github.com/rrezartprebreza/spring-boot-skills --skill layered-architecture
@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
@Valid), returning responses@Entity classes directly — always map to response DTOs@Repository — always goes through a @Service@ControllerAdvice, never try/catch in controllers// ✅ 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 lives here, not in controllers or repositories@Autowired field injectionHttpServletRequest / HttpServletResponse@Retryable / @ConcurrencyLimit(enable with @EnableResilientMethods) — no spring-retry dependency
// ✅ GOOD
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
private final InventoryService inventoryService;
@Transactional
public Order createOrder(CreateOrderRequest request) {
inventoryService.reserve(request.getItems());
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> or CrudRepository@Query or query derivation — no raw SQL unless unavoidableObject[]@NotNull, @Size, etc.) on Request DTOs onlyResponseDto.from(Entity entity) for mapping// ✅ GOOD
public record OrderResponse(UUID id, String status, List<LineItemResponse> items) {
public static OrderResponse from(Order order) {
return new OrderResponse(order.getId(), order.getStatus().name(),
order.getItems().stream().map(LineItemResponse::from).toList());
}
}
OrderResponse.from(order))Order.from(request)) or a mapper class.stream().map(OrderResponse::from).toList() — never manual loops// ✅ 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 classes live in a config/ package — never in service/ or controller/@ConfigurationProperties for type-safe config — never raw @Value for groups of related settings@Slf4j — never System.out.println@Valid on controller parameters, custom validators as @Component@RestControllerAdvice class, never try/catch in controllers@CreatedDate / @LastModifiedDate with @EnableJpaAuditing@Transactional on controllers — move it to services@Autowired field injection — always use constructor injection (@RequiredArgsConstructor)List<Entity> from controllers — always map to List<ResponseDto>OrderAndInventoryService god classes — split by aggregate@Configuration classes that depend on @Service beans — configuration should only wire infrastructureObjectMapper bean to customize JSON — Boot 4 uses Jackson 3 (tools.jackson): define JsonMapper beans, and @JsonComponent is now @JacksonComponent@Retryable, @EnableResilientMethods)Take rrezartprebreza/spring-boot-layered-architecture from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.