2. Spring MVC를 Kotlin답게 작성하는 방법

2026. 8. 3. 00:13·Language·CS/Java·Kotlin

이 글은 Velog에서 이전한 글입니다. Velog 원문 보기

Java 기반으로 Spring MVC를 작성해오다가 Kotlin으로 전환하면서, 코드 구조가 훨씬 단순해졌습니다.
특히 Controller, Service, Repository 흐름이 Kotlin 문법과 자연스럽게 맞물리면서 불필요한 보일러 플레이트 코드가 많이 줄었습니다.

이번 글에서는 Spring MVC 구조를 Kotlin답게 작성하는 방법을 실제 코드 중심으로 정리했습니다.


Controller / Service / Repository 기본 구조

Kotlin으로 Spring 코드를 작성하면 가장 먼저 달라지는 부분이 "클래스 선언 방식"이었어요.

Controller

@RestController
@RequestMapping("/users")
class UserController(
    private val userService: UserService
) {

    @PostMapping
    fun create(@RequestBody request: CreateUserRequest): UserResponse {
        return userService.create(request)
    }
}

Kotlin답게 만든 포인트

  • 생성자에 private val을 선언해 의존성을 바로 주입
  • 반환 타입 생략 가능하지만, Controller는 타입을 명시하는 것이 좋아 명시했습니다.
  • 불필요한 getter/setter 없음

Service

@Service
class UserService(
    private val repository: UserRepository
) {

    fun create(request: CreateUserRequest): UserResponse {
        val user = repository.save(User(request.name, request.age))
        return UserResponse(user)
    }
}

Kotlin답게 만든 포인트

  • 함수가 "값을 반환하는 단일 표현식"으로 가볍게 구성
  • 함수 시작부터 핵심 로직이 바로 보이는 구조

Repository

interface UserRepository : JpaRepository<User, Long>

Repository는 Kotlin에서도 거의 동일하지만, nullable 반환과 함께 더 깔끔한 처리 패턴을 사용하게 됩니다.


Nullable 기반 CRUD 패턴

Java에서 자주 쓰던 Optional은 Kotlin에서 nullable 타입이 대체해요.

Java:

User user = repository.findById(id).orElseThrow(() 
            -> new CustomException());

Kotlin:

val user = repository.findById(id).orElse(null)
    ?: throw CustomException()

Kotli식 CRUD 패턴 요약

  • .orElse(null)로 nullable 변환
  • 바로 ?:(Elvis)로 예외 처리
  • null-safe 로직이 자연스럽게 흐름에 녹아들어요

Update 패턴도 훨씬 깔끔해졌습니다.

fun update(id: Long, request: UpdateUserRequest): UserResponse {
    val user = repository.findById(id).orElse(null)
        ?: throw UserNotFoundException()

    user.update(request) // 도메인 객체에 변경 메서드 작성
    return UserResponse(user)
}

예외 처리-Guard 로직 단순화

Kotlin에서는 Guard 로직이 훨씬 단순해져요.

Java:

if (user == null) {
    throw new UserNotFoundException();
}

Kotlin:

val user = repository.findById(id).orElse(null)
    ?: throw UserNotFoundException()

또한 when 표현식을 활용하면 예외 분기가 명확해졌습니다.

val status = when {
    // 1. 사용자가 차단 상태라면
    user.isBlocked -> throw BlockedUserException()

    // 2. 차단 상태는 아니지만 삭제된 상태라면
    user.isDeleted -> throw DeletedUserException()

    // 3. 위 두 조건에 모두 해당하지 않으면
    else -> "ACTIVE"
}

조건에 따라 예외를 던지는 로직이 명확하고 읽기 좋습니다.

정리하면:

  • 차단된 유저면 → BlockedUserException 던지고 끝
  • 삭제된 유저면 → DeletedUserException 던지고 끝
  • 둘 다 아니면 → "ACTIVE" 문자열을 status에 대입

개인적으로 해당 구조가 흥미로워서 자세히 정리해봤어요.

when을 "표현식"으로 사용하는 코드

Kotlin의 when은 단순한 제어문이 아니라 값을 반환하는 표현식이에요.

  • val status = when {...}
  • 즉, when {...} 전체가 하나의 값이 되고, 그 값이 status에 들어가요.

Java의 swich와 가장 큰 차이는 바로 이 표현식이라는 점입니다.

이 패턴은 주로 이런 곳에서 쓰기 좋습니다:

  • 로그인/인증 시 계정 상태 체크
  • 특정 API 호출 전에 유저 상태 검증
  • 주문/결제 등 상태 기반 비즈니스 로직 초반 Guard 체크

예를 들어 Service 내부에서

fun getUserStatus(user: User): String {
    return when {
        user.isBlocked -> throw BlockedUserException()
        user.isDeleted -> throw DeletedUserException()
        else -> "ACTIVE"
    }
}

이렇게 쓰면, 메서드 시작부에서 상태를 깔끔하게 정리할 수 있고
추가 조건이 생겨도 when에 줄만 하나씩 추가해주면 됩니다.


Scope Functions를 Spring 로직에 적용하는 기준

Kotlin의 Scope Functions(let/run/apply/also/with)은 강력하지만, 역할을 잘못 사용하여 오히려 혼란스러워요.
그래서 Spring 로직 기준으로 어떤 상황에서 어떤 함수를 쓰는지 정리해봤습니다.

let: null-safe 후 처리

조건: nullable 처리 + 변환 작업

val dto = user?.let { UserResponse(it) }

run: 객체 작업을 블록으로 묶을 때

조건: 현재 객체 기반으로 계산해 값을 반환해야 할 때

val result = user.run {
    UserResponse(name, age)
}

apply: 빌더처럼 값을 설정할 때

조건: 객체 초기화 + 설정 작업

val user = User().apply {
    name = "상민"
    age = 25
}

also: "사이드 이펙트"를 넣고 싶을 때

조건: 로그, 임시 출력, 디버깅

val saved = repository.save(user).also {
    println("Saved: $it")
}

with: DSL처럼 사용하고 싶을 때

조건: 특정 객체에 여러 메서드를 호출해야 할 때

with(user) {
    println(name)
    println(age)
}

실제 CRUD 서비스 전체를 Kotlin 스타일로 재작성

전체 흐름을 Kotlin 스타일로 구성하면 다음과 같았어요.

Request DTO

data class CreateUserRequest(
    val name: String,
    val age: Int
)

Java의 record와 비슷합니다.

Entity

@Entity
class User(
    var name: String,
    var age: Int
) {
    fun update(request: UpdateUserRequest) {
        name = request.name
        age = request.age
    }
}

Service

@Service
class UserService(
    private val repository: UserRepository
) {

    fun create(request: CreateUserRequest): UserResponse =
        repository.save(User(request.name, request.age))
            .run { UserResponse(this) }

    fun update(id: Long, request: UpdateUserRequest): UserResponse {
        val user = repository.findById(id).orElse(null)
            ?: throw UserNotFoundException()

        user.update(request)
        return UserResponse(user)
    }

    fun get(id: Long): UserResponse {
        val user = repository.findById(id).orElse(null)
            ?: throw UserNotFoundException()
        return UserResponse(user)
    }
}

Controller

@RestController
@RequestMapping("/users")
class UserController(
    private val service: UserService
) {

    @PostMapping
    fun create(@RequestBody request: CreateUserRequest) =
        service.create(request)

    @GetMapping("/{id}")
    fun get(@PathVariable id: Long) =
        service.get(id)

    @PutMapping("/{id}")
    fun update(
        @PathVariable id: Long,
        @RequestBody request: UpdateUserRequest
    ) = service.update(id, request)
}

Repository

interface UserRepository : JpaRepository<User, Long>

마무리

Kotlin으로 Spring MVC를 작성해보니,
불필요한 코드가 사라지고 핵심 로직이 더 잘 드러나는 구조로 자연스럽게 변화했어요.

nullable 처리, 표현식 기반 문법, scope functions 등을 적절히 활용하면
전체 애플리케이션의 가독성이 크게 좋아졌습니다.

다음 글에서는 Kotlin 기반 Spring 코드에서 확장 함수(Extension Function)를 어떻게 활용해 구조를 더 단순하게 만들 수 있는지 정리해볼 예정입니다.

'Language·CS > Java·Kotlin' 카테고리의 다른 글

코루틴 기반 Spring 비동기 처리 (With WebFlux)  (0) 2026.08.03
Kotlin + Spring Boot 프로젝트 환경 구성  (0) 2026.08.03
3. 확장 함수(Extension Function)로 Spring 코드 최적화  (0) 2026.08.03
1. Kotlin을 처음 쓰는 Spring 개발자를 위한 핵심 문법 가이드  (0) 2026.08.03
'Language·CS/Java·Kotlin' 카테고리의 다른 글
  • 코루틴 기반 Spring 비동기 처리 (With WebFlux)
  • Kotlin + Spring Boot 프로젝트 환경 구성
  • 3. 확장 함수(Extension Function)로 Spring 코드 최적화
  • 1. Kotlin을 처음 쓰는 Spring 개발자를 위한 핵심 문법 가이드
pp8817
pp8817
공부한 내용, 개발 관련 지식, 트러블 슈팅 등을 기록합니다. 이전 블로그: https://velog.io/@pp8817/posts
  • pp8817
    끄적이는 개발 log
    pp8817
  • 전체
    오늘
    어제
    • 분류 전체보기 (273)
      • Project (71)
        • 척척학사 (29)
        • Book (23)
        • Saynow (3)
        • YAPP 27기 (4)
        • 나의 작은 프로젝트 (10)
        • Landit (2)
      • Backend (88)
        • Spring (13)
        • Spring MVC (13)
        • Spring Security (4)
        • JPA (26)
        • Database (19)
        • HTTP·Web (13)
        • Architecture (0)
      • Language·CS (59)
        • Java·Kotlin (5)
        • CS Interview (17)
        • Backend Interview (5)
        • Concepts (32)
      • Algorithm (25)
        • Algorithm (24)
        • 소마 알고리즘 스터디 (1)
      • Infra (8)
      • Troubleshooting (11)
      • Retrospective (6)
      • Etc (5)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    BOJ
    OS
    Algorithm
    게시판
    jpa
    interview
    개념 정리!
    http
    Book
    HTTP WEB 기본 지식
    척척학사
    object
    CS Interview
    Project
    Spring
    나의 작은 프로젝트
    java
    Python
    Spring MVC
    트러블슈팅
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
pp8817
2. Spring MVC를 Kotlin답게 작성하는 방법
상단으로

티스토리툴바