L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Design et création · SKILL.md

spring-boot-patterns

spring-boot-patterns

Use this skill when implementing Spring Boot patterns — dependency injection, AOP, repository patterns, security configuration, testing patterns, transaction management. This skill enforces: constructor injection (no @Autowired on fields), @Transactional at service layer, AOP for cross-cutting concerns, repository abstraction with Spring Data, slice tests w…

Independent GitHub snapshot · Automated classification · Original documentation governs

Documentation de la Skill

GitHub

name: spring-boot-patterns description: > Use this skill when implementing Spring Boot patterns — dependency injection, AOP, repository patterns, security configuration, testing patterns, transaction management. This skill enforces: constructor injection (no @Autowired on fields), @Transactional at service layer, AOP for cross-cutting concerns, repository abstraction with Spring Data, slice tests with TestContainers. Do NOT use for: Spring Boot project structure (see architecture skill), non-Spring Java, frontend. version: "1.0.0" author: "j4flmao" license: "MIT" compatibility: claude-code: true cursor: true codex: true windsurf: true tags: [backend, spring-boot, patterns, phase-10]

Spring Boot Patterns

Purpose

Implement production-grade Spring Boot patterns — dependency injection, aspect-oriented programming, data access, security, transaction management, and testing.

Architecture Decision Trees

Injection Strategy

CriterionConstructorField (@Autowired)Setter
ImmutabilityYes (final fields)NoNo
TestabilityInstantiate directlyReflection requiredEasy override
Null safetyCompiler-enforcedRuntime NPE if missingRuntime NPE
Circular dependencyDetected at startupHiddenHidden
Constructor paramsAll in one callZero-arg constructor neededMutable
Framework couplingNone (pure Java)Spring annotationSpring annotation

Decision: Constructor injection always. Field injection only in tests (via @InjectMocks). Setter injection never.

Transaction Propagation

PropagationBehaviorUse Case
REQUIRED (default)Join existing or create newMost service methods
REQUIRES_NEWSuspend existing, create newAudit logging, independent actions
NESTEDSavepoint within existingPartial rollback within batch
MANDATORYFail if no existing txInternal service calls that need caller tx
NEVERFail if existing txLong-running reads, avoid connection hold
SUPPORTSOptional txRead-only queries, optional transactional
NOT_SUPPORTEDSuspend existingBackground tasks, after-commit actions

Decision: REQUIRED for write operations. REQUIRES_NEW for audit/events. Read-only SUPPORTS.

Testing Slice Strategy

AnnotationScopeWhat's MockedWhat's Real
@SpringBootTestFull appNothing (full context)Everything
@WebMvcTestWeb layer onlyService layerControllers, filters
@DataJpaTestJPA onlyNothingRepositories, entities
@JsonTestJSON onlyNothingJSON serialization
@RestClientTestREST clientServerRestTemplate/WebClient
@DataRedisTestRedisNothingRedis repos

Decision: @WebMvcTest for controller tests. @DataJpaTest for repository tests. @SpringBootTest for integration/E2E only.

Agent Protocol

Trigger

User request includes: Spring Boot patterns, Spring design patterns, dependency injection Spring, AOP, Spring Data, Spring Security, transaction management, Spring Testing, @Transactional, @Autowired, TestContainers, WireMock, @DataJpaTest, @WebMvcTest.

Input Context

  • Spring Boot version (3.x preferred)
  • Data access (Spring Data JPA, JDBC, R2DBC)
  • Security (Spring Security, OAuth2, JWT)
  • Testing framework (JUnit 5, Mockito, TestContainers)
  • Architecture (package-by-feature, hexagonal)

Output Artifact

Pattern implementations with code examples, configuration snippets, testing strategies.

Response Format

Produce artifact directly. No preamble, no postamble, no explanations. No filler, no hedging, no transitions. Strip articles a/an/the where unambiguous. Compress output — why use many token when few do trick.

Completion Criteria

  • DI pattern with constructor injection and qualifiers
  • AOP aspect for logging/performance/timing
  • Repository pattern with Spring Data
  • Security chain with JWT or OAuth2
  • Transaction propagation and isolation configured
  • Test slices for unit, integration, and E2E

Max Response Length

4096 tokens

Workflow

Step 1: Constructor Injection Pattern

// CORRECT: Constructor injection with required dependencies
@Service
public class OrderService {
    private final OrderRepository orderRepository;
    private final InventoryClient inventoryClient;
    private final EventPublisher eventPublisher;

    public OrderService(
        OrderRepository orderRepository,
        @Qualifier("cachingClient") InventoryClient inventoryClient,
        EventPublisher eventPublisher
    ) {
        this.orderRepository = orderRepository;
        this.inventoryClient = inventoryClient;
        this.eventPublisher = eventPublisher;
    }
}

// WRONG: Field injection — never do this
@Service
public class OrderService {
    @Autowired private OrderRepository orderRepository;  // NO
    @Autowired private InventoryClient inventoryClient;  // NO
}

// WRONG: Setter injection — avoid
@Service
public class OrderService {
    private OrderRepository orderRepository;

    @Autowired  // NO — use constructor
    public void setOrderRepository(OrderRepository repo) {
        this.orderRepository = repo;
    }
}

Step 2: Interface Segregation for Dependencies

// Define narrow interfaces — one responsibility each
public interface OrderRepository {
    Optional<Order> findById(OrderId id);
    Page<Order> findByCustomerId(String customerId, Pageable pageable);
    Order save(Order order);
    void deleteById(OrderId id);
}

public interface InventoryClient {
    boolean checkAvailability(String sku, int quantity);
    int reserveStock(String sku, int quantity);
    void releaseStock(String sku, int quantity);
}

public interface EventPublisher {
    void publish(DomainEvent event);
}

// Implementation with qualifier
@Component
@Qualifier("cachingClient")
public class CachingInventoryClient implements InventoryClient {
    private final InventoryClient delegate;
    private final CacheManager cache;

    public CachingInventoryClient(InventoryClient delegate, CacheManager cache) {
        this.delegate = delegate;
        this.cache = cache;
    }

    @Override
    public boolean checkAvailability(String sku, int quantity) {
        return cache.get(sku, () -> delegate.checkAvailability(sku, quantity));
    }
}

Step 3: AOP for Cross-Cutting Concerns

@Aspect
@Component
public class LoggingAspect {
    private static final Logger log = LoggerFactory.getLogger(LoggingAspect.class);

    @Around("execution(* com.project..*(..)) && @annotation(LogExecution)")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.nanoTime();
        Object result = joinPoint.proceed();
        long duration = System.nanoTime() - start;
        log.info("{} executed in {}ms",
            joinPoint.getSignature().toShortString(),
            TimeUnit.NANOSECONDS.toMillis(duration));
        return result;
    }

    @AfterThrowing(
        pointcut = "execution(* com.project.*.application.*.*(..))",
        throwing = "ex"
    )
    public void logApplicationException(JoinPoint joinPoint, RuntimeException ex) {
        log.error("Application exception in {}: {}",
            joinPoint.getSignature().toShortString(), ex.getMessage());
    }
}

// Custom annotation
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecution {}

Step 4: Repository Pattern

// Domain port (in domain package — zero Spring imports)
public interface OrderRepository {
    Optional<Order> findById(OrderId id);
    Order save(Order order);
    void delete(OrderId id);
}

// Spring Data JPA adapter (in infrastructure)
@Component
public class SpringDataOrderRepository implements OrderRepository {
    private final JpaOrderRepository jpaRepo;

    public SpringDataOrderRepository(JpaOrderRepository jpaRepo) {
        this.jpaRepo = jpaRepo;
    }

    @Override
    public Optional<Order> findById(OrderId id) {
        return jpaRepo.findById(id.getValue())
            .map(OrderJpaEntity::toDomain);
    }

    @Override
    @Transactional
    public Order save(Order order) {
        OrderJpaEntity entity = OrderJpaEntity.fromDomain(order);
        return jpaRepo.save(entity).toDomain();
    }
}

// Spring Data interface (internal to infrastructure)
interface JpaOrderRepository extends JpaRepository<OrderJpaEntity, String> {
    Page<OrderJpaEntity> findByCustomerId(String customerId, Pageable pageable);
    Optional<OrderJpaEntity> findByOrderNumber(String orderNumber);
}

Step 5: Transaction Management

@Service
public class PaymentService {
    private final PaymentGateway gateway;
    private final PaymentRepository paymentRepo;
    private final OrderRepository orderRepo;

    public PaymentService(
        PaymentGateway gateway,
        PaymentRepository paymentRepo,
        OrderRepository orderRepo
    ) {
        this.gateway = gateway;
        this.paymentRepo = paymentRepo;
        this.orderRepo = orderRepo;
    }

    @Transactional(rollbackFor = PaymentException.class)
    public PaymentResult processPayment(ProcessPaymentCommand command) {
        // 1. Validate order
        Order order = orderRepo.findById(command.orderId())
            .orElseThrow(() -> new OrderNotFoundException(command.orderId()));

        // 2. Charge payment gateway
        PaymentCharge charge = gateway.charge(
            command.amount(),
            command.paymentToken()
        );

        // 3. Save payment record
        Payment payment = Payment.create(command.orderId(), charge.transactionId(), command.amount());
        paymentRepo.save(payment);

        // 4. Update order status
        order.markAsPaid();
        orderRepo.save(order);

        return new PaymentResult(payment.getId(), charge.transactionId());
    }
}

Step 6: Security Configuration

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
    private final JwtTokenProvider tokenProvider;
    private final UserDetailsService userDetailsService;

    public SecurityConfig(JwtTokenProvider tokenProvider, UserDetailsService userDetailsService) {
        this.tokenProvider = tokenProvider;
        this.userDetailsService = userDetailsService;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(AbstractHttpConfigurer::disable)
            .sessionManagement(session ->
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/v1/auth/**", "/actuator/health").permitAll()
                .requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated())
            .authenticationProvider(authenticationProvider())
            .addFilterBefore(
                new JwtAuthenticationFilter(tokenProvider, userDetailsService),
                UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(passwordEncoder());
        return provider;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Step 7: Event-Driven Pattern

// Domain event
public record OrderSubmittedEvent(OrderId orderId, String customerId, Money total) implements DomainEvent {}

// Publisher
@Component
public class SpringEventPublisher implements EventPublisher {
    private final ApplicationEventPublisher publisher;

    public SpringEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    @Override
    public void publish(DomainEvent event) {
        publisher.publishEvent(event);
    }
}

// Listener
@Component
public class OrderSubmittedHandler {
    private final EmailService emailService;
    private final InventoryService inventoryService;

    public OrderSubmittedHandler(EmailService emailService, InventoryService inventoryService) {
        this.emailService = emailService;
        this.inventoryService = inventoryService;
    }

    @EventListener
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void handleOrderSubmitted(OrderSubmittedEvent event) {
        emailService.sendOrderConfirmation(event.orderId(), event.customerId());
        inventoryService.reserveItems(event.orderId());
    }
}

// Async event processing
@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean
    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("async-");
        executor.initialize();
        return executor;
    }
}

@Component
public class AsyncOrderHandler {
    @EventListener
    @Async
    public void handle(OrderSubmittedEvent event) {
        // Long-running task runs on thread pool
    }
}

Production Considerations

Performance

  • Connection pool: spring.datasource.hikari.maximum-pool-size=20 (adjust per concurrent users)
  • JPA: spring.jpa.open-in-view=false (prevents lazy init exceptions in views, forces explicit fetch planning)
  • Caching: Spring Cache with Caffeine for local, Redis for distributed; annotate methods with @Cacheable
  • Async: @Async with custom ThreadPoolTaskExecutor (corePoolSize=10, maxPoolSize=50)
  • Logging: logback-spring.xml with structured JSON appender; async appender for high-throughput
  • Graceful shutdown: server.shutdown=graceful + spring.lifecycle.timeout-per-shutdown-phase=30s

Observability

  • Actuator: expose /actuator/health, /actuator/metrics, /actuator/prometheus (secured behind management port)
  • Metrics: Micrometer with MicrometerRegistry; custom metrics via MeterRegistry bean
  • Tracing: Micrometer Tracing with Brave + Zipkin for distributed tracing
  • Health indicators: custom HealthIndicator for external services (DB, Redis, Kafka, downstream APIs)

Common Pitfalls

  • N+1 query: use @EntityGraph or JOIN FETCH in JPQL; enable hibernate.query.fail_on_pagination_over_collection_fetch
  • Lazy loading exceptions: disable open-in-view, plan fetches explicitly
  • @Transactional on private methods: Spring proxies can't intercept private calls — move to public or use self-injection

Anti-Patterns

Anti-PatternWhyFix
@Autowired on fieldsCan't test without Spring; hidden depsConstructor injection
@Transactional in controllersTransaction spans HTTP response; connection heldService-layer only
@DataJpaTest with full contextSlow test startupUse slice annotations
@SpringBootTest for every testSlow CI; tests depend on full contextSlice tests per concern
Catch-all @ComponentScanSlow startup; unexpected beans picked upExplicit configuration with @Enable...
Giant application.propertiesHard to maintain; no type safetyType-safe @ConfigurationProperties per module
Using @Query with string concatenationSQL injection riskUse @Param + named parameters or Specification
@Async without custom executorDefault SimpleAsyncTaskExecutor creates unlimited threadsDefine ThreadPoolTaskExecutor bean
Service calling service directly (no interface)Tight coupling; can't proxy for tx/AOPDefine interface, proxy will apply cross-cutting

Rules

  • references/spring-boot-data-access.md — Spring Boot Data Access Reference
  • references/spring-boot-deployment.md — Spring Boot Deployment
  • references/spring-boot-observability.md — Spring Boot Observability
  • references/spring-boot-security.md — Spring Boot Security Reference
  • references/spring-patterns.md — Spring Patterns
  • references/spring-testing.md — Spring Testing

Handoff

Hand off to backend/spring-boot/architecture/SKILL.md for project structure or backend/universal/testing/SKILL.md for broader testing patterns.

Implementation Patterns

Factory Pattern for Module Creation

function createModule<T>(config: ModuleConfig): T { const dependencies = initializeDependencies(config); const module = new Module(dependencies); module.hooks.onInit(); return module as T; }

Builder Pattern for Complex Configuration

class ConfigBuilder { private config: AppConfig = new AppConfig(); withDatabase(url: string): ConfigBuilder { ... } withCache(ttl: number): ConfigBuilder { ... } withLogging(level: string): ConfigBuilder { ... } build(): AppConfig { return this.config; } }

Production Considerations

Deployment Checklist

  • Production build with optimizations enabled
  • Environment variables configured per environment
  • Health check endpoint responds correctly
  • Error tracking and monitoring integrated
  • Logging level configured (not debug in production)
  • Resource limits configured
  • Database migrations applied
  • Static assets built and served from CDN or cache
  • Feature flags toggled appropriately
  • Rollback plan documented and tested

Monitoring and Alerting

MetricThresholdSeverityAction
Error rate> 1%CriticalRollback or fix
p95 latency> 500msWarningProfile and optimize
Uptime< 99.9%CriticalInvestigate infrastructure
Memory usage> 80%WarningCheck for leaks
CPU usage> 80%WarningScale up or optimize

Rules

  • Prefer composition over inheritance
  • Favor immutable data structures
  • Use dependency injection for testability
  • Keep functions pure when possible — no side effects
  • Fail fast with clear error messages
  • Don't repeat yourself (DRY) — extract shared logic
  • Keep it simple (KISS) — avoid unnecessary complexity
  • You aren't gonna need it (YAGNI) — build what's required
  • Separate concerns — single responsibility per module
  • Code to interfaces, not implementations
  • Write self-documenting code — clear names over comments
  • Prefer standard library over third-party dependencies
  • Handle errors explicitly — no silent failures
  • Validate inputs at boundaries
  • Log at appropriate levels (debug, info, warn, error)
Skills enregistrées