DS ForgeCourses
기존 Lab

문법 이후의 백엔드 엔지니어링

껍데기를
제거하라

요청 한 건을 소켓에서 JVM, Spring proxy, transaction, connection, 외부 결제까지 추적한다. 추측 대신 코드로 깨뜨리고, metric으로 측정하고, release gate로 복구한다.

한 요청의 기준 경로

  1. socket → Tomcat request thread
  2. filters → DispatcherServlet
  3. controller → transaction proxy
  4. Hibernate → HikariCP → PostgreSQL
  5. outbox → payment adapter → recovery
SPEC · 공식 사양·문서가 정의한 사실VERIFIED BEHAVIOR · 고정된 이 저장소의 테스트·명령으로 재현하는 동작RECOMMENDED PRACTICE · 근거 있는 권장안이며 유일한 정답은 아님PROJECT POLICY · 이 capstone이 선택한 값이며 부하·SLO에 맞춰 다시 정할 것

읽기 전에

이 가이드의 사용법

읽는 문서가 아니라, 같은 commerce service를 네 번 통과하는 작업 지시서다.

01 · 설명

빈 종이에 책임, thread, proxy, transaction, connection 경계를 그린다.

02 · 재현

고정된 명령과 테스트로 정상 경로와 실패를 직접 관찰한다.

03 · 운영

SLO, 보안, 관측, 복구, 배포 gate를 증거와 함께 제출한다.

COMPANION REPOSITORY

실행되는 코드가 설명의 최종 근거다

Java 21 LTS + Spring Boot 4.1.0 + Gradle 9.6.1을 고정했다. Gradle을 택한 이유는 wrapper, toolchain, dependency locking, test suite 분리가 이 교육 저장소의 재현성 요구에 직접 맞기 때문이다.

고정 스택

Java 21 · Boot 4.1.0 · Gradle 9.6.1

clean install

macOS/Linux: cd lab && ./gradlew clean check · Windows: cd lab; .\gradlew.bat clean check

검증 층

unit · integration · contract · concurrency · failure

P00—P10

하나의 서비스, 열한 번의 절개

각 모듈은 이전 코드를 버리지 않는다. 최소 경로를 만들고, 깨뜨리고, 단단하게 만든다.

P00모듈 00Java 코드가 JVM에서 실행되기까지주문 요청의 한 줄이 bytecode, stack frame, heap allocation, JIT, GC와 container limit에 닿는 경로를 설명한다.

냉정한 실체

SPEC

JVM은 Java 소스가 아니라 class file의 명령을 실행한다. class는 loading 뒤 verification·preparation·resolution을 포함한 linking을 거쳐 initialization되며, JIT와 GC 정책은 JVM 구현의 영역이다.

Oracle · JLS 12장 · 실행Oracle · JVMS Java SE 21

흔한 오해

  • 오해: 지역 변수는 모두 stack, 객체는 모두 heap이라 성능을 예측할 수 있다.
  • 오해: virtual thread는 CPU 작업도 자동으로 더 빠르게 만든다.
  • 오해: heap limit만 설정하면 container OOM을 막는다.

선수 지식

  • Java class·method·thread 기본 개념
  • shell에서 JDK 21과 Gradle Wrapper 실행

예상 시간

2시간 30분

이 모듈의 capstone 증분

JDK 21 실행 기준선, memory budget, JFR 수집 경로를 고정한다.

15분 개념 지도

8–12

실행과 메모리

RECOMMENDED PRACTICE

frame·heap·metaspace·allocation·GC root를 한 호출 위에 표시한다.

Oracle · JVMS Java SE 21

JVM · Spring 내부 실행 과정

SPEC

Application class loader가 OrderApplication의 binary representation을 찾고 VM이 검증한 뒤 static initializer를 실행한다.

Oracle · JLS 12장 · 실행
SPEC

각 method invocation은 frame을 만들며 operand stack과 local variables를 사용한다. heap과 method area의 실제 배치는 구현 세부다.

Oracle · JVMS Java SE 21
SPEC

가시성·순서 보장은 program order, monitor unlock/lock, volatile write/read, thread start/join 같은 happens-before edge에서 나온다.

Oracle · JLS 17장 · 스레드와 메모리 모델
SPEC

Java 21 virtual thread는 I/O 대기가 많은 task에 적합하며 장시간 CPU 집약 task용이 아니다.

Oracle · Java 21 Thread API · 가상 스레드

컴파일되는 최소 경로 발췌

src/test/java/dev/productionlab/commerce/learning/P00BytecodeUnitTest.javajava
final class P00BytecodeUnitTest {
  record OrderId(long value) {}

  static long unwrap(OrderId id) { return id.value(); }

  @org.junit.jupiter.api.Test
  void recordCompilesToJvmMethods() {
    org.assertj.core.api.Assertions.assertThat(unwrap(new OrderId(42))).isEqualTo(42);
  }
}

test 뒤 javap -c -v로 constructor, accessor, invocation bytecode와 class-file version을 확인한다.

PROJECT POLICY

P00 test와 javap 출력으로 Java 21 class file을 확인해야 한다.

Oracle · JVMS Java SE 21

컴파일되는 production 확장 발췌

Dockerfiledockerfile
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75.0",
  "-XX:+ExitOnOutOfMemoryError", "-jar", "/app/app.jar"]

현재 Dockerfile은 heap 밖의 metaspace, code cache, thread stack, direct buffer, native memory를 위해 container memory의 25%를 남기고 OOM에서 즉시 종료한다.

PROJECT POLICY

75%는 현재 container fixture의 시작값이며 실제 RSS·OOM·GC 자료로 조정한다.

Oracle · JDK Flight Recorder 런타임 가이드

중요 코드 해설

SPEC

record도 class의 한 종류이며 value accessor 호출은 JVM method invocation으로 실행된다.

Oracle · JVMS Java SE 21
RECOMMENDED PRACTICE

escape analysis 결과를 소스만 보고 단정하지 말고 JFR allocation sample과 필요한 경우 JIT 진단으로 확인한다.

Oracle · JDK Flight Recorder 런타임 가이드

요청 하나의 end-to-end call trace

  1. PROJECT POLICY

    compileJava evidence에는 선택된 JDK 21 compiler와 build/classes 산출물 경로가 있어야 한다.

    Gradle · Gradle 빌드 생명주기
  2. SPEC

    JVM startup은 initial class를 load/link/initialize하고 main을 호출한다.

    Oracle · JLS 12장 · 실행
  3. RECOMMENDED PRACTICE

    POST /api/v1/orders의 allocation은 orderId와 traceId로 표시한 JFR window에서 찾는다.

    Oracle · JDK Flight Recorder 런타임 가이드
  4. PROJECT POLICY

    container memory kill과 Java heap OOM을 같은 사건으로 분류하지 않고 exit reason, RSS, heap를 함께 기록한다.

실패 주입 실습

allocation 폭증과 memory ceiling

주입
child JVM을 -Xmx32m -XX:+ExitOnOutOfMemoryError로 시작하고 4MiB 배열을 유지한다.
명령
./gradlew failureTest
관찰
10초 안에 child JVM이 non-zero로 종료된다. JFR/GC/RSS 분석은 별도 learner measurement다.
복구
payload 상한, streaming/배치 크기, heap/native 예산을 조정하고 같은 fixture로 재측정한다.
VERIFIED BEHAVIOR

P00MemoryCeilingFailureTest는 현재 Java 실행 파일을 교차 플랫폼 방식으로 찾아 작은 heap child process의 non-zero exit를 assertion한다.

Oracle · JDK Flight Recorder 런타임 가이드

테스트 계약

unit

./gradlew unitTest

증명 범위: 격리된 도메인 규칙과 경계값

증명하지 않는 것: Spring wiring, 실제 DB, 네트워크 동작

integration

./gradlew integrationTest

증명 범위: 고정된 DB·Spring 조합의 실제 연결

증명하지 않는 것: 운영 데이터 분포와 장시간 부하

contract

./gradlew contractTest

증명 범위: HTTP·결제 adapter 경계의 요청/응답 계약

증명하지 않는 것: 상대 시스템의 가용성

load

k6 run -e SCENARIO=p00 scripts/load-test.js

증명 범위: 명시한 부하 모델에서 latency·saturation 변화

증명하지 않는 것: 다른 하드웨어·데이터·트래픽에서 같은 수치

p95/p99 · 자원 측정

PROJECT POLICY

모든 부하 결과에 p50/p95/p99, 오류율, 처리량과 함께 CPU, heap/RSS, GC pause, DB pool active/pending, executor active/queue를 같은 시간축으로 기록한다. 이 모듈의 초점은 allocation rate·GC pause·RSS다.

Micrometer · Micrometer Concepts
RECOMMENDED PRACTICE

평균만으로 gate를 통과시키지 않는다. warm-up, duration, concurrency, payload, DB row 수, CPU/memory limit을 결과와 함께 저장한다.

Micrometer · Micrometer Concepts

보안 경계

PROJECT POLICY

heap/JFR/dump 파일에 대한 신뢰 경계를 표시하고 API key·payment token·DB credential은 코드, fixture, log, trace attribute에 넣지 않는다.

Spring · Spring Security · Servlet Architecture
RECOMMENDED PRACTICE

입력 검증과 권한 검사는 서로 대체하지 않는다. 인증된 주체, 허용된 행위, 대상 리소스 소유권을 각각 확인한다.

Spring · Spring Security · Servlet Architecture

log · metric · trace · profile

PROJECT POLICY

구조화 log에는 timestamp, level, service, traceId, orderId, outcome, error.type을 남기되 원문 body와 secret은 제외한다. 핵심 신호는 jvm.memory, jvm.gc.pause, process.memory, allocation profile다.

Spring · Spring Boot · Observability
RECOMMENDED PRACTICE

metric은 집계 가능한 상태, trace는 한 요청의 인과 경로, profile은 CPU·allocation 원인에 사용한다. 하나의 신호로 다른 신호를 흉내 내지 않는다.

Spring · Spring Boot · ObservabilityOpenTelemetry · OpenTelemetry JavaOracle · JDK Flight Recorder 런타임 가이드

실제 제출 산출물

  • source→bytecode→class lifecycle→execution을 표시한 1장 그림
  • javap 출력과 JFR recording
  • container memory failure 보고서

기계적 완료 조건

  • 고정 JDK 확인
    ./gradlew --version
    통과 기준: JVM과 toolchain이 21
  • bytecode 확인
    ./gradlew classes && javap -c build/classes/java/main/dev/productionlab/commerce/CommerceApplication.class
    통과 기준: main invocation bytecode 출력
  • 대표 실패 재현
    ./gradlew failureTest
    통과 기준: P00 child-JVM OOM fixture를 포함해 failure suite가 통과

자가시험 · 답 보기

linking의 세 작업은?
verification, preparation, resolution. resolution은 구현이 늦출 수 있다.
virtual thread가 DB connection 수를 늘리는가?
아니다. 대기 thread 비용을 낮출 뿐 DB pool과 DB 처리량은 별도 제한이다.

P01모듈 01Java 언어를 production 관점에서 다시 배우기금액·시간·식별자·상태를 잘못 표현해 주문 불변식을 깨는 코드를 타입 경계에서 제거한다.

냉정한 실체

SPEC

Java는 primitive 값과 reference 값을 모두 값으로 전달한다. reference를 전달해도 변수 자체는 복사되며, equals와 hashCode는 hash collection의 정확성에 직접 연결된다.

Oracle · JLS 17장 · 스레드와 메모리 모델Oracle · Object equals/hashCode 계약

흔한 오해

  • 오해: Java는 객체를 reference로 전달한다.
  • 오해: BigDecimal이면 금액 문제가 모두 끝난다.
  • 오해: Stream은 loop보다 항상 간결하고 빠르다.

선수 지식

  • P00의 value/reference와 allocation
  • 주문·재고·결제의 기본 상태

예상 시간

3시간

이 모듈의 capstone 증분

OrderId·Sku·Quantity·Money·상태와 Clock 경계를 도메인 타입으로 고정한다.

15분 개념 지도

JVM · Spring 내부 실행 과정

SPEC

generic type argument는 대체로 erasure 후 runtime class에 남지 않으므로 unchecked cast가 type safety를 우회할 수 있다.

Oracle · JVMS Java SE 21
SPEC

List<Integer>는 List<Number>의 subtype이 아니다. 값을 읽는 producer는 ? extends T, 값을 받는 consumer는 ? super T로 경계를 표현하며 wildcard capture가 필요하면 helper method로 type parameter를 이름 붙인다.

Oracle · dev.java · Generic wildcard와 PECS
SPEC

HashMap 같은 hash 기반 collection은 key가 들어간 뒤 equality/hash 결과가 변하면 lookup 계약을 유지할 수 없다.

Oracle · Java 21 Collections FrameworkOracle · Object equals/hashCode 계약
SPEC

Stream pipeline은 terminal operation이 시작될 때 traversal하며 side effect와 encounter order는 operation 계약을 읽어야 한다.

Oracle · Java 21 Collections Framework

컴파일되는 최소 경로 발췌

src/test/java/dev/productionlab/commerce/learning/P01LanguageContractsTest.javajava
record Sku(String value) {
  Sku { if (value == null || value.isBlank()) throw new IllegalArgumentException("sku"); }
}
record Quantity(int value) {
  Quantity { if (value < 1) throw new IllegalArgumentException("quantity"); }
}
record Money(java.math.BigDecimal amount, java.util.Currency currency) {
  Money {
    if (amount == null || currency == null || amount.signum() < 0) {
      throw new IllegalArgumentException("money");
    }
  }
}

record constructor에서 null·범위를 막고 통화 없는 소수를 금액으로 전달하지 않는다.

RECOMMENDED PRACTICE

경계에서 invalid state를 거부해 downstream 분기를 줄인다.

Oracle · Object equals/hashCode 계약

컴파일되는 production 확장 발췌

src/test/java/dev/productionlab/commerce/learning/P01LanguageContractsTest.javajava
sealed interface OrderDecision permits Accepted, Rejected {}
record Accepted(java.time.Instant acceptedAt) implements OrderDecision {}
record Rejected(String code) implements OrderDecision {}

java.time.Clock clock = java.time.Clock.fixed(
    java.time.Instant.parse("2026-01-01T00:00:00Z"),
    java.time.ZoneOffset.UTC);
OrderDecision decision = new Accepted(clock.instant());

sealed result는 성공/거절 처리를 exhaustively 만들고 Clock 주입은 시간 test를 결정적으로 만든다.

RECOMMENDED PRACTICE

저장은 Instant, 표시는 명시한 ZoneId에서 변환한다.

Oracle · java.time 패키지

중요 코드 해설

SPEC

record component 기반 equality는 값 객체에 적합하지만 entity identity와 같은 개념은 아니다.

Oracle · Object equals/hashCode 계약
PROJECT POLICY

금액 scale·rounding·통화 허용 목록은 결제 계약에 고정하고 암묵적 반올림을 금지한다.

요청 하나의 end-to-end call trace

  1. PROJECT POLICY

    contract test는 JSON sku/quantity/amount의 역직렬화 뒤 domain value constructor 재검증을 요구한다.

  2. SPEC

    reference argument는 복사되지만 mutable object의 동일한 state를 가리킬 수 있으므로 defensive copy 경계가 필요하다.

    Oracle · Object equals/hashCode 계약
  3. PROJECT POLICY

    acceptedAt은 server Clock의 Instant로 생성하고 client timezone은 저장 상태에 섞지 않는다.

  4. RECOMMENDED PRACTICE

    API error에는 안정된 code와 안전한 message만 노출하고 exception class/stack은 외부에 내보내지 않는다.

    Spring · Spring Security · Servlet Architecture

실패 주입 실습

mutable HashMap key로 사라진 주문

주입
mutable key를 map에 넣은 뒤 hash에 참여하는 필드를 변경한다.
명령
./gradlew failureTest
관찰
같은 instance lookup이 실패하는 fixture가 재현된다.
복구
immutable value key로 바꾸고 equals/hashCode contract test를 추가한다.
VERIFIED BEHAVIOR

P01LanguageContractsTest는 insert 뒤 key를 변경하면 같은 instance의 Map.get이 null이 되는 실패를 재현한다.

Oracle · Object equals/hashCode 계약Oracle · Java 21 Collections Framework

테스트 계약

unit

./gradlew unitTest

증명 범위: 격리된 도메인 규칙과 경계값

증명하지 않는 것: Spring wiring, 실제 DB, 네트워크 동작

integration

./gradlew integrationTest

증명 범위: 고정된 DB·Spring 조합의 실제 연결

증명하지 않는 것: 운영 데이터 분포와 장시간 부하

contract

./gradlew contractTest

증명 범위: HTTP·결제 adapter 경계의 요청/응답 계약

증명하지 않는 것: 상대 시스템의 가용성

load

k6 run -e SCENARIO=p01 scripts/load-test.js

증명 범위: 명시한 부하 모델에서 latency·saturation 변화

증명하지 않는 것: 다른 하드웨어·데이터·트래픽에서 같은 수치

p95/p99 · 자원 측정

PROJECT POLICY

모든 부하 결과에 p50/p95/p99, 오류율, 처리량과 함께 CPU, heap/RSS, GC pause, DB pool active/pending, executor active/queue를 같은 시간축으로 기록한다. 이 모듈의 초점은 Stream/loop allocation, serialization payload, CPU다.

Micrometer · Micrometer Concepts
RECOMMENDED PRACTICE

평균만으로 gate를 통과시키지 않는다. warm-up, duration, concurrency, payload, DB row 수, CPU/memory limit을 결과와 함께 저장한다.

Micrometer · Micrometer Concepts

보안 경계

PROJECT POLICY

금액·시간·오류 message에 대한 신뢰 경계를 표시하고 API key·payment token·DB credential은 코드, fixture, log, trace attribute에 넣지 않는다.

Spring · Spring Security · Servlet Architecture
RECOMMENDED PRACTICE

입력 검증과 권한 검사는 서로 대체하지 않는다. 인증된 주체, 허용된 행위, 대상 리소스 소유권을 각각 확인한다.

Spring · Spring Security · Servlet Architecture

log · metric · trace · profile

PROJECT POLICY

구조화 log에는 timestamp, level, service, traceId, orderId, outcome, error.type을 남기되 원문 body와 secret은 제외한다. 핵심 신호는 order.validation.rejected와 error.code cardinality다.

Spring · Spring Boot · Observability
RECOMMENDED PRACTICE

metric은 집계 가능한 상태, trace는 한 요청의 인과 경로, profile은 CPU·allocation 원인에 사용한다. 하나의 신호로 다른 신호를 흉내 내지 않는다.

Spring · Spring Boot · ObservabilityOpenTelemetry · OpenTelemetry JavaOracle · JDK Flight Recorder 런타임 가이드

실제 제출 산출물

  • Order value type source와 contract
  • mutable-key failure fixture
  • 시간대·통화 정책 ADR

기계적 완료 조건

  • 값 타입 단위 test
    ./gradlew unitTest
    통과 기준: null·음수·통화·DST 경계 case 통과
  • mutable key 실패
    ./gradlew failureTest
    통과 기준: 실패와 교정 assertion 모두 통과
  • 금액 JSON 계약
    ./gradlew contractTest
    통과 기준: scale·currency·error code snapshot 일치

자가시험 · 답 보기

reference가 pass-by-value라는 말의 뜻은?
callee가 reference 값의 복사본을 받는다. 같은 mutable object를 가리킬 수 있지만 caller 변수 자체를 재지정할 수 없다.
Optional을 entity field나 argument에 무조건 쓰지 않는 이유는?
Optional은 반환에서 부재를 모델링하는 도구다. framework mapping·serialization·null 계약을 흐릴 수 있어 경계별 계약을 먼저 정해야 한다.

P02모듈 02Build와 dependency같은 commit이 clean macOS·Windows·CI에서 같은 toolchain과 dependency graph로 검증되게 만든다.

냉정한 실체

SPEC

Gradle은 initialization, configuration, execution 단계에서 task graph를 만들고 실행한다. BOM은 version 정렬을 돕지만 plugin, repository, lock, toolchain, generated source까지 자동 고정하지 않는다.

Gradle · Gradle 빌드 생명주기Gradle · Platform/BOM 정렬

흔한 오해

  • 오해: lockfile만 commit하면 reproducible build가 완성된다.
  • 오해: starter는 숨겨진 runtime이다.
  • 오해: multi-module은 항상 더 좋은 architecture다.

선수 지식

  • Gradle task와 classpath 기초
  • P01 domain package

예상 시간

2시간

이 모듈의 capstone 증분

재현 가능한 Gradle build와 분리된 검증 task를 release gate로 만든다.

15분 개념 지도

JVM · Spring 내부 실행 과정

SPEC

settings.gradle.kts가 project graph를 정의하고 build script가 task/model을 구성한 뒤 요청 task와 dependency만 실행된다.

Gradle · Gradle 빌드 생명주기
SPEC

implementation은 main compile/runtime graph에, testImplementation은 test compile/runtime graph에 참여한다. runtime classpath는 compile classpath와 같다고 가정할 수 없다.

Gradle · Gradle 빌드 생명주기
RECOMMENDED PRACTICE

현재 capstone은 package-by-feature 단일 deployable로 시작한다. 독립 build/release 또는 강제 compile boundary가 필요할 때만 subproject를 추가한다.

Gradle · Gradle 빌드 생명주기

컴파일되는 최소 경로 발췌

build.gradle.ktskotlin
plugins {
  java
  id("org.springframework.boot") version "4.1.0"
}
java { toolchain { languageVersion = JavaLanguageVersion.of(21) } }
dependencies {
  implementation(platform("org.springframework.boot:spring-boot-dependencies:4.1.0"))
  implementation("org.springframework.boot:spring-boot-starter-webmvc")
  testImplementation("org.springframework.boot:spring-boot-starter-test")
}

plugin, Java toolchain, BOM을 명시한다. 실제 resolved version은 dependency report와 lock으로 검증한다.

PROJECT POLICY

commerce-lab은 Java 21, Spring Boot BOM 4.1.0, Gradle Wrapper 9.6.1을 고정한다.

Gradle · Platform/BOM 정렬

컴파일되는 production 확장 발췌

build.gradle.ktskotlin
dependencyLocking { lockAllConfigurations() }

fun taggedTest(name: String, tag: String) = tasks.register<Test>(name) {
  testClassesDirs = sourceSets.test.get().output.classesDirs
  classpath = sourceSets.test.get().runtimeClasspath
  useJUnitPlatform { includeTags(tag) }
  shouldRunAfter(tasks.test)
}
val integrationTest = taggedTest("integrationTest", "integration")
val contractTest = taggedTest("contractTest", "contract")
val concurrencyTest = taggedTest("concurrencyTest", "concurrency")
val failureTest = taggedTest("failureTest", "failure")

tasks.check {
  dependsOn(integrationTest, contractTest, concurrencyTest, failureTest)
}

빠른 unit과 외부 resource integration을 분리하되 check가 둘 다 요구하게 만든다.

RECOMMENDED PRACTICE

test 이름이 아니라 task graph로 CI gate를 표현하고 lock 변경을 review한다.

Gradle · Dependency Locking

중요 코드 해설

SPEC

BOM constraint는 명시 version 없는 dependency를 정렬하지만 dependency를 추가하지 않는다.

Gradle · Platform/BOM 정렬
RECOMMENDED PRACTICE

annotation processor는 implementation과 분리해 compile classpath 누출과 cache miss 원인을 줄인다.

Gradle · Gradle 빌드 생명주기

요청 하나의 end-to-end call trace

  1. PROJECT POLICY

    ./gradlew clean check는 Wrapper distribution을 확인하고 unit→integration→contract→concurrency→failure gate를 모두 실행하도록 구성한다.

    Gradle · Gradle 빌드 생명주기
  2. SPEC

    dependency graph resolution은 repository metadata, constraint, conflict rule, lock state로 최종 artifact를 선택한다.

    Gradle · Dependency LockingGradle · Platform/BOM 정렬
  3. PROJECT POLICY

    CI는 cache를 써도 clean checkout에서 check를 실행하며 lock 또는 verification metadata 변경을 별도 review한다.

  4. RECOMMENDED PRACTICE

    macOS의 ./gradlew와 Windows의 gradlew.bat를 모두 문서화하고 개인 JDK 경로를 build script에 넣지 않는다.

    Gradle · Gradle 빌드 생명주기

실패 주입 실습

transitive dependency drift

주입
disposable copy에서 lock 또는 checksum metadata를 제거·변경한 뒤 strict resolution을 실행한다.
명령
./gradlew --dependency-verification strict verifyReproducibilityFiles dependencies --configuration runtimeClasspath
관찰
dependency insight가 선택 이유를 출력하고 lock mismatch가 build를 실패시킨다.
복구
상위 dependency/BOM을 의도적으로 갱신하고 graph·release note·lock diff를 함께 review한다.
PROJECT POLICY

clean check는 metadata 존재와 strict resolution을 gate로 둔다. destructive drift injection은 저장소 원본이 아닌 disposable copy에서 수행한다.

Gradle · Dependency Locking

테스트 계약

unit

./gradlew unitTest

증명 범위: 격리된 도메인 규칙과 경계값

증명하지 않는 것: Spring wiring, 실제 DB, 네트워크 동작

integration

./gradlew integrationTest

증명 범위: 고정된 DB·Spring 조합의 실제 연결

증명하지 않는 것: 운영 데이터 분포와 장시간 부하

contract

./gradlew contractTest

증명 범위: HTTP·결제 adapter 경계의 요청/응답 계약

증명하지 않는 것: 상대 시스템의 가용성

load

k6 run -e SCENARIO=p02 scripts/load-test.js

증명 범위: 명시한 부하 모델에서 latency·saturation 변화

증명하지 않는 것: 다른 하드웨어·데이터·트래픽에서 같은 수치

p95/p99 · 자원 측정

PROJECT POLICY

모든 부하 결과에 p50/p95/p99, 오류율, 처리량과 함께 CPU, heap/RSS, GC pause, DB pool active/pending, executor active/queue를 같은 시간축으로 기록한다. 이 모듈의 초점은 configuration time, task cache hit, clean build duration다.

Micrometer · Micrometer Concepts
RECOMMENDED PRACTICE

평균만으로 gate를 통과시키지 않는다. warm-up, duration, concurrency, payload, DB row 수, CPU/memory limit을 결과와 함께 저장한다.

Micrometer · Micrometer Concepts

보안 경계

PROJECT POLICY

dependency repository·plugin·artifact provenance에 대한 신뢰 경계를 표시하고 API key·payment token·DB credential은 코드, fixture, log, trace attribute에 넣지 않는다.

Spring · Spring Security · Servlet Architecture
RECOMMENDED PRACTICE

입력 검증과 권한 검사는 서로 대체하지 않는다. 인증된 주체, 허용된 행위, 대상 리소스 소유권을 각각 확인한다.

Spring · Spring Security · Servlet Architecture

log · metric · trace · profile

PROJECT POLICY

구조화 log에는 timestamp, level, service, traceId, orderId, outcome, error.type을 남기되 원문 body와 secret은 제외한다. 핵심 신호는 build scan 없이도 남는 task duration·cache state·dependency report다.

Spring · Spring Boot · Observability
RECOMMENDED PRACTICE

metric은 집계 가능한 상태, trace는 한 요청의 인과 경로, profile은 CPU·allocation 원인에 사용한다. 하나의 신호로 다른 신호를 흉내 내지 않는다.

Spring · Spring Boot · ObservabilityOpenTelemetry · OpenTelemetry JavaOracle · JDK Flight Recorder 런타임 가이드

실제 제출 산출물

  • Wrapper와 lock files
  • dependency graph/insight snapshot
  • macOS·Windows clean-install 명령

기계적 완료 조건

  • clean gate
    ./gradlew clean check --no-daemon
    통과 기준: 모든 검증 task 통과
  • dependency lock
    ./gradlew dependencies --write-locks
    통과 기준: 의도하지 않은 lock diff 없음
  • toolchain
    ./gradlew javaToolchains
    통과 기준: Java 21 compiler 발견, 개인 absolute path는 VCS에 없음

자가시험 · 답 보기

BOM과 lock의 차이는?
BOM은 호환 version constraint를 제공하고 lock은 특정 resolution 결과를 고정한다.
starter의 실체는?
관련 dependency와 때로 metadata를 묶은 artifact다. bean 생성은 classpath와 auto-configuration 조건이 별도로 결정한다.

P03모듈 03Spring Core 내부OrderApplicationService가 어떤 BeanDefinition에서 어떤 proxy로 만들어지고 호출되는지 runtime class까지 추적한다.

냉정한 실체

SPEC

IoC container는 configuration metadata를 읽어 BeanDefinition을 만들고 bean을 생성·연결한다. AOP 기능은 일반적으로 원본 객체 주위의 proxy가 MethodInterceptor chain을 실행해 제공한다.

Spring · Spring Framework · 컨테이너와 BeanSpring · Spring AOP · 프록시 메커니즘

흔한 오해

  • 오해: @Service 자체가 객체를 특별하게 만든다.
  • 오해: 같은 class 안의 method 호출도 proxy를 다시 지난다.
  • 오해: constructor circular dependency는 Spring이 해결해야 한다.

선수 지식

  • P02 runtime classpath와 reflection
  • interface·inheritance·method invocation

예상 시간

2시간 30분

이 모듈의 capstone 증분

order/inventory/payment package의 dependency 방향과 실제 proxy를 가시화한다.

15분 개념 지도

JVM · Spring 내부 실행 과정

SPEC

ApplicationContext refresh 중 BeanFactory post-processors가 definition을 수정하고 BeanPostProcessor가 bean instance initialization 전후에 개입한다.

Spring · Spring Framework · 컨테이너와 BeanSpring · Spring Framework · 생명주기 콜백
SPEC

@Configuration class는 bean method 간 호출 semantics를 보존하기 위해 runtime enhancement 대상이 될 수 있다. proxyBeanMethods=false는 그 inter-bean 호출을 요구하지 않는 구성에 적합하다.

Spring · Spring Framework · 컨테이너와 Bean
SPEC

Spring AOP는 interface가 있으면 JDK dynamic proxy를 사용할 수 있고 class proxy는 subclassing 제약(final class/method 등)을 받는다.

Spring · Spring AOP · 프록시 메커니즘
RECOMMENDED PRACTICE

constructor cycle은 책임이 얽혔다는 architecture signal로 취급하고 port/event/orchestrator 경계를 재검토한다.

Spring · Spring Framework · 컨테이너와 Bean

컴파일되는 최소 경로 발췌

src/main/java/dev/productionlab/commerce/order/OrderApplicationService.javajava
@org.springframework.stereotype.Service
public class OrderApplicationService {
  public OrderApplicationService(
      IdempotencyStore idempotencyStore,
      InventoryService inventoryService,
      OrderRepository orderRepository,
      PaymentRepository paymentRepository,
      OutboxRepository outboxRepository,
      Clock clock,
      MeterRegistry registry) {
    // Assign every mandatory dependency explicitly.
  }
}

parameter가 dependency graph를 드러내며 test에서 같은 class를 Spring 없이 생성할 수 있다.

RECOMMENDED PRACTICE

필수 dependency는 constructor로 표현하고 null/field injection을 허용하지 않는다.

Spring · Spring Framework · 컨테이너와 Bean

컴파일되는 production 확장 발췌

src/test/java/dev/productionlab/commerce/learning/P03ProxyIntegrationTest.javajava
@org.junit.jupiter.api.Tag("integration")
class P03ProxyIntegrationTest extends PostgresIntegrationTest {
  @org.springframework.beans.factory.annotation.Autowired OrderApplicationService service;

  @org.junit.jupiter.api.Test
  void reportsRuntimeType() {
    assertThat(org.springframework.aop.support.AopUtils.isAopProxy(service)).isTrue();
    assertThat(org.springframework.aop.support.AopUtils.getTargetClass(service))
        .isEqualTo(OrderApplicationService.class);
    assertThat(service).isInstanceOf(org.springframework.aop.framework.Advised.class);
    var advised = (org.springframework.aop.framework.Advised) service;
    assertThat(advised.getAdvisors()).isNotEmpty();
  }
}

runtime proxy, target class, transaction advisor를 분리해 현재 wiring을 test artifact로 남긴다.

PROJECT POLICY

통합 test가 현재 wiring에서 proxy 존재와 target class를 재현해야 한다.

Spring · Spring AOP · 프록시 메커니즘

중요 코드 해설

SPEC

proxyBeanMethods=false는 @Bean method를 직접 여러 번 호출해도 container singleton lookup으로 바꾸지 않는다.

Spring · Spring Framework · 컨테이너와 Bean
RECOMMENDED PRACTICE

proxy 여부를 class 이름 문자열로 판단하지 말고 Spring AopUtils와 advisor inspection을 사용한다.

Spring · Spring AOP · 프록시 메커니즘

요청 하나의 end-to-end call trace

  1. SPEC

    component scan 또는 @Import가 OrderConfiguration을 candidate로 등록하고 parser가 @Bean method를 BeanDefinition으로 만든다.

    Spring · Spring Framework · 컨테이너와 Bean
  2. SPEC

    BeanFactory가 repository와 inventory dependency를 먼저 resolve하고 constructor/factory method로 service target을 만든다.

    Spring · Spring Framework · 컨테이너와 Bean
  3. SPEC

    auto-proxy creator 역할의 BeanPostProcessor가 matching advisor를 찾아 proxy를 container에 반환한다.

    Spring · Spring AOP · 프록시 메커니즘
  4. PROJECT POLICY

    controller가 주입받는 reference의 target/proxy/advisor를 integration test artifact로 기록한다.

    Spring · Spring AOP · 프록시 메커니즘

실패 주입 실습

constructor circular dependency로 startup 중단

주입
OrderApplicationService와 InventoryService fixture가 서로를 constructor로 요구하게 한다.
명령
./gradlew failureTest
관찰
ApplicationContext 시작이 cycle report와 함께 실패하고 test는 root cause를 assertion한다.
복구
Order orchestration이 inventory port를 호출하는 단방향 dependency로 되돌린다.
PROJECT POLICY

고정 fixture는 cycle을 startup failure로 재현해야 한다.

Spring · Spring Framework · 컨테이너와 Bean

테스트 계약

unit

./gradlew unitTest

증명 범위: 격리된 도메인 규칙과 경계값

증명하지 않는 것: Spring wiring, 실제 DB, 네트워크 동작

integration

./gradlew integrationTest

증명 범위: 고정된 DB·Spring 조합의 실제 연결

증명하지 않는 것: 운영 데이터 분포와 장시간 부하

contract

./gradlew contractTest

증명 범위: HTTP·결제 adapter 경계의 요청/응답 계약

증명하지 않는 것: 상대 시스템의 가용성

load

k6 run -e SCENARIO=p03 scripts/load-test.js

증명 범위: 명시한 부하 모델에서 latency·saturation 변화

증명하지 않는 것: 다른 하드웨어·데이터·트래픽에서 같은 수치

p95/p99 · 자원 측정

PROJECT POLICY

모든 부하 결과에 p50/p95/p99, 오류율, 처리량과 함께 CPU, heap/RSS, GC pause, DB pool active/pending, executor active/queue를 같은 시간축으로 기록한다. 이 모듈의 초점은 bean count, startup steps, proxy invocation overhead다.

Micrometer · Micrometer Concepts
RECOMMENDED PRACTICE

평균만으로 gate를 통과시키지 않는다. warm-up, duration, concurrency, payload, DB row 수, CPU/memory limit을 결과와 함께 저장한다.

Micrometer · Micrometer Concepts

보안 경계

PROJECT POLICY

application context와 injected port에 대한 신뢰 경계를 표시하고 API key·payment token·DB credential은 코드, fixture, log, trace attribute에 넣지 않는다.

Spring · Spring Security · Servlet Architecture
RECOMMENDED PRACTICE

입력 검증과 권한 검사는 서로 대체하지 않는다. 인증된 주체, 허용된 행위, 대상 리소스 소유권을 각각 확인한다.

Spring · Spring Security · Servlet Architecture

log · metric · trace · profile

PROJECT POLICY

구조화 log에는 timestamp, level, service, traceId, orderId, outcome, error.type을 남기되 원문 body와 secret은 제외한다. 핵심 신호는 startup failure root cause, bean count, runtime target/proxy class다.

Spring · Spring Boot · Observability
RECOMMENDED PRACTICE

metric은 집계 가능한 상태, trace는 한 요청의 인과 경로, profile은 CPU·allocation 원인에 사용한다. 하나의 신호로 다른 신호를 흉내 내지 않는다.

Spring · Spring Boot · ObservabilityOpenTelemetry · OpenTelemetry JavaOracle · JDK Flight Recorder 런타임 가이드

실제 제출 산출물

  • bean dependency graph
  • lifecycle/proxy call trace
  • circular dependency failure fixture

기계적 완료 조건

  • context wiring
    ./gradlew integrationTest
    통과 기준: context와 proxy assertion 통과
  • cycle 재현
    ./gradlew failureTest
    통과 기준: 기대한 startup root cause 확인
  • field injection 금지
    rg -n '@Autowired[[:space:]]+(private|protected|public)' src/main/java
    통과 기준: 출력 없음

자가시험 · 답 보기

BeanDefinition과 bean instance 차이는?
Definition은 생성·scope·dependency metadata이고 instance는 그 recipe로 생성·post-process된 runtime object다.
self-invocation이 advisor를 건너뛰는 이유는?
target의 this.method()는 외부 proxy reference로 다시 들어가지 않기 때문이다.

P04모듈 04Spring Boot 내부main부터 ready event까지 조건 평가, property binding, embedded server와 probe 상태를 추적한다.

냉정한 실체

SPEC

Spring Boot auto-configuration은 classpath, bean, property 같은 조건이 맞을 때 configuration을 import한다. starter는 dependency 묶음이고 실행 주체가 아니다.

Spring · Spring Boot · Auto-configuration

흔한 오해

  • 오해: classpath에 starter가 있으면 필요한 bean이 무조건 생긴다.
  • 오해: profile이 설정 전체를 안전하게 격리한다.
  • 오해: process가 살아 있으면 readiness도 true다.

선수 지식

  • P03 ApplicationContext와 BeanDefinition
  • 환경 변수와 YAML 기본

예상 시간

2시간

이 모듈의 capstone 증분

검증된 외부 설정, startup evidence, probe와 graceful shutdown을 추가한다.

15분 개념 지도

JVM · Spring 내부 실행 과정

SPEC

SpringApplication은 Environment를 준비하고 ApplicationContext를 만들고 refresh한 뒤 runner와 ready event를 진행한다.

Spring · Spring Boot · Auto-configuration
SPEC

@ConditionalOnMissingBean 같은 조건은 user configuration을 존중해 auto-config bean을 back off할 수 있다. condition report가 선택 근거다.

Spring · Spring Boot · Auto-configuration
SPEC

외부 설정 source에는 정해진 우선순위가 있으며 뒤 source가 앞 값을 override할 수 있다. secret이 안전해지는 규칙은 아니다.

Spring · Spring Boot · 외부 설정
SPEC

graceful shutdown은 context close의 SmartLifecycle stop phase에서 기존 요청에 grace를 주고 새 요청을 거부한다.

Spring · Spring Boot · Graceful Shutdown
SPEC

startup probe는 느린 시작 동안 liveness failure가 restart loop를 만들지 않도록 초기 생존 판정을 지연한다. Boot가 자동 노출하는 liveness/readiness group과 platform의 startupProbe 설정은 별도 경계다.

Spring · Spring Boot Actuator · Kubernetes Probes

컴파일되는 최소 경로 발췌

src/main/java/dev/productionlab/commerce/config/CommerceProperties.javajava
@org.springframework.boot.context.properties.ConfigurationProperties("commerce")
@org.springframework.validation.annotation.Validated
record CommerceProperties(
    @jakarta.validation.Valid Security security,
    @jakarta.validation.Valid Payment payment,
    @jakarta.validation.Valid Outbox outbox) {
  record Payment(
      @jakarta.validation.constraints.NotBlank String baseUrl,
      @jakarta.validation.constraints.NotNull java.time.Duration connectTimeout,
      @jakarta.validation.constraints.NotNull java.time.Duration readTimeout,
      @jakarta.validation.constraints.Min(1) int maxConcurrent,
      @jakarta.validation.constraints.Min(1) int circuitFailureThreshold,
      @jakarta.validation.constraints.NotNull java.time.Duration circuitOpenDuration) {}
}

문자열 lookup을 흩뿌리지 않고 type, prefix, startup validation을 한 경계에 둔다.

RECOMMENDED PRACTICE

필수 operational setting은 binding 실패 시 startup을 중단한다.

Spring · Spring Boot · 외부 설정

컴파일되는 production 확장 발췌

src/main/resources/application.ymlyaml
spring:
  lifecycle:
    timeout-per-shutdown-phase: 20s
server:
  shutdown: graceful
commerce:
  payment:
    connect-timeout: 500ms
    read-timeout: 800ms
management:
  endpoint:
    health:
      probes:
        enabled: true

probe는 traffic routing 신호, shutdown timeout은 종료 budget이다. 값은 SLO와 platform termination grace보다 작게 검증한다.

PROJECT POLICY

connect 500ms/read 800ms/shutdown 20s는 capstone fixture용 값이며 production 측정으로 재설정한다.

Spring · Spring Boot Actuator · Kubernetes ProbesSpring · Spring Boot · Graceful Shutdown

중요 코드 해설

SPEC

@ConfigurationProperties는 relaxed binding을 지원하지만 알 수 없는/누락된 값 처리 정책은 validation과 type 설계에 달려 있다.

Spring · Spring Boot · 외부 설정
RECOMMENDED PRACTICE

liveness에 DB·payment를 넣어 dependency 장애가 전체 pod restart storm으로 번지게 하지 않는다.

Spring · Spring Boot Actuator · Kubernetes Probes

요청 하나의 end-to-end call trace

  1. SPEC

    main이 SpringApplication.run을 호출하고 command-line, system property, environment, file 등의 property source를 Environment에 결합한다.

    Spring · Spring Boot · 외부 설정
  2. SPEC

    auto-configuration import selector가 candidate를 찾고 condition outcome으로 적용/제외한다.

    Spring · Spring Boot · Auto-configuration
  3. PROJECT POLICY

    learner startup artifact는 embedded Tomcat connector 시작과 ApplicationReadyEvent 뒤 readiness 전환을 별도 timeline으로 기록한다. 현재 자동 test는 health 접근 제어만 검증한다.

    Spring · Spring Boot 4.1.0 시스템 요구사항Spring · Spring Boot Actuator · Kubernetes Probes
  4. SPEC

    SIGTERM은 context close와 graceful shutdown을 시작하며 grace 뒤 남은 lifecycle phase가 종료된다.

    Spring · Spring Boot · Graceful Shutdown

실패 주입 실습

잘못된 timeout 설정으로 startup 실패

주입
PAYMENT_READ_TIMEOUT=fast처럼 Duration으로 변환할 수 없는 값을 넣는다.
명령
./gradlew failureTest
관찰
binding origin과 property 이름을 포함한 startup failure가 assertion된다.
복구
유효한 unit 포함 값으로 복구하고 config validation test를 gate에 둔다.
VERIFIED BEHAVIOR

ConfigurationBindingFailureTest는 commerce.payment.read-timeout=fast를 넣고 ApplicationContextRunner startup failure의 property 이름을 assertion한다.

Spring · Spring Boot · 외부 설정

테스트 계약

unit

./gradlew unitTest

증명 범위: 격리된 도메인 규칙과 경계값

증명하지 않는 것: Spring wiring, 실제 DB, 네트워크 동작

integration

./gradlew integrationTest

증명 범위: 고정된 DB·Spring 조합의 실제 연결

증명하지 않는 것: 운영 데이터 분포와 장시간 부하

contract

./gradlew contractTest

증명 범위: HTTP·결제 adapter 경계의 요청/응답 계약

증명하지 않는 것: 상대 시스템의 가용성

load

k6 run -e SCENARIO=p04 scripts/load-test.js

증명 범위: 명시한 부하 모델에서 latency·saturation 변화

증명하지 않는 것: 다른 하드웨어·데이터·트래픽에서 같은 수치

p95/p99 · 자원 측정

PROJECT POLICY

모든 부하 결과에 p50/p95/p99, 오류율, 처리량과 함께 CPU, heap/RSS, GC pause, DB pool active/pending, executor active/queue를 같은 시간축으로 기록한다. 이 모듈의 초점은 startup duration, bean count, readiness transition, shutdown drain다.

Micrometer · Micrometer Concepts
RECOMMENDED PRACTICE

평균만으로 gate를 통과시키지 않는다. warm-up, duration, concurrency, payload, DB row 수, CPU/memory limit을 결과와 함께 저장한다.

Micrometer · Micrometer Concepts

보안 경계

PROJECT POLICY

Environment·Actuator endpoint·config origin에 대한 신뢰 경계를 표시하고 API key·payment token·DB credential은 코드, fixture, log, trace attribute에 넣지 않는다.

Spring · Spring Security · Servlet Architecture
RECOMMENDED PRACTICE

입력 검증과 권한 검사는 서로 대체하지 않는다. 인증된 주체, 허용된 행위, 대상 리소스 소유권을 각각 확인한다.

Spring · Spring Security · Servlet Architecture

log · metric · trace · profile

PROJECT POLICY

구조화 log에는 timestamp, level, service, traceId, orderId, outcome, error.type을 남기되 원문 body와 secret은 제외한다. 핵심 신호는 condition report, startup step, availability state, shutdown timeout다.

Spring · Spring Boot · Observability
RECOMMENDED PRACTICE

metric은 집계 가능한 상태, trace는 한 요청의 인과 경로, profile은 CPU·allocation 원인에 사용한다. 하나의 신호로 다른 신호를 흉내 내지 않는다.

Spring · Spring Boot · ObservabilityOpenTelemetry · OpenTelemetry JavaOracle · JDK Flight Recorder 런타임 가이드

실제 제출 산출물

  • startup sequence 그림
  • property precedence 실험 표
  • probe/shutdown runbook

기계적 완료 조건

  • 설정 binding
    ./gradlew failureTest --tests '*ConfigurationBindingFailureTest'
    통과 기준: invalid Duration이 startup 전에 거부되고 property 이름이 남음
  • probe contract
    ./gradlew contractTest
    통과 기준: public health detail은 숨고 admin credential에는 component가 노출됨
  • graceful 종료
    ./gradlew failureTest
    통과 기준: context close가 진행 HTTP 요청을 먼저 drain하고 제한 시간 안에 끝남

자가시험 · 답 보기

starter와 auto-configuration의 차이는?
starter는 dependency 묶음이고 auto-configuration은 조건부 configuration class다.
liveness와 readiness를 왜 나누나?
liveness는 restart 필요 여부, readiness는 새 traffic 수신 가능 여부다.

P05모듈 05HTTP 요청 전체 호출 경로socket에서 JSON response까지 filter·security·MVC·service·repository의 정확한 순서와 오류 계약을 추적한다.

냉정한 실체

SPEC

Servlet container가 request/response를 만들고 filter chain을 호출한 뒤 DispatcherServlet이 handler mapping, adapter, argument resolution, controller, exception resolution, message conversion을 조정한다.

Jakarta · Jakarta Servlet · FilterSpring · Spring MVC · DispatcherServlet

흔한 오해

  • 오해: controller가 HTTP 요청의 첫 Java 코드다.
  • 오해: authentication 성공이 resource authorization도 뜻한다.
  • 오해: client timeout이면 server 작업도 반드시 취소된다.

선수 지식

  • P03 proxy와 P04 embedded server
  • HTTP method/status/header/JSON

예상 시간

3시간

이 모듈의 capstone 증분

인증·validation·idempotency·error contract를 갖춘 주문 HTTP boundary를 완성한다.

15분 개념 지도

JVM · Spring 내부 실행 과정

SPEC

Servlet Filter는 DispatcherServlet 앞/뒤를 감싸고 Spring Security는 FilterChainProxy 내부에서 선택된 SecurityFilterChain을 실행한다.

Jakarta · Jakarta Servlet · FilterSpring · Spring Security · Servlet Architecture
SPEC

HandlerMethodArgumentResolver가 header/body/path 값을 parameter로 만들고 validation 실패는 controller body 진입 전에 exception resolution으로 갈 수 있다.

Spring · Spring MVC · DispatcherServlet
SPEC

HandlerExceptionResolver와 message converter가 status/header/body를 만들며 response가 commit된 뒤에는 error body를 안전하게 바꿀 수 없다.

Spring · Spring MVC · DispatcherServlet
SPEC

client timeout·socket disconnect는 server transaction이나 payment call의 cancellation을 보장하지 않는다. request thread가 write 실패를 늦게 관찰할 수 있으므로 server-side deadline, idempotency, 명시적 cancellation propagation을 각각 검증한다.

Jakarta · Jakarta Servlet · FilterSpring · Spring MVC · DispatcherServlet
RECOMMENDED PRACTICE

API version은 URL/header/media type 중 하나를 명시적으로 고르고 pagination cursor는 stable ordering key를 포함한다.

Spring · Spring MVC · DispatcherServlet

컴파일되는 최소 경로 발췌

src/main/java/dev/productionlab/commerce/order/OrderController.javajava
@org.springframework.web.bind.annotation.RestController
@org.springframework.web.bind.annotation.RequestMapping("/api/v1/orders")
public final class OrderController {
  private final OrderApplicationService service;
  public OrderController(OrderApplicationService service) { this.service = service; }

  @org.springframework.web.bind.annotation.PostMapping
  org.springframework.http.ResponseEntity<OrderResponse> create(
      @org.springframework.web.bind.annotation.RequestHeader("Idempotency-Key") String key,
      @jakarta.validation.Valid @org.springframework.web.bind.annotation.RequestBody CreateOrderRequest body) {
    var response = service.create(key, body);
    return org.springframework.http.ResponseEntity.accepted()
        .location(java.net.URI.create("/api/v1/orders/" + response.id()))
        .body(response);
  }
}

version, idempotency key, validation, status를 HTTP boundary에 드러내고 business orchestration은 service에 둔다.

PROJECT POLICY

POST /api/v1/orders는 API key 인증과 100자 이하 Idempotency-Key를 요구한다.

Spring · Spring Security · Servlet Architecture

컴파일되는 production 확장 발췌

src/main/java/dev/productionlab/commerce/common/ApiExceptionHandler.javajava
@org.springframework.web.bind.annotation.RestControllerAdvice
public final class ApiExceptionHandler {
  @org.springframework.web.bind.annotation.ExceptionHandler(ConflictException.class)
  org.springframework.http.ResponseEntity<org.springframework.http.ProblemDetail> conflict(
      ConflictException exception, jakarta.servlet.http.HttpServletRequest request) {
    return problem(org.springframework.http.HttpStatus.CONFLICT,
        "conflict", exception.getMessage(), request);
  }
}

외부 계약은 안정된 status/code/title이며 내부 stack·SQL·secret을 response에 넣지 않는다.

RECOMMENDED PRACTICE

exception taxonomy와 외부 error code를 분리한다.

Spring · Spring MVC · DispatcherServlet

중요 코드 해설

SPEC

@Valid는 Bean Validation provider와 MVC argument resolution을 통해 body constraint를 평가한다.

Spring · Spring MVC · DispatcherServlet
PROJECT POLICY

같은 key+같은 request hash는 기존 결과, 같은 key+다른 hash는 409를 반환한다.

요청 하나의 end-to-end call trace

  1. SPEC

    Tomcat connector가 socket bytes를 HTTP request로 parse하고 worker thread가 servlet filter chain에 진입한다.

    Jakarta · Jakarta Servlet · FilterSpring · Spring Boot 4.1.0 시스템 요구사항
  2. PROJECT POLICY

    CorrelationIdFilter는 유효한 correlation ID를 보존/생성하고 ApiKeyAuthenticationFilter는 principal을 만들어야 한다.

    Spring · Spring Security · Servlet Architecture
  3. SPEC

    DispatcherServlet이 OrderController method를 찾고 Idempotency-Key/body를 resolve·validate한 뒤 service proxy를 호출한다.

    Spring · Spring MVC · DispatcherServlet
  4. PROJECT POLICY

    비동기 payment를 시작한 성공은 202 JSON, 동일 replay는 같은 order, hash conflict는 409 ProblemDetail이어야 한다.

    Spring · Spring MVC · DispatcherServlet

실패 주입 실습

동시 중복 주문

주입
같은 Idempotency-Key로 동일 POST를 barrier 뒤에서 동시에 보낸다.
명령
./gradlew concurrencyTest
관찰
한 order row만 생성되고 모든 성공 응답이 같은 orderId를 가진다.
복구
DB unique constraint와 request hash 검사, conflict 처리로 atomicity를 보장한다.
PROJECT POLICY

concurrency fixture는 check-then-act 구현을 깨고 constraint 기반 구현만 통과시켜야 한다.

테스트 계약

unit

./gradlew unitTest

증명 범위: 격리된 도메인 규칙과 경계값

증명하지 않는 것: Spring wiring, 실제 DB, 네트워크 동작

integration

./gradlew integrationTest

증명 범위: 고정된 DB·Spring 조합의 실제 연결

증명하지 않는 것: 운영 데이터 분포와 장시간 부하

contract

./gradlew contractTest

증명 범위: HTTP·결제 adapter 경계의 요청/응답 계약

증명하지 않는 것: 상대 시스템의 가용성

load

k6 run -e SCENARIO=p05 scripts/load-test.js

증명 범위: 명시한 부하 모델에서 latency·saturation 변화

증명하지 않는 것: 다른 하드웨어·데이터·트래픽에서 같은 수치

p95/p99 · 자원 측정

PROJECT POLICY

모든 부하 결과에 p50/p95/p99, 오류율, 처리량과 함께 CPU, heap/RSS, GC pause, DB pool active/pending, executor active/queue를 같은 시간축으로 기록한다. 이 모듈의 초점은 HTTP p95/p99, Tomcat threads, request/response bytes, disconnects다.

Micrometer · Micrometer Concepts
RECOMMENDED PRACTICE

평균만으로 gate를 통과시키지 않는다. warm-up, duration, concurrency, payload, DB row 수, CPU/memory limit을 결과와 함께 저장한다.

Micrometer · Micrometer Concepts

보안 경계

PROJECT POLICY

public HTTP boundary·API key·order ownership에 대한 신뢰 경계를 표시하고 API key·payment token·DB credential은 코드, fixture, log, trace attribute에 넣지 않는다.

Spring · Spring Security · Servlet Architecture
RECOMMENDED PRACTICE

입력 검증과 권한 검사는 서로 대체하지 않는다. 인증된 주체, 허용된 행위, 대상 리소스 소유권을 각각 확인한다.

Spring · Spring Security · Servlet Architecture

log · metric · trace · profile

PROJECT POLICY

구조화 log에는 timestamp, level, service, traceId, orderId, outcome, error.type을 남기되 원문 body와 secret은 제외한다. 핵심 신호는 http.server.requests, status/error.code, traceId/orderId correlation다.

Spring · Spring Boot · Observability
RECOMMENDED PRACTICE

metric은 집계 가능한 상태, trace는 한 요청의 인과 경로, profile은 CPU·allocation 원인에 사용한다. 하나의 신호로 다른 신호를 흉내 내지 않는다.

Spring · Spring Boot · ObservabilityOpenTelemetry · OpenTelemetry JavaOracle · JDK Flight Recorder 런타임 가이드

실제 제출 산출물

  • socket→response sequence diagram
  • OpenAPI/error/idempotency contract
  • 동시 duplicate fixture

기계적 완료 조건

  • HTTP contract
    ./gradlew contractTest
    통과 기준: 202/400/401/403/409와 schema assertion 통과
  • 중복 방지
    ./gradlew concurrencyTest
    통과 기준: 한 row·한 orderId
  • 민감정보 응답 검사
    ./gradlew failureTest
    통과 기준: stack·SQL·API key가 body/header에 없음

자가시험 · 답 보기

Filter와 HandlerInterceptor의 핵심 경계는?
Filter는 servlet/DispatcherServlet 밖을 감싸고 interceptor는 선택된 MVC handler 실행 전후에 동작한다.
client timeout 뒤 server state를 왜 조회해야 하나?
network 응답 실패는 server commit 실패를 뜻하지 않는다. idempotency key로 결과를 재조회/재시도해야 한다.

P06모듈 06Transaction과 JPA/Hibernate주문·재고·outbox의 한 원자 경계와 외부 결제의 불확실한 경계를 분리하고 flush 시점까지 설명한다.

냉정한 실체

SPEC

선언적 transaction은 proxy를 통과하는 method invocation에 transaction interceptor를 적용한다. persistence context는 managed entity의 identity와 변경을 추적하고 flush에서 SQL과 constraint 결과가 드러날 수 있다.

Spring · Spring · 선언적 트랜잭션Hibernate · Hibernate ORM · Persistence Context

흔한 오해

  • 오해: @Transactional이 붙은 모든 호출은 transaction 안이다.
  • 오해: save 호출 즉시 SQL과 commit이 끝난다.
  • 오해: DB transaction 안에서 payment HTTP를 호출하면 둘이 원자적이다.

선수 지식

  • P03 proxy/self-invocation
  • SQL transaction·index·constraint 기본

예상 시간

4시간

이 모듈의 capstone 증분

order+inventory+outbox local transaction과 payment UNKNOWN recovery 경계를 완성한다.

15분 개념 지도

JVM · Spring 내부 실행 과정

SPEC

TransactionInterceptor가 propagation에 따라 기존 transaction에 참여하거나 새 status를 만들고 정상/예외 반환 규칙에 따라 commit 또는 rollback을 요청한다.

Spring · Spring · 선언적 트랜잭션Spring · Spring · 롤백 규칙
SPEC

기본 declarative rollback 규칙은 RuntimeException/Error이며 checked exception은 명시 rule 없이 자동 rollback되지 않는다.

Spring · Spring · 롤백 규칙
SPEC

같은 persistence context에서 같은 entity identity는 같은 managed instance로 대응되고 dirty checking은 flush 전에 변경을 계산한다.

Hibernate · Hibernate ORM · Persistence Context
SPEC

optimistic lock은 version 충돌을 감지하고 pessimistic lock은 database lock을 요청한다. 어느 쪽도 잘못된 business invariant를 대신 정의하지 않는다.

Hibernate · Hibernate ORM · Locking

컴파일되는 최소 경로 발췌

src/main/java/dev/productionlab/commerce/order/OrderApplicationService.javajava
InventoryService.Reservation reservation =
    inventoryService.reserve(request.sku(), request.quantity());
CommerceOrder order =
    new CommerceOrder(
        idempotencyKey,
        reservation.sku(),
        reservation.quantity(),
        reservation.unitPrice(),
        reservation.currency().getCurrencyCode(),
        now);
orderRepository.save(order);

Payment payment =
    new Payment(
        order.id(),
        reservation.total(),
        reservation.currency().getCurrencyCode(),
        now);
paymentRepository.save(payment);
String payload =
    "{\"orderId\":\"%s\",\"paymentId\":\"%s\","
        + "\"amount\":\"%s\",\"currency\":\"%s\"}"
            .formatted(
                order.id(), payment.id(), payment.amount(), payment.currency());
outboxRepository.save(
    new OutboxEvent(
        "ORDER", order.id(), "PAYMENT_REQUESTED", payload,
        MDC.get("correlationId"), now));
orderRepository.flush();
idempotencyStore.complete(idempotencyKey, order.id(), now);

idempotency, inventory reservation, order, payment intent, outbox row를 같은 local DB transaction에 둔다. payment network는 이 method 밖이다.

RECOMMENDED PRACTICE

DB constraint를 최종 invariant로 두고 application check는 친절한 오류와 빠른 경로에 사용한다.

Spring · Spring · 선언적 트랜잭션

컴파일되는 production 확장 발췌

src/main/java/dev/productionlab/commerce/inventory/InventoryItem.javajava
@jakarta.persistence.Entity
class InventoryItem {
  @jakarta.persistence.Id private java.util.UUID id;
  @jakarta.persistence.Column(nullable = false, unique = true, length = 64)
  private String sku;
  private int available;
  @jakarta.persistence.Version private long version;
  private java.time.Instant updatedAt;

  public void reserve(int quantity, java.time.Instant now) {
    if (quantity <= 0) throw new IllegalArgumentException("quantity must be positive");
    if (available < quantity) {
      throw new BusinessRuleException("insufficient inventory for sku " + sku);
    }
    available -= quantity;
    updatedAt = now;
  }
}

@Version update count가 0이면 concurrent write 충돌이다. retry는 새 transaction에서 invariant를 다시 읽고 제한적으로 수행한다.

SPEC

version field는 optimistic lock value로 사용된다.

Hibernate · Hibernate ORM · Locking

중요 코드 해설

SPEC

self-invocation으로 create를 부르면 caller가 proxy 밖에 있어 transaction interceptor가 실행되지 않을 수 있다.

Spring · Spring AOP · 프록시 메커니즘Spring · Spring · 선언적 트랜잭션
RECOMMENDED PRACTICE

LAZY association은 transaction 밖 serialization에 기대지 말고 query별 DTO projection/fetch plan을 명시한다.

Hibernate · Hibernate ORM · Fetching
PROJECT POLICY

payment UNKNOWN은 실패와 구분하고 recovery가 idempotency key로 provider 상태를 조회한다.

요청 하나의 end-to-end call trace

  1. SPEC

    controller가 service proxy를 호출하면 transaction interceptor가 connection-bound transaction을 시작한다.

    Spring · Spring · 선언적 트랜잭션
  2. SPEC

    repository query가 persistence context/DB에서 entity를 managed state로 가져오고 inventory mutation은 dirty checking 대상이 된다.

    Hibernate · Hibernate ORM · Persistence Context
  3. PROJECT POLICY

    flush에서 inventory version update, order insert, outbox insert와 unique/FK/check constraint가 확인되고 그 뒤 local commit한다.

  4. RECOMMENDED PRACTICE

    별도 relay transaction이 outbox를 claim하고 payment adapter를 호출하며 timeout이면 UNKNOWN으로 남겨 조회 기반 recovery를 예약한다.

    Spring · Spring · 선언적 트랜잭션

실패 주입 실습

transaction/JPA 실패 gauntlet

주입
fixture가 self-invocation, checked exception, N+1, session 종료 뒤 lazy 접근, pool 고갈, optimistic conflict, deadlock, payment timeout-after-success를 각각 켠다.
명령
./gradlew failureTest
관찰
각 fixture는 실패 증상·SQL/connection 수·최종 DB state를 명시한 assertion으로 끝난다.
복구
proxy 경계, rollbackFor 또는 exception 정책, fetch plan, 짧은 transaction, bounded retry, outbox/recovery를 각각 적용한다.
PROJECT POLICY

실패 test 전체가 release gate에 포함되어야 VERIFIED BEHAVIOR로 승격한다.

Spring · Spring · 선언적 트랜잭션Hibernate · Hibernate ORM · FetchingHibernate · Hibernate ORM · Locking

테스트 계약

unit

./gradlew unitTest

증명 범위: 격리된 도메인 규칙과 경계값

증명하지 않는 것: Spring wiring, 실제 DB, 네트워크 동작

integration

./gradlew integrationTest

증명 범위: 고정된 DB·Spring 조합의 실제 연결

증명하지 않는 것: 운영 데이터 분포와 장시간 부하

contract

./gradlew contractTest

증명 범위: HTTP·결제 adapter 경계의 요청/응답 계약

증명하지 않는 것: 상대 시스템의 가용성

load

k6 run -e SCENARIO=p06 scripts/load-test.js

증명 범위: 명시한 부하 모델에서 latency·saturation 변화

증명하지 않는 것: 다른 하드웨어·데이터·트래픽에서 같은 수치

p95/p99 · 자원 측정

PROJECT POLICY

모든 부하 결과에 p50/p95/p99, 오류율, 처리량과 함께 CPU, heap/RSS, GC pause, DB pool active/pending, executor active/queue를 같은 시간축으로 기록한다. 이 모듈의 초점은 SQL count/duration, flush batches, DB pool pending, lock wait/deadlock다.

Micrometer · Micrometer Concepts
RECOMMENDED PRACTICE

평균만으로 gate를 통과시키지 않는다. warm-up, duration, concurrency, payload, DB row 수, CPU/memory limit을 결과와 함께 저장한다.

Micrometer · Micrometer Concepts

보안 경계

PROJECT POLICY

entity/tenant ownership·DB credential·payment token에 대한 신뢰 경계를 표시하고 API key·payment token·DB credential은 코드, fixture, log, trace attribute에 넣지 않는다.

Spring · Spring Security · Servlet Architecture
RECOMMENDED PRACTICE

입력 검증과 권한 검사는 서로 대체하지 않는다. 인증된 주체, 허용된 행위, 대상 리소스 소유권을 각각 확인한다.

Spring · Spring Security · Servlet Architecture

log · metric · trace · profile

PROJECT POLICY

구조화 log에는 timestamp, level, service, traceId, orderId, outcome, error.type을 남기되 원문 body와 secret은 제외한다. 핵심 신호는 transaction duration, query count, pool acquire, outbox age, payment outcome다.

Spring · Spring Boot · Observability
RECOMMENDED PRACTICE

metric은 집계 가능한 상태, trace는 한 요청의 인과 경로, profile은 CPU·allocation 원인에 사용한다. 하나의 신호로 다른 신호를 흉내 내지 않는다.

Spring · Spring Boot · ObservabilityOpenTelemetry · OpenTelemetry JavaOracle · JDK Flight Recorder 런타임 가이드

실제 제출 산출물

  • transaction/proxy/flush sequence
  • outbox schema와 recovery state machine
  • 8종 failure evidence

기계적 완료 조건

  • transaction/JPA integration
    ./gradlew integrationTest
    통과 기준: 실제 DB에서 constraint·flush·outbox assertion 통과
  • 동시성
    ./gradlew concurrencyTest
    통과 기준: optimistic conflict·idempotency에서 oversell/duplicate 없음
  • 강제 실패
    ./gradlew failureTest
    통과 기준: self-invocation·rollback·N+1·lazy·pool·deadlock·timeout fixture 통과

자가시험 · 답 보기

flush와 commit의 차이는?
flush는 persistence context 변경을 SQL로 동기화하지만 transaction을 끝내지 않는다. commit은 transaction 성공을 확정하며 그 과정에서 flush될 수 있다.
payment timeout을 FAILED로 바로 두면 안 되는 이유는?
provider가 charge 뒤 response 전달에 실패했을 수 있다. UNKNOWN을 유지하고 같은 idempotency key로 조회/보상해야 한다.

P07모듈 07Spring MVC와 reactive같은 seeded order GET workload를 MVC와 WebFlux/R2DBC로 측정해 thread·event loop·connection 경계를 근거로 선택한다.

냉정한 실체

SPEC

Spring MVC는 Servlet blocking contract 위에서 보통 request thread가 완료까지 흐른다. WebFlux는 non-blocking runtime과 reactive chain을 지원하지만 blocking call을 넣으면 event-loop 자원을 막을 수 있다.

Spring · Spring MVC · DispatcherServletSpring · Spring WebFlux · Reactive Core

흔한 오해

  • 오해: reactive는 같은 workload에서 무조건 더 빠르다.
  • 오해: Mono를 반환하면 JDBC도 non-blocking이 된다.
  • 오해: backpressure가 외부 결제나 DB capacity를 자동 보호한다.

선수 지식

  • P05 MVC path와 P06 DB pool
  • callback, Publisher/Subscriber 기본

예상 시간

2시간 30분

이 모듈의 capstone 증분

commerce-lab의 MVC baseline과 격리된 reactive 비교 harness를 만든다.

15분 개념 지도

JVM · Spring 내부 실행 과정

SPEC

MVC DispatcherServlet은 Servlet request lifecycle에서 handler를 호출하며 blocking repository/client와 직접 맞는다.

Spring · Spring MVC · DispatcherServlet
SPEC

Reactor Publisher는 subscription 전에는 pipeline description이며 demand와 onNext/onError/onComplete signal로 실행된다.

Project Reactor · Reactor Core Reference
SPEC

WebFlux는 작은 고정 event-loop를 전제하므로 blocking call은 감지·격리하거나 non-blocking driver로 끝까지 연결해야 한다.

Spring · Spring WebFlux · Reactive Core
RECOMMENDED PRACTICE

JPA/JDBC 중심 modular monolith는 MVC가 더 단순하고 안전한 기본값이다. 높은 fan-out I/O와 non-blocking dependency가 입증될 때 WebFlux를 비교한다.

Spring · Spring WebFlux · Reactive Core

컴파일되는 최소 경로 발췌

src/main/java/dev/productionlab/commerce/order/OrderController.javajava
@org.springframework.web.bind.annotation.RestController
@org.springframework.web.bind.annotation.RequestMapping("/api/v1/orders")
final class OrderController {
  private final OrderApplicationService service;

  @org.springframework.web.bind.annotation.GetMapping("/{orderId}")
  OrderResponse get(
      @org.springframework.web.bind.annotation.PathVariable java.util.UUID orderId) {
    return service.get(orderId);
  }
}

JPA-backed 동기 application service를 호출하는 MVC baseline이다.

PROJECT POLICY

capstone production 경로는 측정 전 MVC를 baseline으로 유지한다.

Spring · Spring MVC · DispatcherServlet

컴파일되는 production 확장 발췌

reactive-comparison/src/main/java/dev/productionlab/comparison/order/ReactiveOrderRepository.javajava
public Mono<OrderResponse> findById(UUID orderId) {
  return databaseClient.sql(FIND_ORDER)
      .bind("orderId", orderId)
      .map((row, metadata) -> mapRow(row))
      .all()
      .collectList()
      .filter(rows -> !rows.isEmpty())
      .map(this::toResponse);
}

독립 build가 기존 commerce schema를 한 번의 joined R2DBC query로 읽는다. 쓰기 경로를 이식하지 않으며 MVC보다 빠르다는 결론도 내리지 않는다.

RECOMMENDED PRACTICE

동일 contract·row·connection cap·자원 제한으로 read path를 격리하고, 측정 결과를 write-heavy transaction 경로로 일반화하지 않는다.

Spring · Spring WebFlux · Reactive CoreProject Reactor · Reactor Core Reference

중요 코드 해설

SPEC

fromCallable은 subscription 때 callable을 실행하고 subscribeOn이 subscription/work 실행 scheduler를 지정한다.

Project Reactor · Reactor Core Reference
RECOMMENDED PRACTICE

MDC/ThreadLocal correlation은 scheduler hop에서 자동 보존을 가정하지 말고 Reactor Context/관측 library integration을 test한다.

Spring · Spring Boot · Observability

요청 하나의 end-to-end call trace

  1. SPEC

    MVC: Tomcat worker → controller → transaction proxy → JDBC wait → serializer → worker 반환.

    Spring · Spring MVC · DispatcherServlet
  2. SPEC

    WebFlux: event loop → handler Publisher → subscribe → non-blocking signals → encoder; blocking bridge가 있으면 별도 scheduler hop이 추가된다.

    Spring · Spring WebFlux · Reactive CoreProject Reactor · Reactor Core Reference
  3. PROJECT POLICY

    비교 fixture는 동일 endpoint semantics, payload, DB rows, connection cap, CPU/memory limit, warm-up을 사용한다.

  4. RECOMMENDED PRACTICE

    throughput만 보고 선택하지 않고 p99, RSS, queue saturation, cancellation, trace continuity, 코드 복잡도를 함께 판정한다.

    Spring · Spring WebFlux · Reactive Core

실패 주입 실습

single event loop 위의 blocking call

주입
단일 Reactor scheduler의 첫 task를 250ms block하고 바로 뒤 marker signal의 지연을 잰다.
명령
./gradlew -p reactive-comparison test --tests '*BlockingBoundaryFailureTest'
관찰
뒤 marker가 최소 150ms 밀려 single event-loop의 head-of-line blocking을 재현한다.
복구
MVC 유지, bounded bridge, 또는 실제 non-blocking driver 중 전체 비용을 비교해 선택한다.
PROJECT POLICY

동일 workload 비교 결과를 저장한 뒤에만 선택을 VERIFIED BEHAVIOR로 기술한다.

Spring · Spring WebFlux · Reactive Core

테스트 계약

unit

./gradlew -p reactive-comparison test --tests '*BlockingBoundaryFailureTest'

증명 범위: single scheduler에서 blocking task 뒤 signal 지연

증명하지 않는 것: production event-loop 지연 분포 또는 BlockHound coverage

integration

./gradlew -p reactive-comparison --dependency-verification strict clean check

증명 범위: 고정된 WebFlux·R2DBC·PostgreSQL 조합의 실제 기동과 read-only transaction

증명하지 않는 것: MVC 대비 latency·memory 우위

contract

./gradlew -p reactive-comparison test --tests '*OrderHttpContractIntegrationTest'

증명 범위: 동일 order GET schema, 인증, 404/405, DB read-only 경계

증명하지 않는 것: write path 또는 payment workflow 호환성

load

k6 run -e TARGET=mvc -e ORDER_ID=<uuid> -e API_KEY=<key> scripts/p07-comparison.js

증명 범위: 동일 seeded GET workload의 환경·error·p95/p99 JSON

증명하지 않는 것: 다른 workload나 write-heavy 서비스의 기술 선택

p95/p99 · 자원 측정

PROJECT POLICY

모든 부하 결과에 p50/p95/p99, 오류율, 처리량과 함께 CPU, heap/RSS, GC pause, DB pool active/pending, executor active/queue를 같은 시간축으로 기록한다. 이 모듈의 초점은 MVC threads vs event-loop delay, scheduler queue, p99, RSS다.

Micrometer · Micrometer Concepts
RECOMMENDED PRACTICE

평균만으로 gate를 통과시키지 않는다. warm-up, duration, concurrency, payload, DB row 수, CPU/memory limit을 결과와 함께 저장한다.

Micrometer · Micrometer Concepts

보안 경계

PROJECT POLICY

reactive Context·principal·cancellation boundary에 대한 신뢰 경계를 표시하고 API key·payment token·DB credential은 코드, fixture, log, trace attribute에 넣지 않는다.

Spring · Spring Security · Servlet Architecture
RECOMMENDED PRACTICE

입력 검증과 권한 검사는 서로 대체하지 않는다. 인증된 주체, 허용된 행위, 대상 리소스 소유권을 각각 확인한다.

Spring · Spring Security · Servlet Architecture

log · metric · trace · profile

PROJECT POLICY

구조화 log에는 timestamp, level, service, traceId, orderId, outcome, error.type을 남기되 원문 body와 secret은 제외한다. 핵심 신호는 event-loop delay, scheduler pending, context/trace continuity다.

Spring · Spring Boot · Observability
RECOMMENDED PRACTICE

metric은 집계 가능한 상태, trace는 한 요청의 인과 경로, profile은 CPU·allocation 원인에 사용한다. 하나의 신호로 다른 신호를 흉내 내지 않는다.

Spring · Spring Boot · ObservabilityOpenTelemetry · OpenTelemetry JavaOracle · JDK Flight Recorder 런타임 가이드

실제 제출 산출물

  • MVC/WebFlux execution diagrams
  • same-workload result table
  • technology decision ADR

기계적 완료 조건

  • 동일 contract
    ./gradlew contractTest
    통과 기준: 현재 MVC HTTP와 payment adapter 계약이 통과
  • blocking 감지
    ./gradlew -p reactive-comparison test --tests '*BlockingBoundaryFailureTest'
    통과 기준: single scheduler marker가 지정된 최소 지연 뒤 실행
  • 부하 비교
    k6 run -e TARGET=reactive -e ORDER_ID=<uuid> -e API_KEY=<key> scripts/p07-comparison.js
    통과 기준: 같은 commit·row·rate·자원 제한의 MVC/reactive JSON 두 개가 생성됨

자가시험 · 답 보기

Mono.fromCallable+boundedElastic이 JDBC를 reactive로 만드는가?
아니다. blocking 작업을 bounded worker로 옮기는 bridge다.
MVC가 더 안전한 선택인 조건은?
dependency가 blocking이고 traffic이 thread/pool budget 안이며 팀의 debugging·transaction model이 MVC에 맞을 때다.

P08모듈 08동시성·비동기·복구outbox relay와 payment recovery의 pool·queue·context·cancellation·retry budget을 명시하고 폭주를 재현한다.

냉정한 실체

SPEC

Executor는 task 제출과 실행을 분리하고 ExecutorService는 lifecycle을 갖는다. virtual thread는 값싼 대기 단위를 제공하지만 DB connection, remote rate limit, CPU capacity를 늘리지 않는다.

Oracle · ExecutorService APIOpenJDK · JEP 444 · Virtual Threads (Final)

흔한 오해

  • 오해: unbounded queue는 rejection을 없애 안전하다.
  • 오해: retry는 성공률만 높이고 비용은 없다.
  • 오해: Java 21 StructuredTaskScope는 일반 production API다.

선수 지식

  • P06 transaction/outbox와 P07 thread model
  • race·lock·future 기본

예상 시간

3시간 30분

이 모듈의 capstone 증분

bounded outbox execution, payment recovery, retry/circuit/bulkhead와 graceful drain을 추가한다.

15분 개념 지도

JVM · Spring 내부 실행 과정

SPEC

ThreadPoolExecutor는 core/max worker, work queue, keep-alive, rejection policy 조합으로 admission을 결정한다.

Oracle · ExecutorService API
SPEC

CompletableFuture의 async stage는 executor를 명시하지 않으면 default asynchronous facility를 쓸 수 있으므로 blocking workload가 shared pool을 점유하지 않게 한다.

Oracle · ExecutorService API
SPEC

Structured concurrency는 Java 21에서 preview였고 JDK 26에서도 sixth preview다. 이 Java 21 baseline은 --enable-preview 없이 production dependency로 사용하지 않는다.

OpenJDK · JEP 453 · Structured Concurrency (Preview)OpenJDK · JEP 525 · Structured Concurrency (Sixth Preview)
SPEC

Java 21 virtual thread는 일부 blocking operation에서 carrier pinning을 겪을 수 있다. JDK 24의 JEP 491이 synchronized 관련 pinning 대부분을 제거했으므로 JDK별 결과를 섞지 않는다.

OpenJDK · JEP 491 · synchronized에서 Virtual Thread Pinning 제거OpenJDK · JEP 444 · Virtual Threads (Final)

컴파일되는 최소 경로 발췌

src/main/java/dev/productionlab/commerce/config/AsyncConfiguration.javajava
@org.springframework.context.annotation.Bean("outboxTaskExecutor")
org.springframework.core.task.TaskExecutor outboxTaskExecutor() {
  var executor = new org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor();
  executor.setCorePoolSize(2);
  executor.setMaxPoolSize(4);
  executor.setQueueCapacity(32);
  executor.setWaitForTasksToCompleteOnShutdown(true);
  executor.setAwaitTerminationSeconds(15);
  executor.setRejectedExecutionHandler(
      new java.util.concurrent.ThreadPoolExecutor.AbortPolicy());
  executor.initialize();
  return executor;
}

core 2, max 4, queue 32, rejection과 15초 drain을 숨기지 않는다. 이 수치는 throughput/latency/DB·payment cap으로 다시 계산한다.

PROJECT POLICY

outbox core=2, max=4, queue=32는 saturation fixture를 위한 현재 시작값이다.

Oracle · ExecutorService API

컴파일되는 production 확장 발췌

src/main/java/dev/productionlab/commerce/payment/PaymentOutboxProcessor.javajava
PaymentGateway.Result result;
if (payment.status() == PaymentStatus.UNKNOWN) {
  result = gateway.findByPaymentId(payment.id());
} else if (payment.status() == PaymentStatus.PENDING) {
  result = gateway.charge(
      payment.id(), payment.orderId(), payment.amount(), payment.currency());
} else {
  throw new IllegalStateException("terminal payment cannot be retried");
}
recovery.recover(event.id(), result);

timeout 뒤 재청구부터 하지 않고 provider 상태를 조회한다. attempt와 nextAt은 durable state여야 process restart를 견딘다.

RECOMMENDED PRACTICE

복구는 idempotent query/command와 durable state를 결합하고 retry budget을 전체 요청 chain에 둔다.

Oracle · ExecutorService API

중요 코드 해설

SPEC

AbortPolicy는 saturation을 RejectedExecutionException으로 caller에 드러낸다. CallerRunsPolicy는 backpressure처럼 보이지만 caller thread latency와 deadlock risk를 바꾼다.

Oracle · ExecutorService API
RECOMMENDED PRACTICE

ThreadLocal transaction/security state를 암묵적으로 복사하지 말고 필요한 principal/trace/business key만 명시적으로 전달한다.

Spring · Spring Boot · Task Execution and Scheduling
PROJECT POLICY

현재 fixture는 최대 8 attempts, 2초 base의 capped exponential delay+deterministic jitter를 사용하고 소진 시 DEAD metric/error log를 낸다. 총 wall-clock budget은 아직 learner가 SLO로 결정해야 한다.

요청 하나의 end-to-end call trace

  1. PROJECT POLICY

    OutboxRelay가 transaction 안에서 due row를 제한 수 claim한 뒤 commit하고 executor에 제출한다.

  2. SPEC

    worker가 payment task를 실행하고 Future는 success/exception/cancel 중 하나로 완료된다.

    Oracle · ExecutorService API
  3. RECOMMENDED PRACTICE

    timeout/circuit-open은 order를 UNKNOWN으로 보존하고 attempt/nextAt/error class를 저장해 scheduler가 재개한다.

    Oracle · ExecutorService API
  4. SPEC

    shutdown은 새 제출을 막고 진행 task 완료를 기다린 뒤 deadline에서 강제 취소한다. interrupt는 협력적 신호라 code가 처리해야 한다.

    Oracle · ExecutorService API

실패 주입 실습

queue saturation과 retry storm

주입
bounded executor를 포화시키고 32개 retry event를 같은 시각에 release하며 한 event는 attempt budget을 끝까지 소진한다.
명령
./gradlew failureTest && ./gradlew concurrencyTest
관찰
rejection이 caller에 드러나고 retry cohort가 여러 nextAttempt 시각으로 분산되며 budget 소진 event는 DEAD가 된다. p99/outbox-age는 별도 load artifact다.
복구
bounded admission, jitter, retry budget, circuit breaker, bulkhead, durable reschedule로 폭주를 끊는다.
VERIFIED BEHAVIOR

ThreadPoolSaturationFailureTest, RetryStormFailureTest, OutboxEventTest가 rejection·retry 분산·DEAD budget을 각각 assertion한다.

테스트 계약

unit

./gradlew unitTest

증명 범위: 격리된 도메인 규칙과 경계값

증명하지 않는 것: Spring wiring, 실제 DB, 네트워크 동작

integration

./gradlew integrationTest

증명 범위: 고정된 DB·Spring 조합의 실제 연결

증명하지 않는 것: 운영 데이터 분포와 장시간 부하

contract

./gradlew contractTest

증명 범위: HTTP·결제 adapter 경계의 요청/응답 계약

증명하지 않는 것: 상대 시스템의 가용성

load

k6 run -e SCENARIO=p08 scripts/load-test.js

증명 범위: 명시한 부하 모델에서 latency·saturation 변화

증명하지 않는 것: 다른 하드웨어·데이터·트래픽에서 같은 수치

p95/p99 · 자원 측정

PROJECT POLICY

모든 부하 결과에 p50/p95/p99, 오류율, 처리량과 함께 CPU, heap/RSS, GC pause, DB pool active/pending, executor active/queue를 같은 시간축으로 기록한다. 이 모듈의 초점은 executor active/queue/rejected, retry rate, outbox age, carrier pinning다.

Micrometer · Micrometer Concepts
RECOMMENDED PRACTICE

평균만으로 gate를 통과시키지 않는다. warm-up, duration, concurrency, payload, DB row 수, CPU/memory limit을 결과와 함께 저장한다.

Micrometer · Micrometer Concepts

보안 경계

PROJECT POLICY

async principal/context·payment idempotency key에 대한 신뢰 경계를 표시하고 API key·payment token·DB credential은 코드, fixture, log, trace attribute에 넣지 않는다.

Spring · Spring Security · Servlet Architecture
RECOMMENDED PRACTICE

입력 검증과 권한 검사는 서로 대체하지 않는다. 인증된 주체, 허용된 행위, 대상 리소스 소유권을 각각 확인한다.

Spring · Spring Security · Servlet Architecture

log · metric · trace · profile

PROJECT POLICY

구조화 log에는 timestamp, level, service, traceId, orderId, outcome, error.type을 남기되 원문 body와 secret은 제외한다. 핵심 신호는 executor saturation, retry/circuit state, recovery age, cancellation다.

Spring · Spring Boot · Observability
RECOMMENDED PRACTICE

metric은 집계 가능한 상태, trace는 한 요청의 인과 경로, profile은 CPU·allocation 원인에 사용한다. 하나의 신호로 다른 신호를 흉내 내지 않는다.

Spring · Spring Boot · ObservabilityOpenTelemetry · OpenTelemetry JavaOracle · JDK Flight Recorder 런타임 가이드

실제 제출 산출물

  • pool/queue capacity sheet
  • recovery state machine
  • saturation/retry/deadlock evidence

기계적 완료 조건

  • race/deadlock
    ./gradlew concurrencyTest
    통과 기준: deterministic barrier test 통과, deadlock fixture는 timeout 내 탐지
  • saturation/retry
    ./gradlew failureTest
    통과 기준: rejection·retry cohort 분산·retry cap·stale recovery assertion 통과
  • 종료 drain
    ./gradlew failureTest --tests '*GracefulShutdownFailureTest'
    통과 기준: 진행 HTTP 요청이 context close보다 먼저 완료되고 close가 제한 시간 안에 끝남

자가시험 · 답 보기

virtual thread와 connection pool의 관계는?
virtual thread는 대기 thread 비용을 줄이지만 connection pool capacity는 그대로다. 더 많은 task가 pool에서 기다릴 수 있어 admission이 더 중요해진다.
distributed lock이 exactly-once를 보장하지 못하는 이유는?
lease 만료, network partition, process pause, lock 뒤 side effect 실패가 있다. fencing/idempotency/constraint/reconciliation이 별도로 필요하다.

P09모듈 09테스트: 증거의 층을 설계하기mock 성공이 아니라 domain, Spring slice, real DB, HTTP contract, concurrency, load, failure 증거를 release gate로 묶는다.

냉정한 실체

SPEC

JUnit은 test discovery/execution model을 제공하고 Spring TestContext는 ApplicationContext와 transaction test support를 제공한다. 어떤 현실을 증명하는지는 fixture·boundary·assertion이 결정한다.

JUnit · JUnit 6 User GuideSpring · Spring Framework Testing

흔한 오해

  • 오해: coverage 100%면 production behavior가 검증됐다.
  • 오해: @Transactional test가 통과하면 commit/flush 이후 동작도 통과한다.
  • 오해: 모든 dependency를 mock하면 unit test가 더 좋다.

선수 지식

  • P00–P08 capstone path와 실패 목록
  • JUnit assertion·fixture 기본

예상 시간

3시간

이 모듈의 capstone 증분

다섯 test suite와 load/failure evidence matrix를 release gate에 연결한다.

15분 개념 지도

0–4

질문

RECOMMENDED PRACTICE

rule, wiring, infrastructure, peer contract, race, capacity 중 무엇을 묻는지 먼저 쓴다.

JUnit · JUnit 6 User Guide

12–15

증거 한계

RECOMMENDED PRACTICE

각 test가 증명하는 것/하지 못하는 것을 명시하고 gate 시간을 관리한다.

JUnit · JUnit 6 User Guide

JVM · Spring 내부 실행 과정

SPEC

JUnit Platform이 TestEngine을 통해 test descriptor를 발견하고 lifecycle/callback을 실행한다.

JUnit · JUnit 6 User Guide
SPEC

Spring TestContext framework는 context cache와 listener를 사용한다. context-altering annotation은 cache key를 늘려 suite 시간과 격리를 바꾼다.

Spring · Spring Framework Testing
SPEC

Spring-managed test transaction은 기본 rollback될 수 있어 실제 commit callback, DB constraint timing, outbox visibility를 숨길 수 있다.

Spring · Spring Framework Testing
RECOMMENDED PRACTICE

MVC slice는 controller·argument resolver·validation·security boundary를 빠르게 격리하지만 JPA/Flyway/실제 network는 증명하지 않는다. 이 저장소는 중요한 HTTP 계약을 full context+PostgreSQL에서 다시 검증한다.

Spring · Spring Framework Testing
RECOMMENDED PRACTICE

Testcontainers는 실제 engine behavior를 높이지만 Docker availability, image pin, startup, migration, cleanup도 test surface에 포함한다.

Testcontainers · Testcontainers for Java

컴파일되는 최소 경로 발췌

src/test/java/dev/productionlab/commerce/payment/PaymentOutboxProcessorTest.javajava
@org.junit.jupiter.api.Test
void unknownPaymentIsReconciledByStatusWithoutAnotherCharge() {
  java.time.Instant now = java.time.Instant.EPOCH;
  var event = new OutboxEvent(
      "ORDER", orderId, "PAYMENT_REQUESTED", "{}",
      "request-correlation-0001", now);
  event.startAttempt(now);
  payment.unknown("transport_timeout_or_io", now.plusSeconds(1));

  when(outbox.findById(event.id())).thenReturn(java.util.Optional.of(event));
  when(payments.findByOrderId(orderId)).thenReturn(java.util.Optional.of(payment));
  when(gateway.findByPaymentId(payment.id())).thenReturn(providerResult);

  processor.process(event.id());

  verify(gateway).findByPaymentId(payment.id());
  verifyNoMoreInteractions(gateway);
}

sleep/wall clock을 제거하고 핵심 안전성인 '불확실할 때 재청구하지 않음'을 직접 assertion한다.

RECOMMENDED PRACTICE

test fixture 시각을 고정하고 production의 현재 시각 의존성은 Clock 경계로 격리한 뒤 business outcome을 assertion한다.

Oracle · java.time 패키지JUnit · JUnit 6 User Guide

컴파일되는 production 확장 발췌

src/test/java/dev/productionlab/commerce/order/OrderIdempotencyIntegrationTest.javajava
@org.junit.jupiter.api.Test
void replayReturnsTheOriginalOrderAndReservesOnce() {
  var request = new CreateOrderRequest("LAB-SKU", 2);

  var first = orders.create("integration-key-0001", request);
  var replay = orders.create("integration-key-0001", request);

  assertThat(replay.id()).isEqualTo(first.id());
  assertThat(jdbc.sql("select count(*) from commerce_orders")
      .query(Integer.class).single()).isEqualTo(1);
  assertThat(jdbc.sql("select count(*) from outbox_events")
      .query(Integer.class).single()).isEqualTo(1);
}

test method 전체 rollback에 기대지 않는다. transactional service 호출이 commit된 뒤 독립 JDBC reader와 repository 조회로 persisted row를 관측한다.

RECOMMENDED PRACTICE

commit 뒤에만 보이는 behavior는 명시적인 transaction boundary와 새 reader로 검증한다.

Spring · Spring Framework Testing

중요 코드 해설

RECOMMENDED PRACTICE

mock은 관측 가능한 port interaction이 contract일 때만 사용하고 entity/repository/framework internals를 복제하지 않는다.

Spring · Spring Framework Testing
PROJECT POLICY

unit/integration/contract/concurrency/failure는 Gradle JUnit tag task로 분리하며 check가 모두 의존한다. k6 load는 환경 명세와 함께 별도 gate다.

요청 하나의 end-to-end call trace

  1. SPEC

    Gradle test task가 JUnit Platform engine을 시작하고 tag로 suite를 선택한다.

    JUnit · JUnit 6 User GuideGradle · Gradle 빌드 생명주기
  2. SPEC

    integration suite는 Testcontainers lifecycle에서 DB를 시작하고 migration 후 Spring context가 datasource와 repository를 연결한다.

    Testcontainers · Testcontainers for JavaSpring · Spring Framework Testing
  3. PROJECT POLICY

    contract suite는 실제 HTTP serialization과 stub payment adapter request를 snapshot이 아닌 semantic assertion으로 확인한다.

  4. RECOMMENDED PRACTICE

    failure/concurrency suite는 barrier, bounded timeout, final DB state를 남겨 hang과 false pass를 막는다.

    JUnit · JUnit 6 User GuideSpring · Spring Framework Testing

실패 주입 실습

rollback test가 숨긴 outbox 결함

주입
rollback-only test의 관찰을 신뢰하지 않고 실제 service transaction 종료 뒤 새 DB query로 order와 outbox를 읽는다.
명령
./gradlew integrationTest --tests '*OrderIdempotencyIntegrationTest'
관찰
commit 뒤 commerce_orders 1건과 outbox_events 1건이 보이고 replay에도 중복이 없다.
복구
production과 같은 commit/reader boundary를 integration suite에 추가한다.
VERIFIED BEHAVIOR

OrderIdempotencyIntegrationTest는 실제 service transaction이 끝난 뒤 JdbcClient로 order/outbox count와 inventory를 확인한다.

Spring · Spring Framework Testing

테스트 계약

unit

./gradlew unitTest

증명 범위: 격리된 도메인 규칙과 경계값

증명하지 않는 것: Spring wiring, 실제 DB, 네트워크 동작

integration

./gradlew integrationTest

증명 범위: 고정된 DB·Spring 조합의 실제 연결

증명하지 않는 것: 운영 데이터 분포와 장시간 부하

contract

./gradlew contractTest

증명 범위: HTTP·결제 adapter 경계의 요청/응답 계약

증명하지 않는 것: 상대 시스템의 가용성

load

k6 run -e SCENARIO=p09 scripts/load-test.js

증명 범위: 명시한 부하 모델에서 latency·saturation 변화

증명하지 않는 것: 다른 하드웨어·데이터·트래픽에서 같은 수치

p95/p99 · 자원 측정

PROJECT POLICY

모든 부하 결과에 p50/p95/p99, 오류율, 처리량과 함께 CPU, heap/RSS, GC pause, DB pool active/pending, executor active/queue를 같은 시간축으로 기록한다. 이 모듈의 초점은 suite duration/flakiness, context cache, container startup, coverage gap다.

Micrometer · Micrometer Concepts
RECOMMENDED PRACTICE

평균만으로 gate를 통과시키지 않는다. warm-up, duration, concurrency, payload, DB row 수, CPU/memory limit을 결과와 함께 저장한다.

Micrometer · Micrometer Concepts

보안 경계

PROJECT POLICY

test fixture·recording·container credential에 대한 신뢰 경계를 표시하고 API key·payment token·DB credential은 코드, fixture, log, trace attribute에 넣지 않는다.

Spring · Spring Security · Servlet Architecture
RECOMMENDED PRACTICE

입력 검증과 권한 검사는 서로 대체하지 않는다. 인증된 주체, 허용된 행위, 대상 리소스 소유권을 각각 확인한다.

Spring · Spring Security · Servlet Architecture

log · metric · trace · profile

PROJECT POLICY

구조화 log에는 timestamp, level, service, traceId, orderId, outcome, error.type을 남기되 원문 body와 secret은 제외한다. 핵심 신호는 test duration, retry/flaky count, seed, container/log artifact다.

Spring · Spring Boot · Observability
RECOMMENDED PRACTICE

metric은 집계 가능한 상태, trace는 한 요청의 인과 경로, profile은 CPU·allocation 원인에 사용한다. 하나의 신호로 다른 신호를 흉내 내지 않는다.

Spring · Spring Boot · ObservabilityOpenTelemetry · OpenTelemetry JavaOracle · JDK Flight Recorder 런타임 가이드

실제 제출 산출물

  • test evidence matrix
  • fake Clock와 concurrency fixture
  • clean-install verification log

기계적 완료 조건

  • 전체 자동 gate
    ./gradlew clean check --no-daemon
    통과 기준: 5개 tagged suite 포함, 0 failures
  • 개별 suite
    ./gradlew unitTest integrationTest contractTest concurrencyTest failureTest
    통과 기준: 각 task가 실제 test를 실행하고 모두 통과
  • load evidence
    k6 run -e SCENARIO=checkout scripts/load-test.js
    통과 기준: threshold와 환경 metadata 포함 결과 생성

자가시험 · 답 보기

@Transactional integration test가 숨길 수 있는 두 가지는?
실제 commit/after-commit callback과 commit/flush 시점 constraint; rollback 덕분의 데이터 cleanup 착시도 있다.
concurrency test에서 sleep보다 barrier가 나은 이유는?
경쟁 window를 명시적으로 맞춰 scheduler 운에 덜 의존하고 bounded timeout으로 hang을 판정할 수 있다.

P10모듈 10관측성과 운영한 주문 incident를 log·metric·trace·JFR에서 연결하고 canary/rollback/DB migration runbook으로 복구한다.

냉정한 실체

SPEC

Spring Boot observability는 Micrometer Observation을 바탕으로 metrics와 traces를 기록할 수 있다. correlation은 instrumentation과 context propagation이 실제 경로에 있어야 생기며 log만 많다고 관측 가능한 것은 아니다.

Spring · Spring Boot · ObservabilityMicrometer · Micrometer Concepts

흔한 오해

  • 오해: 모든 값을 label/tag로 넣으면 dashboard가 더 유용하다.
  • 오해: trace 100%가 항상 안전하고 저렴하다.
  • 오해: backward-compatible application binary면 DB migration도 무중단이다.

선수 지식

  • P00 JFR/GC와 P04 Actuator
  • P05 trace path와 P06–P08 pool/queue/recovery

예상 시간

3시간

이 모듈의 capstone 증분

관측 signal, incident drill, canary/rollback, backward-compatible migration gate를 완성한다.

15분 개념 지도

JVM · Spring 내부 실행 과정

SPEC

Observation은 start/stop/error lifecycle과 low/high-cardinality key-values를 갖고 registry handler가 meter/trace 같은 signal을 만든다.

Spring · Spring Boot · ObservabilityMicrometer · Micrometer Concepts
SPEC

low-cardinality key는 metrics와 traces에, high-cardinality key는 traces에 사용할 수 있으므로 orderId를 metric label로 두지 않는다.

Spring · Spring Boot · Observability
SPEC

JFR는 JVM event를 recording하고 thread/heap dump는 시점 상태를 보여 준다. artifact에는 민감한 heap/object/string이 포함될 수 있다.

Oracle · JDK Flight Recorder 런타임 가이드
SPEC

Java 21 unified logging은 -Xlog selector로 gc·safepoint 같은 tag, level, output, rotation을 정한다. GC log는 pause·heap transition의 시간축 증거이며 allocation 원인 자체는 JFR/allocation profile과 연결해야 한다.

Oracle · Java 21 java launcher · Unified JVM LoggingOracle · JDK Flight Recorder 런타임 가이드
RECOMMENDED PRACTICE

canary는 작은 traffic cohort에서 동일 SLO/error/queue 기준을 비교하고 rollback은 app version뿐 아니라 schema compatibility를 고려한다.

Spring · Spring Boot · Observability

컴파일되는 최소 경로 발췌

src/main/java/dev/productionlab/commerce/observability/CorrelationIdFilter.javajava
@org.springframework.stereotype.Component
final class CorrelationIdFilter extends org.springframework.web.filter.OncePerRequestFilter {
  protected void doFilterInternal(
      jakarta.servlet.http.HttpServletRequest req,
      jakarta.servlet.http.HttpServletResponse res,
      jakarta.servlet.FilterChain chain) throws java.io.IOException, jakarta.servlet.ServletException {
    var supplied = req.getHeader("X-Correlation-Id");
    var id = supplied != null && SAFE_ID.matcher(supplied).matches()
        ? supplied : java.util.UUID.randomUUID().toString();
    org.slf4j.MDC.put("correlationId", id);
    res.setHeader("X-Correlation-Id", id);
    try {
      chain.doFilter(req, res);
    } finally {
      org.slf4j.MDC.remove("correlationId");
    }
  }
}

허용 문자/길이를 검증해 log injection과 unbounded cardinality를 막고 finally 성격의 close로 thread reuse 누출을 막는다.

RECOMMENDED PRACTICE

외부 correlation ID는 untrusted input으로 다루고 traceId와 역할을 구분한다.

Spring · Spring Boot · Observability

컴파일되는 production 확장 발췌

src/main/java/dev/productionlab/commerce/payment/PaymentOutboxProcessor.javajava
String previousCorrelationId = MDC.get("correlationId");
if (event.correlationId() != null) {
  MDC.put("correlationId", event.correlationId());
} else {
  MDC.remove("correlationId");
}
try {
  Observation.createNotStarted("commerce.outbox.payment", observations)
      .lowCardinalityKeyValue("event.type", event.eventType())
      .observe(() -> processPayment(event));
} finally {
  if (previousCorrelationId == null) {
    MDC.remove("correlationId");
  } else {
    MDC.put("correlationId", previousCorrelationId);
  }
}

event.type은 bounded low-cardinality key로 두고 orderId/provider message는 metric tag로 만들지 않는다. worker는 저장한 correlation을 복원한 뒤 finally에서 이전 MDC를 되돌린다.

RECOMMENDED PRACTICE

metric dimension은 bounded business state로 제한하고 cardinality budget을 review한다.

Micrometer · Micrometer ConceptsSpring · Spring Boot · Observability

중요 코드 해설

SPEC

MDC는 thread-local 성격이므로 async/reactive hop에서 별도 propagation integration을 검증해야 한다.

Spring · Spring Boot · Observability
PROJECT POLICY

trace sample은 baseline 10%에서 시작하고 error/UNKNOWN recovery는 tail/rule 기반 보존 경로를 검토한다. 비용·누락률로 조정한다.

Spring · Spring Boot · Observability

요청 하나의 end-to-end call trace

  1. PROJECT POLICY

    POST /orders ingress가 trace/correlation context를 만들고 controller→transaction→SQL→outbox span에 전파한다.

  2. RECOMMENDED PRACTICE

    outbox에는 trace link에 필요한 안전한 context만 저장하고 relay는 producer/consumer 인과를 span link 또는 명시 attribute로 연결한다.

    OpenTelemetry · OpenTelemetry Java
  3. PROJECT POLICY

    dashboard는 HTTP RED, order/payment outcome, DB pool, executor queue/rejection, outbox oldest age, JVM CPU/heap/GC를 같은 deploy marker 위에 겹친다.

  4. RECOMMENDED PRACTICE

    incident timeline은 최초 alert, deploy/config/migration, symptom, hypothesis, evidence, mitigation, recovery 확인, follow-up owner를 UTC로 기록한다.

    Spring · Spring Boot · Observability

실패 주입 실습

호환성 없는 migration과 민감정보 logging

주입
기존 row와 호환되지 않는 NOT NULL column migration을 적용하고 credential/header/body를 redactor와 access-log filter 입력으로 넣는다.
명령
./gradlew failureTest && ./gradlew unitTest --tests '*SensitiveDataRedactorTest' --tests '*SafeAccessLogFilterTest'
관찰
breaking migration은 실패하고 redactor/filter output에는 raw credential·body가 없다. old/new binary matrix와 repository-wide secret scanner는 아직 learner release artifact다.
복구
expand → dual-compatible deploy/backfill → contract 순서와 allow-list structured logging으로 바꾼다.
VERIFIED BEHAVIOR

SchemaCompatibilityFailureTest는 unsafe migration failure를, SensitiveDataRedactorTest와 SafeAccessLogFilterTest는 known credential/body 비노출을 assertion한다. 범위를 넘어선 scanner·binary matrix는 주장하지 않는다.

테스트 계약

unit

./gradlew unitTest

증명 범위: 격리된 도메인 규칙과 경계값

증명하지 않는 것: Spring wiring, 실제 DB, 네트워크 동작

integration

./gradlew integrationTest

증명 범위: 고정된 DB·Spring 조합의 실제 연결

증명하지 않는 것: 운영 데이터 분포와 장시간 부하

contract

./gradlew contractTest

증명 범위: HTTP·결제 adapter 경계의 요청/응답 계약

증명하지 않는 것: 상대 시스템의 가용성

load

k6 run -e SCENARIO=p10 scripts/load-test.js

증명 범위: 명시한 부하 모델에서 latency·saturation 변화

증명하지 않는 것: 다른 하드웨어·데이터·트래픽에서 같은 수치

p95/p99 · 자원 측정

PROJECT POLICY

모든 부하 결과에 p50/p95/p99, 오류율, 처리량과 함께 CPU, heap/RSS, GC pause, DB pool active/pending, executor active/queue를 같은 시간축으로 기록한다. 이 모듈의 초점은 RED/USE, cardinality, trace sampling cost, JFR allocation/CPU다.

Micrometer · Micrometer Concepts
RECOMMENDED PRACTICE

평균만으로 gate를 통과시키지 않는다. warm-up, duration, concurrency, payload, DB row 수, CPU/memory limit을 결과와 함께 저장한다.

Micrometer · Micrometer Concepts

보안 경계

PROJECT POLICY

logs·metrics backend·traces·JFR/thread/heap dumps에 대한 신뢰 경계를 표시하고 API key·payment token·DB credential은 코드, fixture, log, trace attribute에 넣지 않는다.

Spring · Spring Security · Servlet Architecture
RECOMMENDED PRACTICE

입력 검증과 권한 검사는 서로 대체하지 않는다. 인증된 주체, 허용된 행위, 대상 리소스 소유권을 각각 확인한다.

Spring · Spring Security · Servlet Architecture

log · metric · trace · profile

PROJECT POLICY

구조화 log에는 timestamp, level, service, traceId, orderId, outcome, error.type을 남기되 원문 body와 secret은 제외한다. 핵심 신호는 order/payment SLI, DB/executor saturation, incident timeline completeness다.

Spring · Spring Boot · Observability
RECOMMENDED PRACTICE

metric은 집계 가능한 상태, trace는 한 요청의 인과 경로, profile은 CPU·allocation 원인에 사용한다. 하나의 신호로 다른 신호를 흉내 내지 않는다.

Spring · Spring Boot · ObservabilityOpenTelemetry · OpenTelemetry JavaOracle · JDK Flight Recorder 런타임 가이드

실제 제출 산출물

  • dashboard와 alert rule
  • JFR/thread/heap dump 접근 runbook
  • canary/rollback/migration incident drill

기계적 완료 조건

  • signal contract
    ./gradlew contractTest unitTest --tests '*SensitiveDataRedactorTest' --tests '*PaymentOutboxProcessorTest'
    통과 기준: HTTP→outbox correlation 저장, worker MDC 복원, known secret redaction assertion 통과
  • 운영 failure gate
    ./gradlew failureTest
    통과 기준: migration incompatibility·retry exhaustion·shutdown drain fixture가 기대대로 탐지
  • production web build
    npm run verify
    통과 기준: format·type·lint·source/render contract·production bundle·audit 성공

자가시험 · 답 보기

orderId를 metric tag로 두지 않는 이유는?
값 수가 사실상 무한해 time-series cardinality와 비용을 폭증시킨다. trace/log의 searchable field로 둔다.
DB column 삭제를 한 deploy에 하면 rollback이 위험한 이유는?
새 schema가 이전 binary의 read/write contract를 깨면 app rollback이 작동하지 않는다. expand/contract로 양쪽 호환 window를 만든다.

BOUNDARY TABLE

용어의 경계를 먼저 고정한다

같은 단어를 서로 다른 층의 개념으로 쓰는 순간 디버깅이 흔들린다.

용어이 가이드의 경계이것과 혼동 금지공식 출처
JDK / JVMJDK는 compiler·tools·runtime을 배포하고 JVM은 class-file 실행 모델을 구현한다.Java 언어, JDK distribution, HotSpot 구현을 모두 JVM이라는 한 단어로 섞지 않는다.Oracle · JVMS Java SE 21
Class loading / initializationloading은 binary로 Class를 만들고 linking은 verify/prepare/resolve하며 initialization은 static initializer를 실행한다.class가 load됐다는 사실만으로 static initialization이 끝났다고 보지 않는다.Oracle · JLS 12장 · 실행
Visibility / atomicity / orderingvisibility는 write 관측, atomicity는 중간 상태 불가, ordering은 허용 재배치와 관측 순서의 문제다.volatile 하나가 복합 read-modify-write 전체를 atomic하게 만들지 않는다.Oracle · JLS 17장 · 스레드와 메모리 모델
Platform thread / virtual threadplatform thread는 OS thread와 밀접히 대응하고 virtual thread는 JVM이 carrier 위에 schedule하는 Java Thread다.virtual thread를 CPU·DB connection·rate limit 증설로 번역하지 않는다.OpenJDK · JEP 444 · Virtual Threads (Final)
BeanDefinition / bean / target / proxydefinition은 recipe, bean은 container가 관리하는 instance, target은 실제 logic, proxy는 interceptor를 거치는 외부 reference다.@Service annotation, 원본 class, runtime class를 같은 것으로 보지 않는다.Spring · Spring Framework · 컨테이너와 Bean
Starter / auto-configurationstarter는 dependency 묶음, auto-configuration은 조건 평가 후 import되는 configuration code다.starter가 bean을 직접 실행하거나 모든 조건을 강제한다고 말하지 않는다.Spring · Spring Boot · Auto-configuration
Authentication / authorizationauthentication은 주체 증명, authorization은 그 주체가 이 리소스에 이 행위를 할 수 있는지 판정한다.유효한 API key가 모든 order 접근 권한을 뜻하지 않는다.Spring · Spring Security · Servlet Architecture
Transaction / connectiontransaction은 commit/rollback되는 작업 경계이고 connection은 DB와 통신하는 제한 자원이다.thread, HTTP request, @Transactional method, connection lifetime이 항상 일치한다고 가정하지 않는다.Spring · Spring · 선언적 트랜잭션
Flush / commitflush는 persistence 변경을 SQL로 동기화하고 commit은 transaction 성공을 확정한다.repository save가 즉시 flush 또는 commit을 뜻하지 않는다.Hibernate · Hibernate ORM · Persistence Context
Persistence context / databasepersistence context는 managed entity의 identity·snapshot을 추적하는 application-side unit이고 DB가 최종 constraint와 durability를 제공한다.first-level cache hit를 DB commit 또는 최신 외부 상태로 해석하지 않는다.Hibernate · Hibernate ORM · Persistence Context
Lazy / N+1lazy는 접근 시점까지 load를 미루는 전략이고 N+1은 한 query 뒤 반복 association query가 생기는 실행 형태다.모든 lazy가 N+1이거나 모든 eager가 해결책이라고 보지 않는다.Hibernate · Hibernate ORM · Fetching
Timeout / cancellation / deadlinetimeout은 기다림 한도, cancellation은 중단 요청, deadline은 전체 작업이 끝나야 할 절대/전파 시간 경계다.client timeout이 server side effect 취소를 증명하지 않는다.Oracle · ExecutorService API
Retry / recoveryretry는 같은 operation을 제한적으로 다시 시도하고 recovery는 durable state를 읽어 process restart 뒤에도 reconcile한다.memory queue retry를 durable recovery로 부르지 않는다.Oracle · ExecutorService API
Idempotency / exactly-onceidempotency는 같은 logical request 반복이 허용된 동일 효과를 내도록 하는 contract다.network와 process 실패가 있는 end-to-end 경로를 단일 lock으로 exactly-once라 부르지 않는다.Spring · Spring · 선언적 트랜잭션
MVC / WebFluxMVC는 Servlet blocking model, WebFlux는 reactive/non-blocking model이며 실제 driver와 call chain이 모델을 완성한다.return type이 Mono라는 사실만으로 end-to-end non-blocking이라 하지 않는다.Spring · Spring WebFlux · Reactive Core
Liveness / readiness / startupliveness는 restart 필요, readiness는 traffic 수신 가능, startup은 느린 시작 동안 liveness 개입을 늦추는 신호다.모든 dependency health를 세 probe에 똑같이 넣지 않는다.Spring · Spring Boot Actuator · Kubernetes Probes
Log / metric / trace / profilelog는 사건 record, metric은 집계 time series, trace는 요청 인과 경로, profile은 자원 소비 attribution이다.하나를 과적재해 다른 signal의 질문에 모두 답하게 하지 않는다.Spring · Spring Boot · Observability
Unit / integration / contract / load testunit은 격리 규칙, integration은 실제 연결, contract는 경계 semantics, load는 명시 workload의 capacity를 묻는다.한 종류의 통과를 다른 종류의 증거로 확장하지 않는다.Spring · Spring Framework Testing

CLAIM TRANSLATOR

마케팅 문장을 운영 문장으로 번역한다

절대 표현을 측정 가능한 조건과 실패 비용으로 바꾼다.

주장

“JPA가 entity를 자동 저장한다.”

운영 번역

managed entity snapshot의 변경을 dirty checking하고 flush에서 SQL로 동기화한다.

detached entity, flush timing, constraint, commit 실패는 '자동 저장' 밖에 숨는다.

SPEC

persistence context와 flush를 구분한다.

Hibernate · Hibernate ORM · Persistence Context

주장

“starter 하나면 production-ready다.”

운영 번역

starter가 관련 artifact를 classpath에 추가하고 auto-configuration 조건이 일부 bean 기본값을 만든다.

timeout, authz, capacity, data migration, alert, recovery, release gate는 team 책임이다.

SPEC

starter와 조건부 configuration은 서로 다른 층이다.

Spring · Spring Boot · Auto-configuration

주장

“Reactive라서 빠르다.”

운영 번역

non-blocking dependency와 event-loop model이면 많은 I/O 대기 작업의 thread 비용을 줄일 수 있다.

JDBC bridge, scheduler queue, p99, memory, debugging 비용을 동일 workload에서 비교해야 한다.

RECOMMENDED PRACTICE

reactive는 측정 전 성능 결론이 아니라 execution model이다.

Spring · Spring WebFlux · Reactive Core

주장

“결제는 exactly once다.”

운영 번역

client/server idempotency key, unique state transition, provider 조회, reconciliation을 결합해 duplicate effect를 제한한다.

timeout-after-success와 process crash가 있으므로 UNKNOWN 상태와 운영 recovery가 필요하다.

RECOMMENDED PRACTICE

원자성을 주장할 수 없는 network 경계를 상태 machine으로 드러낸다.

Spring · Spring · 선언적 트랜잭션

주장

“graceful shutdown이면 요청 손실이 없다.”

운영 번역

새 요청을 거부하고 기존 요청에 제한된 완료 시간을 주는 lifecycle 절차다.

deadline 초과, client retry, load balancer drain, durable async state를 함께 설계해야 한다.

SPEC

grace period는 무한 완료 보장이 아니다.

Spring · Spring Boot · Graceful Shutdown

주장

“Actuator를 켜면 observability가 끝난다.”

운영 번역

instrumentation hook와 endpoint를 얻고 domain outcome, context propagation, dashboard, alert, runbook을 연결한다.

tag cardinality, sampling, secret 노출, backend retention과 incident 사용성은 별도 검증한다.

RECOMMENDED PRACTICE

signal 생성과 운영 의사결정 가능성은 다른 완료 수준이다.

Spring · Spring Boot · ObservabilityMicrometer · Micrometer Concepts

VERIFICATION LEDGER

실행한 것과 아직 모르는 것을 분리한다

명령·환경·기대값·관찰값·보존 artifact를 함께 기록한다. Java 25, Windows 실실행, 실제 학습 효과는 검증되지 않았다.

VERIFIED BEHAVIOR

아래 원장은 2026-07-16 고정 환경에서 실제 실행한 명령만 기록한다. 통과한 테스트는 기능 증거이고, 짧은 로컬 부하는 POST 202 수락 경로의 품질 표본일 뿐 production 용량이나 학습 효과의 증거가 아니다.

기능·품질·제품 검증 원장
ID수준상태환경명령기대관찰artifact기록 시각
FUNC-MAIN-20260716FUNCTIONVERIFIEDmacOS 26.2 arm64 · OpenJDK 21.0.11 Homebrew · Docker Engine 28.5.2 · PostgreSQL 17.6 Testcontainerscd lab && ./gradlew --no-daemon --dependency-verification strict clean check --rerun-tasksformat·정적 분석·unit·integration·contract·concurrency·failure suite가 모두 성공한다.BUILD SUCCESSFUL (33초), 16/16 task 실행. 6개 test task의 66회 실행은 실패·오류·skip 0이며 task 간 중복을 포함한다.docs/evidence/2026-07-16-local-verification.json2026-07-17T00:27:34Z
FUNC-P07-20260716FUNCTIONVERIFIEDmacOS 26.2 arm64 · OpenJDK 21.0.11 Homebrew · Docker Engine 28.5.2 · PostgreSQL 17.6 Testcontainerscd lab && ./gradlew -p reactive-comparison --no-daemon --dependency-verification strict clean check --rerun-tasksWebFlux/R2DBC contract와 blocking-boundary failure test가 성공한다.BUILD SUCCESSFUL (22초). `--rerun-tasks`로 독립 P07 build의 10개 task를 실행해 성공했다.docs/evidence/2026-07-16-local-verification.json2026-07-17T00:21:00Z
FUNC-IMAGE-20260716FUNCTIONVERIFIEDDocker Engine 28.5.2 · Eclipse Temurin 21.0.11 Alpine · pinned PostgreSQL/WireMock imagescd lab && docker compose --profile full up --build -d && ./scripts/smoke.shhealth가 UP이고 inventory seed·order accept·payment reconciliation이 연결된다.health UP, inventory HTTP 200, order HTTP 202, 7회 polling 후 최종 CONFIRMED. 앱은 uid 100이며 당시 존재한 최근 로그 159줄에서 ERROR/Exception·로컬 secret literal 0건이었다.docs/evidence/2026-07-16-local-verification.json2026-07-17T00:24:00Z
QUALITY-ADMISSION-20260716QUALITYVERIFIEDApple M2 Max · host 32 GB/no Compose memory cap · k6 2.0.0 · 10 req/s for 10s · one post-run resource snapshotcd lab && k6 run -e RATE=10 -e DURATION=10s -e SUMMARY_EXPORT=load-summary.json scripts/load-test.jsPOST 202 수락 경로에서 check 100%, HTTP failure <1%, p95 <500ms, p99 <1000ms.102 요청, check 100%, failure 0%, 평균 15.11ms, p95 22.57ms, p99 28.09ms. post-run 표본은 앱 CPU 1.92%·452.4MiB, Hikari active/pending 0, outbox active/queue 0이다. 동기화된 시계열은 아니다.docs/evidence/2026-07-16-local-verification.json2026-07-17T00:24:54Z
PRODUCT-LEARNERPRODUCTUNVERIFIEDNo representative learner study has been runUNRUN — moderated learner task study학습자가 도움 없이 요청 경로를 설명하고 장애를 재현·복구하며 시간·오류율이 기준선보다 개선된다.UNVERIFIED — 저장소 테스트와 콘텐츠 검사는 실제 이해도나 업무 속도를 증명하지 않는다.NONE — learner test required2026-07-16

CAPSTONE ORDER

주문 → 재고 → 결제, 한 줄로 끝까지

순서를 건너뛰지 않는다. 각 gate는 다음 단계의 입력이다.

01 · P00
15 MIN

JDK 21 위에서 commerce process의 class/thread/memory 지도를 만든다.

gate: javap/JFR/container limit evidence를 서로 구분한다.

artifact: LEARNER OUTPUT (not committed) — docs/evidence/p00-jvm-map.md

02 · P01
15 MIN

Money·Sku·Quantity·Clock·상태 타입으로 invalid state를 줄인다.

gate: null·금액·시간대·mutable-key 경계 test가 있다.

artifact: src/main/java/dev/productionlab/commerce/order

03 · P02
MINIMUM

고정 Wrapper/toolchain/BOM/lock과 clean CI graph를 만든다.

gate: macOS ./gradlew와 Windows gradlew.bat 경로가 개인 설정 없이 재현 가능하다.

artifact: build.gradle.kts

04 · P03
MINIMUM

package-by-feature dependency와 service proxy를 runtime에서 확인한다.

gate: bean graph에 cycle이 없고 target/proxy/advisor가 식별된다.

artifact: LEARNER OUTPUT (not committed) — docs/evidence/p03-beans.md

05 · P04
MINIMUM

validated config, condition report, probes와 graceful shutdown을 붙인다.

gate: invalid config는 startup 실패, readiness는 traffic 상태와 일치한다.

artifact: src/main/resources/application.yml

06 · P05
MINIMUM

인증·idempotency·오류 계약을 가진 order API를 연다.

gate: 202/400/401/403/409 contract와 동시 duplicate test가 있다.

artifact: src/main/java/dev/productionlab/commerce/order/OrderController.java

07 · P06
BREAK IT

order+inventory+outbox를 local atomic하게 하고 payment UNKNOWN을 복구한다.

gate: self-invocation·rollback·N+1·lazy·pool·lock·timeout 실패가 fixture로 재현된다.

artifact: src/main/java/dev/productionlab/commerce/outbox

08 · P07
BREAK IT

현재 MVC baseline과 격리된 WebFlux/R2DBC GET harness를 실행하고 동일-workload 측정 evidence를 학습자가 추가한다.

gate: 두 harness의 contract test는 존재하지만 같은 자원 제한의 비교 JSON 전에는 성능 우위를 VERIFIED BEHAVIOR로 주장하지 않는다.

artifact: reactive-comparison/ + LEARNER OUTPUT — docs/evidence/p07-mvc-reactive.json

09 · P08
BREAK IT

bounded executor와 durable payment recovery로 saturation/retry storm을 끊는다.

gate: 현재 race·deadlock·rejection·retry-spread·recovery·실제 HTTP shutdown-drain fixture를 실행한다.

artifact: src/main/java/dev/productionlab/commerce/payment/PaymentRecoveryService.java

10 · P09
HARDEN

다섯 JUnit tag suite와 k6 evidence를 check/release gate로 만든다.

gate: 각 suite가 실제 test를 실행하며 clean check가 모두 요구한다.

artifact: GENERATED BY TEST RUN — build/reports/tests

11 · P10
HARDEN

RED/USE dashboard, incident runbook, canary/rollback/migration gate를 연결한다.

gate: 현재 schema compatibility와 redaction test를 실행한다. secret scanner·signal contract·runbook은 학습자 산출물이다.

artifact: docs/RUNBOOK.md + learner dashboard/alert evidence