Kotlin Spring Boot 입문 | 30분 만에 REST API 만들기 [초보자용]

Kotlin Spring Boot 입문 | 30분 만에 REST API 만들기 [초보자용]

이 글의 핵심

Kotlin Spring Boot 완전 초보자를 위한 입문 튜토리얼. Java보다 간결한 문법으로 REST API 만들기. 설치부터 배포까지 30분이면 충분합니다.

들어가며: Kotlin으로 더 쉽게 시작하기

“요즘 채용 공고 보니까 Kotlin + Spring Boot를 많이 쓰던데요?”

맞습니다. Kotlin은 최근 백엔드 개발에서 빠르게 성장하고 있습니다:

  • Coupang, Kakao, Toss, 당근마켓 등 주요 기업 사용
  • Java 대비 30-40% 적은 코드량
  • Null 안전성으로 NullPointerException 방지
  • Google이 Android 공식 언어로 지정

이 튜토리얼은 완전 초보자를 위한 가이드입니다. 설치 → 프로젝트 생성 → REST API → 실행까지 30분이면 충분합니다.

Java vs Kotlin 코드 비교

Java (장황함):

public class User {
    private Long id;
    private String name;
    
    public User(Long id, String name) {
        this.id = id;
        this.name = name;
    }
    
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

Kotlin (간결함):

data class User(
    val id: Long,
    var name: String
)

똑같은 기능이지만 Kotlin은 2줄!

이 튜토리얼에서 만들 것

간단한 북마크 관리 REST API:

  • GET /api/bookmarks - 북마크 목록 조회
  • POST /api/bookmarks - 북마크 추가
  • PUT /api/bookmarks/{id} - 북마크 수정
  • DELETE /api/bookmarks/{id} - 북마크 삭제

사전 요구사항

  • Java 17 이상 설치 (Kotlin은 JVM에서 실행)
  • IDE: IntelliJ IDEA Community (무료, Kotlin 기본 지원)

1단계: 환경 설정 (3분)

Java 설치 확인

# 터미널에서 확인
java -version

# 출력 예시:
# openjdk version "17.0.2"

Java가 없다면:

  • Windows: https://adoptium.net/ → JDK 17 다운로드
  • Mac: brew install openjdk@17
  • Linux: sudo apt install openjdk-17-jdk

IntelliJ IDEA 설치

https://www.jetbrains.com/idea/download/

  • Community Edition (무료) 다운로드
  • Kotlin 플러그인 자동 포함

2단계: 프로젝트 생성 (5분)

방법 1: Spring Initializr (웹)

  1. https://start.spring.io/ 접속
  2. 다음과 같이 설정:
    • Project: Gradle - Kotlin
    • Language: Kotlin
    • Spring Boot: 3.2.x (최신 안정 버전)
    • Packaging: Jar
    • Java: 17
  3. Project Metadata:
    • Group: com.example
    • Artifact: demo
    • Name: demo
    • Package name: com.example.demo
  4. Dependencies 추가:
    • Spring Web
    • Spring Data JPA
    • H2 Database
  5. GENERATE 클릭 → ZIP 다운로드
  6. 압축 해제 후 IntelliJ로 열기

방법 2: IntelliJ IDEA (권장)

  1. New Project → Spring Initializr
  2. 위와 동일하게 설정
  3. Create

3단계: 첫 REST API 만들기 (10분)

프로젝트 구조

src/main/kotlin/com/example/demo/
├── DemoApplication.kt          # 메인
├── controller/
│   └── BookmarkController.kt   # REST API
├── model/
│   └── Bookmark.kt             # 데이터 모델
├── repository/
│   └── BookmarkRepository.kt   # DB 접근
└── service/
    └── BookmarkService.kt      # 비즈니스 로직

1) 데이터 모델 (Bookmark.kt)

src/main/kotlin/com/example/demo/model/Bookmark.kt:

package com.example.demo.model

import jakarta.persistence.*

@Entity
@Table(name = "bookmarks")
data class Bookmark(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,
    
    var title: String,
    var url: String,
    var description: String? = null
)

Java와 비교:

  • data class: getter/setter/equals/hashCode 자동 생성
  • val: 불변 (Java의 final)
  • var: 가변
  • String?: Null 허용 타입

2) Repository (BookmarkRepository.kt)

src/main/kotlin/com/example/demo/repository/BookmarkRepository.kt:

package com.example.demo.repository

import com.example.demo.model.Bookmark
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository

@Repository
interface BookmarkRepository : JpaRepository<Bookmark, Long>

한 줄로 끝! Spring Data JPA가 자동으로 구현합니다.

3) Service (BookmarkService.kt)

src/main/kotlin/com/example/demo/service/BookmarkService.kt:

package com.example.demo.service

import com.example.demo.model.Bookmark
import com.example.demo.repository.BookmarkRepository
import org.springframework.stereotype.Service

@Service
class BookmarkService(
    private val repository: BookmarkRepository
) {
    fun getAllBookmarks(): List<Bookmark> = repository.findAll()
    
    fun getBookmarkById(id: Long): Bookmark? = 
        repository.findById(id).orElse(null)
    
    fun createBookmark(bookmark: Bookmark): Bookmark = 
        repository.save(bookmark)
    
    fun updateBookmark(id: Long, bookmarkDetails: Bookmark): Bookmark {
        val bookmark = repository.findById(id)
            .orElseThrow { RuntimeException("Bookmark not found") }
        
        bookmark.title = bookmarkDetails.title
        bookmark.url = bookmarkDetails.url
        bookmark.description = bookmarkDetails.description
        
        return repository.save(bookmark)
    }
    
    fun deleteBookmark(id: Long) = repository.deleteById(id)
}

Kotlin의 장점:

  • 생성자 주입: 파라미터에 val만 붙이면 자동으로 필드 선언 + 초기화
  • 표현식 함수: fun get() = repository.findAll() (중괄호 생략)
  • Null 안전: Bookmark? 타입으로 명시적 처리

4) Controller (BookmarkController.kt)

src/main/kotlin/com/example/demo/controller/BookmarkController.kt:

package com.example.demo.controller

import com.example.demo.model.Bookmark
import com.example.demo.service.BookmarkService
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/api/bookmarks")
class BookmarkController(
    private val service: BookmarkService
) {
    // GET /api/bookmarks - 전체 조회
    @GetMapping
    fun getAllBookmarks(): List<Bookmark> = service.getAllBookmarks()
    
    // GET /api/bookmarks/{id} - 단건 조회
    @GetMapping("/{id}")
    fun getBookmarkById(@PathVariable id: Long): ResponseEntity<Bookmark> {
        val bookmark = service.getBookmarkById(id)
        return if (bookmark != null) {
            ResponseEntity.ok(bookmark)
        } else {
            ResponseEntity.notFound().build()
        }
    }
    
    // POST /api/bookmarks - 생성
    @PostMapping
    fun createBookmark(@RequestBody bookmark: Bookmark): Bookmark =
        service.createBookmark(bookmark)
    
    // PUT /api/bookmarks/{id} - 수정
    @PutMapping("/{id}")
    fun updateBookmark(
        @PathVariable id: Long,
        @RequestBody bookmarkDetails: Bookmark
    ): ResponseEntity<Bookmark> {
        return try {
            val updated = service.updateBookmark(id, bookmarkDetails)
            ResponseEntity.ok(updated)
        } catch (e: RuntimeException) {
            ResponseEntity.notFound().build()
        }
    }
    
    // DELETE /api/bookmarks/{id} - 삭제
    @DeleteMapping("/{id}")
    fun deleteBookmark(@PathVariable id: Long): ResponseEntity<Void> {
        service.deleteBookmark(id)
        return ResponseEntity.ok().build()
    }
}

Java 대비 코드량 절반!

5) application.properties 설정

src/main/resources/application.properties:

# 서버 포트
server.port=8080

# H2 데이터베이스 (개발용)
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

# JPA 설정
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true

# H2 콘솔 활성화
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

4단계: 실행 및 테스트 (7분)

서버 실행

# Gradle
./gradlew bootRun

# 또는 IntelliJ에서 DemoApplication.kt 실행 (▶️ 버튼)

출력:

Tomcat started on port 8080
Started DemoApplicationKt in 2.123 seconds

API 테스트 (curl)

# 1. 북마크 추가
curl -X POST http://localhost:8080/api/bookmarks \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Kotlin 공식 문서",
    "url": "https://kotlinlang.org",
    "description": "Kotlin 배우기"
  }'

# 응답:
# {"id":1,"title":"Kotlin 공식 문서","url":"https://kotlinlang.org","description":"Kotlin 배우기"}

# 2. 전체 조회
curl http://localhost:8080/api/bookmarks

# 3. 단건 조회
curl http://localhost:8080/api/bookmarks/1

# 4. 수정
curl -X PUT http://localhost:8080/api/bookmarks/1 \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Kotlin 공식 문서 (수정됨)",
    "url": "https://kotlinlang.org/docs",
    "description": "깊이 배우기"
  }'

# 5. 삭제
curl -X DELETE http://localhost:8080/api/bookmarks/1

브라우저에서 테스트

  1. H2 콘솔 접속: http://localhost:8080/h2-console

    • JDBC URL: jdbc:h2:mem:testdb
    • Username: sa
    • Password: (빈칸)
  2. SQL 쿼리:

SELECT * FROM bookmarks;
INSERT INTO bookmarks (title, url, description) 
VALUES ('Google', 'https://google.com', 'Search engine');

5단계: Kotlin의 핵심 문법 (5분)

1) data class (자동 생성)

// Kotlin: 이것만으로 getter/setter/equals/hashCode/toString 자동 생성
data class User(val id: Long, var name: String)

// Java 동등 코드: 50줄+

2) Null 안전성

// Null 불가 타입
val name: String = "홍길동"
// name = null  // 컴파일 에러!

// Null 가능 타입
val description: String? = null
println(description?.length)  // Safe call: null이면 null 반환

// Elvis 연산자
val length = description?.length ?: 0  // null이면 0

Java에서는 항상 NullPointerException 위험!

3) 표현식 함수

// 한 줄 함수
fun add(a: Int, b: Int) = a + b

// 조건식도 표현식
fun max(a: Int, b: Int) = if (a > b) a else b

4) 생성자 주입 (간결함)

// Kotlin: 한 줄
class TodoController(private val service: TodoService)

// Java: 여러 줄
public class TodoController {
    private final TodoService service;
    
    public TodoController(TodoService service) {
        this.service = service;
    }
}

5) 스마트 캐스팅

fun process(value: Any) {
    if (value is String) {
        println(value.length)  // 자동으로 String으로 캐스팅!
    }
}

실무 팁

자주 하는 실수

1) data class에서 var 남용

// ❌ 나쁜 예: 모두 가변
data class User(var id: Long, var name: String)

// ✅ 좋은 예: ID는 불변
data class User(val id: Long, var name: String)

2) !! 연산자 남용 (Null 강제 언래핑)

// ❌ 위험: NullPointerException 가능
val length = description!!.length

// ✅ 안전: Safe call 또는 Elvis
val length = description?.length ?: 0

3) lateinit 잘못 사용

// ❌ nullable한 변수에는 사용 불가
lateinit var name: String?  // 컴파일 에러

// ✅ Non-null 타입에만 사용
lateinit var name: String

디버깅 팁

# application.properties
logging.level.org.springframework=DEBUG
logging.level.com.example=DEBUG

# SQL 로그 상세히
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

IntelliJ 단축키

단축키기능
Ctrl + Shift + KKotlin REPL 실행
Ctrl + Alt + L코드 포맷팅
Alt + EnterQuick fix
Ctrl + Space자동 완성

다음 단계: 실무로 가는 길

이 튜토리얼을 마쳤다면 다음을 학습하세요:

필수 학습 (순서대로)

1) Exception 처리

@RestControllerAdvice
class GlobalExceptionHandler {
    @ExceptionHandler(RuntimeException::class)
    fun handleNotFound(ex: RuntimeException) =
        ResponseEntity.status(404).body(ex.message)
}

2) Validation

data class Bookmark(
    @field:NotBlank(message = "제목은 필수입니다")
    var title: String,
    
    @field:URL(message = "올바른 URL을 입력하세요")
    var url: String
)

@PostMapping
fun create(@Valid @RequestBody bookmark: Bookmark) = ...

3) Kotlin Coroutines (비동기)

@Service
class BookmarkService(private val repository: BookmarkRepository) {
    suspend fun getAllBookmarks() = withContext(Dispatchers.IO) {
        repository.findAll()
    }
}

4) 테스트

@SpringBootTest
class BookmarkControllerTest {
    @Test
    fun `북마크 생성 테스트`() {
        // given, when, then
    }
}

5) 프로덕션 DB (PostgreSQL)

// build.gradle.kts
dependencies {
    runtimeOnly("org.postgresql:postgresql")
}
# application.properties
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=postgres
spring.datasource.password=password
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect

학습 로드맵

Kotlin 입문 튜토리얼 (30분)

Kotlin 시리즈 #1-10 (5시간)

Kotlin Coroutines 학습 (2시간)

실전 프로젝트 (1-2주)

포트폴리오 제작

흔한 에러 및 해결

에러 1: unresolved reference: springframework

# build.gradle.kts 확인
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
}

# 의존성 다시 다운로드
./gradlew clean build --refresh-dependencies

에러 2: lateinit property has not been initialized

// ❌ 초기화 전 사용
lateinit var service: BookmarkService
println(service.getAllBookmarks())

// ✅ 생성자 주입 사용
class Controller(private val service: BookmarkService)

에러 3: Type mismatch: inferred type is Long? but Long was expected

// ❌ Nullable을 Non-null에 할당
val id: Long = bookmark.id  // bookmark.id는 Long?

// ✅ Null 체크 또는 Elvis
val id: Long = bookmark.id ?: 0L

Java vs Kotlin 실전 비교

엔티티 클래스

Java (34줄):

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;
    
    public User() {}
    
    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }
    
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    
    @Override
    public boolean equals(Object o) { ... }
    
    @Override
    public int hashCode() { ... }
    
    @Override
    public String toString() { ... }
}

Kotlin (7줄):

@Entity
@Table(name = "users")
data class User(
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,
    var name: String,
    var email: String
)

80% 코드량 감소!


실전 프로젝트 아이디어

이제 배운 내용으로 실전 프로젝트를 만들어보세요:

초급:

  • 간단한 북마크 관리 앱
  • 메모장 API
  • URL 단축 서비스

중급:

  • JWT 인증이 있는 TODO 앱
  • 파일 업로드 서비스
  • 실시간 채팅 (Kotlin Coroutines)

고급:

  • 마이크로서비스 (Spring Cloud)
  • Kafka 이벤트 스트리밍
  • Redis 캐싱 + PostgreSQL

빠른 레퍼런스

주요 애노테이션

애노테이션용도Java와 동일
@RestControllerREST API 컨트롤러
@GetMappingGET 요청
@PostMappingPOST 요청
@RequestBodyJSON → 객체
@PathVariableURL 경로 변수
@Service비즈니스 로직
@RepositoryDB 접근
@EntityJPA 엔티티

Kotlin 고유 문법

// Null 안전
val name: String? = null
println(name?.length ?: 0)

// data class
data class User(val id: Long, var name: String)

// 표현식 함수
fun add(a: Int, b: Int) = a + b

// 생성자 주입
class Service(private val repo: Repository)

// when 표현식 (switch 대체)
when (status) {
    200 -> "OK"
    404 -> "Not Found"
    else -> "Error"
}

Spring Boot 내부와 프로덕션에 가까운 메모

서블릿 스택과 임베디드 Tomcat

Spring Boot Web은 기본적으로 서블릿 컨테이너(임베디드 Tomcat 등) 위에서 동작합니다. HTTP 요청은 커넥터 스레드 풀에서 받고, 디스패처 서블릿이 컨트롤러 매핑으로 넘깁니다. 블로킹 I/O 모델에서는 동시 처리량이 스레드 풀 크기와 직결되므로, 장시간 점유하는 요청이 많으면 스레드 고갈이 납니다. 이때는 WebFlux·비동기 서블릿·외부 워커 큐 같은 아키텍처 변경을 검토합니다.

설정 계층: application.yml과 프로파일

실제 서비스에서는 application-dev.yml, application-prod.yml처럼 프로파일로 DB URL·로그 레벨을 나눕니다. 시크릿은 저장소에 평문으로 두지 않고 환경 변수·시크릿 매니저로 주입합니다.

관측: Actuator와 헬스

운영 환경에서는 spring-boot-starter-actuator/health, /metrics를 열고, Kubernetes라면 liveness/readiness 프로브와 연결합니다. 데이터베이스가 끊긴 상태에서도 프로세스는 살아 있을 수 있으므로 readiness에 DB 검사를 넣는지 팀 규약을 정합니다.

트러블슈팅 빠른 표

증상점검
Whitelabel Error Page 500서버 로그 스택 트레이스, @ControllerAdvice 없을 때 기본 페이지
포트 충돌server.port, 다른 로컬 프로세스 점유
JPA가 테이블을 안 만듦ddl-auto, 엔티티 스캔 패키지, DB 권한
CORS 에러@CrossOrigin 또는 API 게이트웨이에서 헤더 — 브라우저만의 증상임을 기억

마무리

핵심 정리

  1. Kotlin은 Java보다 30-40% 코드 감소
  2. data class로 엔티티 한 줄 정의
  3. Null 안전성으로 NullPointerException 방지
  4. Spring Boot의 모든 기능 100% 호환

30분 동안 배운 것

  • Kotlin + Spring Boot 프로젝트 생성
  • REST API 4가지 (GET, POST, PUT, DELETE)
  • data class로 엔티티 정의
  • JPA로 데이터베이스 연동
  • H2 인메모리 DB 사용

Kotlin을 선택해야 하는 이유

Java 대비 장점:

  • ✅ 코드량 30-40% 감소
  • ✅ Null 안전성 (컴파일 타임 체크)
  • ✅ 간결한 문법 (data class, 표현식)
  • ✅ Coroutines (비동기 처리)
  • ✅ 100% Java 호환

채용 시장:

  • Coupang, Kakao, Toss, 당근마켓, 배달의민족
  • 스타트업에서 빠르게 도입 중
  • Android + 백엔드 동시 개발 가능

축하합니다! 첫 Kotlin Spring Boot API를 만들었습니다. 이제 Kotlin 시리즈 #9 Spring Boot로 넘어가 실무 수준을 익히세요.


관련 글 (내부 링크)

Kotlin과 함께 보면 좋은 백엔드 개발 가이드입니다:

  • Spring Boot 입문 튜토리얼 | Java 버전 - Java 버전과 비교
  • Kotlin 시리즈 #9 Spring Boot | REST API 서버 - 더 상세한 설명
  • Kotlin 시리즈 #1 Kotlin 입문 | 기초 문법 - Kotlin 기초
  • Kotlin Coroutine vs Thread 비교 - 비동기 처리
  • Java 시리즈 #10 Spring Boot - Java 버전
  • Supabase 완벽 가이드 - 백엔드 대안

한 줄 요약: Kotlin은 Java보다 30-40% 적은 코드로 Spring Boot API를 만들 수 있습니다. data class와 Null 안전성으로 생산성이 높아 최근 백엔드 채용에서 주목받고 있습니다.

심화 부록: 구현·운영 관점

이 부록은 앞선 본문에서 다룬 주제(「Kotlin Spring Boot 입문 | 30분 만에 REST API 만들기 [초보자용]」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.

내부 동작과 핵심 메커니즘

flowchart TD
  A[입력·요청·이벤트] --> B[파싱·검증·디코딩]
  B --> C[핵심 연산·상태 전이]
  C --> D[부작용: I/O·네트워크·동시성]
  D --> E[결과·관측·저장]
sequenceDiagram
  participant C as 클라이언트/호출자
  participant B as 경계(런타임·게이트웨이·프로세스)
  participant D as 의존성(API·DB·큐·파일)
  C->>B: 요청/이벤트
  B->>D: 조회·쓰기·RPC
  D-->>B: 지연·부분 실패·재시도 가능
  B-->>C: 응답 또는 오류(코드·상관 ID)
  • 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
  • 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
  • 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
  • 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.

프로덕션 운영 패턴

영역운영 관점 질문
관측성요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가
안전성입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가
신뢰성재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가
성능캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가
배포롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가
용량피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가

스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.

확장 예시: 엔드투엔드 미니 시나리오

앞선 본문 주제(「Kotlin Spring Boot 입문 | 30분 만에 REST API 만들기 [초보자용]」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.

  1. 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
  2. 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
  3. 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
  4. 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
  5. 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
  ctx = newCorrelationId()
  validated = validateSchema(request)
  authorize(validated, ctx)
  result = domainCore(validated)
  persistOrEmit(result, idempotentKey)
  recordMetrics(ctx, latency, outcome)
  return result

문제 해결(Troubleshooting)

증상가능 원인조치
간헐적 실패레이스, 타임아웃, 외부 의존성, DNS최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검
성능 저하N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거
메모리 증가캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납상한·TTL·힙/FD 스냅샷 비교
빌드·배포만 실패환경 변수, 권한, 플랫폼 차이, lockfileCI 로그와 로컬 diff, 런타임·이미지 버전 핀
설정 불일치프로필·시크릿·기본값, 리전스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화
데이터 불일치비멱등 재시도, 부분 쓰기, 캐시 무효화 누락멱등 키·아웃박스·트랜잭션 경계 재검토

권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.

배포 전에는 git addgit commitgit pushnpm run deploy 순서를 권장합니다.