핵심 한 줄
기능을 하나 더 만드는 게 아니라 틀리기 쉬운 지점을 골라 반복한다. 백엔드 설계는 결국 세 개의 질문으로 압축된다 — “불변식을 누가 지키나(L1·L2)”, “동시에 오면(L3 경합)”, “두 번 오면 / 트랜잭션 밖으로 나가면(L3 경계)“. 이 시리즈는 값 객체 하나에서 시작해 카타가 진행되는 만큼 계속 자란다. 완결이 아니라 진행 중인 기록이다.
시리즈 — 커머스 백엔드를 카타로 쌓다 (계속 연재)
00.카타 서문 · 01.돈을 primitive로 두면 언젠가 샌다 · 02.불변식은 누가 지키나 · 03.동시에 사면 초과판매된다 · 04.두 번 취소해도 한 번만 · 05.save가 INSERT가 아니었다 · 06.기다리게 하지 말고 거절하라 · 07.트랜잭션 안에서 HTTP를 부르지 마라 · 08.응답을 못 받았다는 실패가 아니다 · 09.요청 하나에 쿼리 1+N · 10.5만 행을 1행으로 · 11.같은 걸 두 번 묻지 않기 · 12.가짜는 내가 시킨 답만 한다 · 13.분명히 잡았는데 터졌다 · 14.5개 주세요 했는데 5만 개를 읽었다 · 15.순수하게 만들었더니 저장이 사라졌다 · 16.설정 한 줄 바꿨더니 세 번 터졌다 · … 그리고 계속
1. 왜 기능이 아니라 카타인가
토이 프로젝트를 하나 더 만드는 건 쉽다. CRUD를 붙이고, 화면을 그리고, “동작한다”고 말한다. 하지만 동작하는 것과 틀리지 않는 것은 다르다. 커머스 백엔드가 실제로 무너지는 지점은 화면이 아니라 보이지 않는 곳이다 — 두 사람이 마지막 재고 하나를 동시에 사고, 결제 취소 요청이 네트워크 재시도로 두 번 도착하고, 트랜잭션이 외부 PG 응답을 기다리며 커넥션을 붙잡는다.
그래서 기능을 넓히는 대신 틀리기 쉬운 지점 하나하나를 카타(kata, 반복 연습)로 골랐다. 각 편은 “이런 걸 만들었다”가 아니라 **“여기서 이렇게 틀렸고, 그래서 이렇게 바꿨다”**의 기록이다.
2. 세 개의 축 — 난이도는 기능이 아니라 사고
카타를 난이도로 나누는 기준은 코드량이 아니라 어떤 사고를 요구하는가였다.
- L1 — 값과 불변식. 돈을
double로 두지 않기. 값 객체가 스스로를 방어하기. “이 값이 깨질 수 있나?” - L2 — 애그리거트. 여러 값이 모인 덩어리의 일관성을 누가 지키나. 불변식이 서비스로 새지 않게. “경계는 어디인가?”
- L3 — 경합과 경계. 여기서부터 진짜다. “동시에 오면? 두 번 오면? 트랜잭션 밖으로 나가면?” 동시성·멱등성·외부호출이 전부 이 축에 있다.
이 세 질문이 시리즈의 등뼈다. 뒤로 갈수록 답이 하나가 아니라 트레이드오프가 된다.
3. 지금까지 밟은 길
각 편은 앞 편이 드러낸 문제에 떠밀려 나온다:
| 편 | 문제 | 배우는 것 |
|---|---|---|
| 1 | 돈을 long으로 더하다 | 값 객체·불변·반올림 |
| 2 | 총액 계산이 서비스로 샌다 | 애그리거트 루트 |
| 3 | 재고 100에 주문 150이 동시에 | 낙관/비관/원자적 UPDATE |
| 4 | 취소 요청이 두 번 도착 | 멱등성·유니크 제약 |
| 5 | save()가 INSERT를 안 했다 | Persistable·merge 함정 |
| 6 | 중복이 락에서 기다린다 | IN_PROGRESS 상태기계 |
| 7 | 트랜잭션이 HTTP를 기다린다 | 외부호출 경계 |
| 8 | 타임아웃 = 실패? | UNKNOWN이라는 세 번째 상태 |
| 9 | 목록 하나에 쿼리가 1+N | N+1·fetch join |
| 10 | 5만 행 풀스캔 | EXPLAIN 읽기·인덱스 |
| 11 | 같은 걸 계속 다시 묻는다 | 캐시 두 층 |
여기서 멈추지 않는다. 날짜 경계(반품 기한), 할인 배분의 잔돈, 주문 상태머신의 전이 가드 — 카타가 이어지는 만큼 편도 이어진다. 이 표는 앞으로 늘어난다.
4. 전제
- 스택은 Java 21 · Spring Boot · JPA · MySQL. 하지만 원리는 스택을 넘는다 — 유니크 제약으로 중복을 막는 발상, “느린 건 트랜잭션 밖으로”라는 감각, “응답 못 받음 ≠ 실패”라는 외부 세계의 불확실성은 언어가 바뀌어도 남는다.
- 모든 편은 테스트나 실행계획으로 검증한다. “그럴 것이다”가 아니라 “이렇게 나왔다”로 말한다.
시작은 가장 작은 벽돌 — 돈 이다.