# Java Spring Backend

> Java/Spring Boot 백엔드 기능을 새로 작성하거나 구조를 바꿀 때 사용한다. Controller/Service/Repository 계층 배치, transaction 경계 설정, DTO와 entity 경계, 예외 처리 및 응답 규약, 설정(profile/property) 배치 판단이 필요할 때 트리거된다. "Spring에서 이 기능 어떻게 짜지", "서비스 계층 나누는 게 맞나", "@Transactional 어디에 붙이나", "entity를 그대로 반환해도 되나" 같은 요청에 해당한다. 이미 작성된 코드를 평가하는 것이 목적이면 code-review를, 성능/장애 원인 추적이 목적이면 debugging을 쓴다.

- Skill: `j99way99/java-spring-backend` (Agent Skill)
- Install (CLI): `npx skillmds@latest add j99way99/java-spring-backend`
- Raw SKILL.md: https://api.skillmd.com/api/skills/j99way99/java-spring-backend/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: j99way99 (https://skillmd.com/u/j99way99)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/j99way99/java-spring-backend

---


# Java/Spring 백엔드 구현

Spring Boot 백엔드 코드를 **새로 쓰거나 구조를 바꿀 때**의 판단 절차다. 프레임워크 사용법
설명이 아니라, 어디에 무엇을 둘지 결정하는 기준이다.

## 절차

1. **변경 단위를 먼저 정한다.** 요청 하나가 끝나는 지점(트랜잭션 1건, 외부 호출 1건)을
   식별한다. 이 경계가 정해지지 않으면 계층 배치도 정할 수 없다.
2. **계층에 책임을 배정한다.** 아래 기준을 벗어나면 벗어나는 이유를 코드가 아니라 말로 설명할 수 있어야 한다.
3. **트랜잭션 경계를 명시한다.**
4. **경계에서 타입을 바꾼다.** 외부로 나가는 타입과 내부 타입을 분리한다.
5. **실패 경로를 먼저 정한다.** 정상 경로보다 예외 응답 규약을 먼저 확정한다.

## 계층 배치 기준

| 계층 | 두는 것 | 두지 않는 것 |
|---|---|---|
| Controller | 요청/응답 매핑, 입력 검증(형식), 인증 주체 추출 | 비즈니스 분기, 트랜잭션, 엔티티 직접 조작 |
| Service | 유스케이스 흐름, 트랜잭션 경계, 도메인 규칙 조합 | HTTP 개념(status, header), 쿼리 최적화 세부 |
| Domain/Entity | 불변식, 상태 전이 규칙 | 영속성 외 인프라 의존, 프레젠테이션 포맷 |
| Repository | 조회/저장, 쿼리 | 비즈니스 분기, 트랜잭션 시작 |

계층을 늘리기 전에 먼저 묻는다: **이 계층이 없으면 무엇이 깨지는가.** 답이 없으면 만들지 않는다.

## 트랜잭션 경계

- `@Transactional`은 **Service의 유스케이스 메서드**에 둔다. Controller와 Repository에 두지 않는다.
- 조회 전용 경로는 `readOnly = true`를 명시한다.
- **외부 호출(HTTP, 메시지 발행, 결제, 메일)을 트랜잭션 안에 넣지 않는다.** 커밋 이후로 밀거나
  (이벤트/아웃박스), 트랜잭션을 외부 호출 앞뒤로 쪼갠다. 이 규칙을 어기면 DB 락 유지 시간이
  외부 서비스 지연에 묶인다.
- 같은 클래스 내부 호출은 프록시를 타지 않아 트랜잭션이 걸리지 않는다. 자기 호출 구조가
  보이면 경계 설계가 잘못된 신호다.
- 예외에 의한 롤백 범위를 확인한다. checked exception은 기본적으로 롤백되지 않는다.

## DTO와 entity 경계

- **entity를 controller 응답으로 그대로 반환하지 않는다.** 지연 로딩, 순환 참조, 내부 필드
  노출이 한꺼번에 따라온다.
- 요청 DTO → 도메인 변환은 Service 진입 지점에서 끝낸다.
- 조회 전용 화면은 entity를 거치지 않고 projection으로 바로 뽑는 쪽을 먼저 검토한다.

## 예외와 응답 규약

- 도메인 예외는 도메인 언어로 던지고, HTTP 상태 매핑은 한 곳(`@RestControllerAdvice`)에 모은다.
- 에러 응답 body 형식을 프로젝트 전체에서 하나로 고정한다. 형식이 이미 정해져 있으면 그것을 따른다.
- 예외를 삼키지 않는다. 로그만 남기고 정상 응답을 반환하는 코드는 장애를 은폐한다.

## 설정

- 환경에 따라 달라지는 값은 코드가 아니라 property로 뺀다.
- property는 `@ConfigurationProperties`로 타입 있는 객체에 바인딩한다. 문자열 키를 코드 곳곳에
  흩지 않는다.
- **비밀값을 저장소에 커밋하지 않는다.** 주입 경로(환경변수, 시크릿 매니저)를 먼저 정한다.

## 마무리 확인

- 새로 만든 public 메서드 각각에 대해: 트랜잭션 경계 안인가 밖인가 답할 수 있는가
- 외부 호출이 트랜잭션 안에 들어가 있지 않은가
- entity가 controller 밖으로 새어 나가지 않는가
- 실패했을 때 호출자가 무엇을 받는지 정의되어 있는가

프로젝트 고유의 계층 규칙·패키지 구조·네이밍이 있으면 **그 프로젝트의 CLAUDE.md가 우선한다.**

