agentsclimarketplace

Layered architecture

Skill rrezartprebreza/spring-boot-skills/skills/spring-boot-4/layered-architecture

Production-grade Claude Code and Codex skills for Spring Boot developers

Install
npx -y skills add rrezartprebreza/spring-boot-skills --skill layered-architecture

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

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.

SKILL.md

6.6 KB, as published. Nobody here has run it

Layered Architecture

Layer Rules

@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

Controller Layer

  • Handles HTTP: parsing requests, validating input (@Valid), returning responses
  • Calls ONE service method per endpoint — no orchestration in controllers
  • Never returns @Entity classes directly — always map to response DTOs
  • Never injects @Repository — always goes through a @Service
  • Exception handling via @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
}

Service Layer

  • Contains all business logic, validation rules, and orchestration
  • @Transactional lives here, not in controllers or repositories
  • Constructor injection only — never @Autowired field injection
  • One service per aggregate root (OrderService, not OrderAndPaymentService)
  • Returns domain objects or DTOs — never HttpServletRequest / HttpServletResponse
  • Retries and concurrency caps on service methods via Framework 7's @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
}

Repository Layer

  • Extends JpaRepository<Entity, ID> or CrudRepository
  • Custom queries via @Query or query derivation — no raw SQL unless unavoidable
  • Returns entities or Spring Data Projections — never raw Object[]
  • No business logic — pure data access

DTOs

  • Separate Request / Response DTOs — never use the same class for both
  • Validation annotations (@NotNull, @Size, etc.) on Request DTOs only
  • Static factory method ResponseDto.from(Entity entity) for mapping
  • Use records for immutable DTOs (Java 16+)
// ✅ 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());
    }
}

Mapper Pattern

  • Keep mapping logic out of controllers and services — use dedicated mapper classes or static factory methods
  • Mapper is a plain class or utility — not a Spring bean unless it needs injected dependencies
  • Entity → Response DTO: static method on the response DTO (OrderResponse.from(order))
  • Request DTO → Entity: static factory on the entity (Order.from(request)) or a mapper class
  • Collection mapping: use .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 Layer

  • @Configuration classes live in a config/ package — never in service/ or controller/
  • Configuration never imports service or controller classes
  • Use @ConfigurationProperties for type-safe config — never raw @Value for groups of related settings
  • Bean definitions for infrastructure concerns only (RestClient, JsonMapper, SecurityFilterChain)

Cross-Cutting Concerns

  • Logging: use @Slf4j — never System.out.println
  • Validation: @Valid on controller parameters, custom validators as @Component
  • Exception handling: single @RestControllerAdvice class, never try/catch in controllers
  • Auditing: @CreatedDate / @LastModifiedDate with @EnableJpaAuditing

Gotchas

  • Agent tends to put @Transactional on controllers — move it to services
  • Agent uses @Autowired field injection — always use constructor injection (@RequiredArgsConstructor)
  • Agent returns List<Entity> from controllers — always map to List<ResponseDto>
  • Agent creates OrderAndInventoryService god classes — split by aggregate
  • Agent puts mapping logic inside controllers — extract to mapper class or DTO factory method
  • Agent creates @Configuration classes that depend on @Service beans — configuration should only wire infrastructure
  • Agent defines an ObjectMapper bean to customize JSON — Boot 4 uses Jackson 3 (tools.jackson): define JsonMapper beans, and @JsonComponent is now @JacksonComponent
  • Agent adds spring-retry for service-level retries — built into Framework 7 (@Retryable, @EnableResilientMethods)

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.