Communitygithub.com

springboot-patterns

Spring Boot 아키텍처 패턴, REST API 설계, 계층형 서비스, 데이터 접근, 캐싱, 비동기 처리 및 로깅. Java Spring Boot 백엔드 작업 시 사용합니다. REST 계층, 서비스, 데이터 접근, 캐싱 또는 비동기 작업을 포함한 Spring Boot 백엔드를 구축하거나 검토할 때 사용하세요.

springboot-patterns란 무엇인가요?

이 스킬은 Spring Boot 백엔드 개발을 위한 포괄적인 아키텍처 및 API 패턴을 제공합니다. 컨트롤러, 서비스, 리포지토리 계층을 구조화하는 방법, Spring Data JPA를 사용한 리포지토리 패턴, 트랜잭션 관리, DTO 및 유효성 검증, 전역 예외 처리, 캐싱, 비동기 처리, 로깅, 필터, 페이지네이션 및 정렬, 재시도 로직, 요청 제한(Rate Limiting)까지 다룹니다. Spring MVC/WebFlux 기반의 REST API 구축, 이벤트 기반 패턴 구현, 프로덕션 환경 설정에 특히 유용합니다. 프로덕션 환경에서 확장 가능하고 유지보수 가능한 Spring Boot 서비스를 구축하는 모든 개발자에게 실용적인 코드 예제와 함께 모범 사례를 제시합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/springboot-patterns

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

Spring Boot 개발 패턴

확장 가능하고 프로덕션 등급의 서비스를 위한 Spring Boot 아키텍처 및 API 패턴입니다.

활성화 시점

  • Spring MVC 또는 WebFlux로 REST API 구축
  • 컨트롤러 → 서비스 → 리포지토리 계층 구조화
  • Spring Data JPA, 캐싱 또는 비동기 처리 구성
  • 유효성 검증, 예외 처리 또는 페이지네이션 추가
  • 개발/스테이징/프로덕션 환경을 위한 프로파일 설정
  • Spring Events 또는 Kafka를 사용한 이벤트 중심 패턴 구현

REST API 구조

@RestController
@RequestMapping("/api/markets")
@Validated
class MarketController {
  private final MarketService marketService;

  MarketController(MarketService marketService) {
    this.marketService = marketService;
  }

  @GetMapping
  ResponseEntity<Page<MarketResponse>> list(
      @RequestParam(defaultValue = "0") int page,
      @RequestParam(defaultValue = "20") int size) {
    Page<Market> markets = marketService.list(PageRequest.of(page, size));
    return ResponseEntity.ok(markets.map(MarketResponse::from));
  }

  @PostMapping
  ResponseEntity<MarketResponse> create(@Valid @RequestBody CreateMarketRequest request) {
    Market market = marketService.create(request);
    return ResponseEntity.status(HttpStatus.CREATED).body(MarketResponse.from(market));
  }
}

리포지토리 패턴 (Spring Data JPA)

public interface MarketRepository extends JpaRepository<MarketEntity, Long> {
  @Query("select m from MarketEntity m where m.status = :status order by m.volume desc")
  List<MarketEntity> findActive(@Param("status") MarketStatus status, Pageable pageable);
}

트랜잭션을 포함한 서비스 계층

@Service
public class MarketService {
  private final MarketRepository repo;

  public MarketService(MarketRepository repo) {
    this.repo = repo;
  }

  @Transactional
  public Market create(CreateMarketRequest request) {
    MarketEntity entity = MarketEntity.from(request);
    MarketEntity saved = repo.save(entity);
    return Market.from(saved);
  }
}

DTO 및 유효성 검증

public record CreateMarketRequest(
    @NotBlank @Size(max = 200) String name,
    @NotBlank @Size(max = 2000) String description,
    @NotNull @FutureOrPresent Instant endDate,
    @NotEmpty List<@NotBlank String> categories) {}

public record MarketResponse(Long id, String name, MarketStatus status) {
  static MarketResponse from(Market market) {
    return new MarketResponse(market.id(), market.name(), market.status());
  }
}

예외 처리

@ControllerAdvice
class GlobalExceptionHandler {
  @ExceptionHandler(MethodArgumentNotValidException.class)
  ResponseEntity<ApiError> handleValidation(MethodArgumentNotValidException ex) {
    String message = ex.getBindingResult().getFieldErrors().stream()
        .map(e -> e.getField() + ": " + e.getDefaultMessage())
        .collect(Collectors.joining(", "));
    return ResponseEntity.badRequest().body(ApiError.validation(message));
  }

  @ExceptionHandler(AccessDeniedException.class)
  ResponseEntity<ApiError> handleAccessDenied() {
    return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ApiError.of("Forbidden"));
  }

  @ExceptionHandler(Exception.class)
  ResponseEntity<ApiError> handleGeneric(Exception ex) {
    // 예상치 못한 오류는 스택 추적과 함께 로깅
    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
        .body(ApiError.of("Internal server error"));
  }
}

캐싱

구성 클래스에 @EnableCaching이 필요합니다.

@Service
public class MarketCacheService {
  private final MarketRepository repo;

  public MarketCacheService(MarketRepository repo) {
    this.repo = repo;
  }

  @Cacheable(value = "market", key = "#id")
  public Market getById(Long id) {
    return repo.findById(id)
        .map(Market::from)
        .orElseThrow(() -> new EntityNotFoundException("Market not found"));
  }

  @CacheEvict(value = "market", key = "#id")
  public void evict(Long id) {}
}

비동기 처리

구성 클래스에 @EnableAsync가 필요합니다.

@Service
public class NotificationService {
  @Async
  public CompletableFuture<Void> sendAsync(Notification notification) {
    // 이메일/SMS 전송
    return CompletableFuture.completedFuture(null);
  }
}

로깅 (SLF4J)

@Service
public class ReportService {
  private static final Logger log = LoggerFactory.getLogger(ReportService.class);

  public Report generate(Long marketId) {
    log.info("generate_report marketId={}", marketId);
    try {
      // 로직
    } catch (Exception ex) {
      log.error("generate_report_failed marketId={}", marketId, ex);
      throw ex;
    }
    return new Report();
  }
}

미들웨어 / 필터

@Component
public class RequestLoggingFilter extends OncePerRequestFilter {
  private static final Logger log = LoggerFactory.getLogger(RequestLoggingFilter.class);

  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
      FilterChain filterChain) throws ServletException, IOException {
    long start = System.currentTimeMillis();
    try {
      filterChain.doFilter(request, response);
    } finally {
      long duration = System.currentTimeMillis() - start;
      log.info("req method={} uri={} status={} durationMs={}",
          request.getMethod(), request.getRequestURI(), response.getStatus(), duration);
    }
  }
}

페이지네이션 및 정렬

PageRequest page = PageRequest.of(pageNumber, pageSize, Sort.by("createdAt").descending());
Page<Market> results = marketService.list(page);

오류 복원 외부 호출

public <T> T withRetry(Supplier<T> supplier, int maxRetries) {
  int attempts = 0;
  while (true) {
    try {
      return supplier.get();
    } catch (Exception ex) {
      attempts++;
      if (attempts >= maxRetries) {
        throw ex;
      }
      try {
        Thread.sleep((long) Math.pow(2, attempts) * 100L);
      } catch (InterruptedException ie) {
        Thread.currentThread().interrupt();
        throw ex;
      }
    }
  }
}

요청 제한 (필터 + Bucket4j)

보안 참고사항: X-Forwarded-For 헤더는 클라이언트가 위조할 수 있으므로 기본적으로 신뢰할 수 없습니다. 전달된 헤더는 다음 조건을 충족할 때만 사용하세요:

  1. 앱이 신뢰할 수 있는 리버스 프록시(nginx, AWS ALB 등) 뒤에 있는 경우
  2. ForwardedHeaderFilter를 빈으로 등록한 경우
  3. application.properties에 server.forward-headers-strategy=NATIVE 또는 FRAMEWORK를 설정한 경우
  4. 프록시가 X-Forwarded-For 헤더를 추가하지 않고 덮어쓰도록 구성된 경우

ForwardedHeaderFilter가 올바르게 구성되면 request.getRemoteAddr()는 전달된 헤더에서 올바른 클라이언트 IP를 자동으로 반환합니다. 이 구성이 없으면 request.getRemoteAddr()를 직접 사용하세요. 이는 신뢰할 수 있는 유일한 값인 직접 연결 IP를 반환합니다.

@Component
public class RateLimitFilter extends OncePerRequestFilter {
  private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();

  /*
   * 보안: 이 필터는 요청 제한을 위해 클라이언트 식별에 request.getRemoteAddr()를 사용합니다.
   *
   * 애플리케이션이 리버스 프록시(nginx, AWS ALB 등) 뒤에 있는 경우, 정확한 클라이언트 IP 감지를 위해
   * 전달된 헤더를 올바르게 처리하도록 Spring을 구성해야 합니다:
   *
   * 1. application.properties/yaml에 server.forward-headers-strategy=NATIVE (클라우드 플랫폼)
   *    또는 FRAMEWORK 설정
   * 2. FRAMEWORK 전략 사용 시 ForwardedHeaderFilter 등록:
   *
   *    @Bean
   *    ForwardedHeaderFilter forwardedHeaderFilter() {
   *        return new ForwardedHeaderFilter();
   *    }
   *
   * 3. 프록시가 X-Forwarded-For 헤더를 추가하지 않고 덮어쓰도록 구성하여 위조 방지
   * 4. 컨테이너에 맞게 server.tomcat.remoteip.trusted-proxies 등 구성
   *
   * 이 구성이 없으면 request.getRemoteAddr()는 클라이언트 IP가 아닌 프록시 IP를 반환합니다.
   * X-Forwarded-For를 직접 읽지 마세요. 신뢰할 수 있는 프록시 처리 없이는 쉽게 위조될 수 있습니다.
   */
  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
      FilterChain filterChain) throws ServletException, IOException {
    // ForwardedHeaderFilter가 구성된 경우 올바른 클라이언트 IP를 반환하고,
    // 그렇지 않으면 직접 연결 IP를 반환하는 getRemoteAddr()를 사용합니다.
    // 적절한 프록시 구성 없이 X-Forwarded-For 헤더를 직접 신뢰하지 마십시오.
    String clientIp = request.getRemoteAddr();

    Bucket bucket = buckets.computeIfAbsent(clientIp,
        k -> Bucket.builder()
            .addLimit(Bandwidth.classic(100, Refill.greedy(100, Duration.ofMinutes(1))))
            .build());

    if (bucket.tryConsume(1)) {
      filterChain.doFilter(request, response);
    } else {
      response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
    }
  }
}

백그라운드 작업

Spring의 @Scheduled를 사용하거나 큐(예: Kafka, SQS, RabbitMQ)와 통합합니다. 핸들러는 멱등성을 유지하고 관찰 가능해야 합니다.

관찰 가능성

  • 구조화된 로깅(JSON) via Logback encoder
  • 메트릭: Micrometer + Prometheus/OTel
  • 추적: Micrometer Tracing with OpenTelemetry 또는 Brave 백엔드

프로덕션 기본값

  • 필드 주입보다 생성자 주입 선호
  • RFC 7807 오류를 위해 spring.mvc.problemdetails.enabled=true 활성화 (Spring Boot 3+)
  • 워크로드에 맞게 HikariCP 풀 크기 구성, 타임아웃 설정
  • 쿼리에 @Transactional(readOnly = true) 사용
  • @NonNull 및 Optional을 적절히 사용하여 null 안전성 강화

기억하세요: 컨트롤러는 얇게, 서비스는 집중적으로, 리포지토리는 단순하게, 오류는 중앙에서 처리하세요. 유지보수성과 테스트 용이성을 최적화하세요.

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

관련 스킬