들어가며
저번 1편에서 구현에 앞서 MSA의 특징와 의의에 대해서 알아보았다. 이번 편에서는 MSA에서 많은 엔터프라이즈 기업들이 Java 대신 Kotlin을 채택하는 이유와 그에 따른 장점과 사용 예시에 대해 알아보자.
Kotlin의 생산성과 안정성
더하여 Spring 생태계는 Java 기반으로 탄생했지만 이제 Spring boot 3.0 이상에서는 Kotlin을 1순위로 지원하며 코틀린을 적극적으로 활용하고 있다.
생산성: 더 적은 코드로 더 많은 일을 하라
Java와 Kotlin의 가장 큰 차이점은 코드의 간결함이다.
- 데이터 클래스: 이커머스 시스템의 DTO(Data Transfer Object)나 도메인의 모델을 생각해보자. Java에서는
equals(),hashCode(),toString(),getter/setter등을 구현하기 위해 수십줄의 보일러 플레이트가 발생한다.
// Java (with Lombok)
@Getter
@EqualsAndHashCode
@ToString
public class OrderDto {
private final Long orderId;
private final String productName;
private final Long amount;
public OrderDto(Long orderId, String productName, Long amount) {
this.orderId = orderId;
this.productName = productName;
this.amount = amount;
}
}
반면 Kotlin은 한 줄로 작성할 수 있다. data 키워드 하나에 equals(), hashCode(), toString(), 그리고 copy() 메서드까지 자동으로 생성된다.
// Kotlin
data class OrderDto(
val orderId: Long,
val productName: String,
val amount: Long
)
- 불변성(Immutability): 코틀린은 기본적으로 불변성을 권장한다. 읽기 전용인
val, 변경 가능한var을 명확히 구분한다. MSA 환경에서는 여러 서비스가 데이터를 주고받거나 여러 스레드가 동시에 데이터에 접근할 수 있으므로, 변경 불가능한 객체를 사용하는 것이 시스템의 안정성을 극적으로 높여준다. 위 OrderDto 예시의 val 키워드가 바로 그것이다.
I call it my billion-dollar mistake (Tony Hoare)
안정성: 실수를 방지하라
Java 개발자를 가장 고통스럽게하는 예외는 NPE일 것이다. MSA 환경에서 주문 서비스가 회원 서비스로부터 응답을 받았는데, 특정 필드가 null이어서 시스템 전체가 멈춘다면 끔찍한 일이다.
코틀린은 타입 시스템(Type System) 자체에 Null 안정성을 내장햇다.
-
Nullable vs Non-Nullable: 코틀린은 타입 선언 시 ? 기호를 통해 이 변수가 null을 허용하는지 아닌지 컴파일 시점에 명시해야한다.
-
안전한 호출 (Safe Call): null일 수도 있는 타입(String?)은 ?. 연산자를 사용해야만 메서드를 호출할 수 있다. 만약 객체가 null이면, 메서드를 호출하는 대신 null을 반환하고 프로그램은 멈추지 않는다.
이처럼 코틀린은 런타임(Runtime)에 터질 NPE를 컴파일(Compile) 시점에 미리 제거하도록 강제한다. 이는 특히 복잡한 도메인 로직을 다루는 이커머스 백엔드에서 서비스의 안정성을 보장하는 가장 강력한 무기다.
코루틴: 더 적은 자원으로 많은 트래픽을 처리하라
현대 MSA는 대규모 트래픽을 효율적으로 처리하기 위해 비동기/논블로킹(Async/Non-blocking) 패러다임을 요구한다. Java의 CompletableFuture나 RxJava같은 리액티브 프로그래밍은 강력하지만, 콜백 헬로 인해 코드가 복잡해지고 가독성이 떨어지는 문제가 있다.
코틀린은 코루틴이라는 독보적인 동시성 모델을 제공한다. 코루틴을 사용하면, 비동기/논블로킹 코드를 마치 동기식 코드처럼 쉽고 간결하게 작성할 수 있다. 이는 개발생산성과 시스템 성능이라는 두 마리 토끼를 동시에 잡는 기능이다.
결론적으로 코틀린은 JVM 생태계를 그대로 유지하면서, 더 간결한 문법과 Null 안정서, 그리고 코루틴을 제공한다.
코틀린이 Spring boot와 만나게 된다면
바로 위에서 왜 코틀린이 자바보다 생산성과 안정성 측면에서 더 나은선택인지 살펴보았다. 그렇다면 이 장점들이 실제 Spring boot 환경에서 어떤 강점이 생기는지 생각해보자.
코틀린은 기본적으로 함수형 프로그래밍의장점과 객체 지향 프로그래밍의 실용성을 ‘잘’ 결합한 언어이다. Spring Framework는 코틀린은 1순위 언어로 받아들였다. 이는 스프링의 핵심 철학이 코틀린의 설계 철학과 맞닿아 있기 때문이다.
이는 다음 4가지로 정리할 수 있다.
-
불변성(Immutability) 중심 설계 스프링 핵심 원칙 중에 하나는 상태가 없는 Bean을 통한 동시성 관리이다. 코틀린은 val과 data 클래스를 통해 Immutable한 Object를 만드는 것을 기본으로 권장한다. 이는 여러 스레드가 동시에 접근해도 안전한 컴포넌트를 설계하도록 자연스럽게 유도하며, 복잡한 MSA 환경에서 예측 가능하고 안정적인 서비스를 만드는 기반이 된다.
-
Null 안정성을 통한 방어적 프로그래밍의 내재화 Spring으로 개발할 때 우리는
@Autowired나@RequestParam등이 null이 아닐 것이라 가정하고 코드를 작성한다. 하지만 자바에서는 언제 어디서 NPE가 발생할지 모른다. 코틀린의 Null 안정성은 가정을 컴파일 시점의 보증으로 바꿔준다. 이는 개발자가if (obj != null)같은 방어적 코드를 붙이지 않아도 시스템이 런타임에 안정적으로 동작할 것을 보장해준다. -
확장 함수(Extension Functions)를 통한 ‘관심사의 분리’ Spring을 사용하다 보면 비즈니스 로직과 프레임워크 로직이 섞이기 쉽다. 예를 들어, Order라는 순수한 도메인 객체가 DTO로 변환되기 위해 Spring에 의존적인 코드를 포함하게 될 수 있다. 코틀린의 확장 함수를 사용하면 원본 Order 클래스를 수정하지 않고도
Order.toDTO같은 변환 로직을 외부 파일에 깔끔하게 분리해낼 수 있다. 이는 도메인 모델의 순수성을 지키는 도구이다. -
코루틴을 통한 비동기/논블로킹의 단순화 스프링의 차세대 reactive 스택은 webflux는 강력하지만 Mono와 Flux라는 새로운 모델을 학습해야하는 러닝커브가 존재한다. 하지만 스프링은 코틀린 코루틴을 완벽하게 지원한다. 덕분에 우리는 suspend 키워드 하나 만으로 기존의 동기식 스프링 MVC 코드와 거의 동일한 구조를 윶하면서도 논블로킹의 성능을 달성할 수 있다.