딜리셔스가 Java 로 만든 프로젝트를 Kotlin 으로 전환한 기록이다. 차기 프로젝트에 Kotlin 을 쓰기로 정한 뒤 실무 경험을 쌓으려고 기존 프로젝트를 옮겼고, 전환 중 마주친 Java 와 Kotlin 혼용 구간의 문제들을 항목별로 정리했다.
핵심 포인트- 목표는 둘이었다. 실제 프로젝트 코드에 써서 Kotlin 에 익숙해지는 것과 팀 차원의 전환 경험을 쌓는 것이다.
- 사전 준비로 Kotlin 과 Spring Boot 공식 문서, 다른 회사 전환 사례를 조사했다. Java util 패키지 대신 Kotlin 내장 라이브러리로 바꾸는 편이 좋다는 것도 그중 하나다.
- IntelliJ 의 Show Kotlin Bytecode 와 Decompile 로 지금 Kotlin 코드가 Java 로 어떻게 되는지 확인하며 진행하면 에러를 잡기 쉽다.
- 컴파일러 플러그인 둘이 필수다. no-arg 는 JPA Entity 초기화에 필요한 매개변수 없는 생성자를 만들어주고, all-open 은 기본이 final 인 클래스에 open 을 붙여 CGLIB 프록시 생성을 가능하게 한다.
- 원래 전략은 Domain 과 Use case 를 먼저 옮기고 UI 와 Infra 를 나중에 옮기는 것이었지만 적용이 어려웠다.
상세 정리- 순수 Domain 이 바뀌면 그에 의존하는 Infra 변경이 따라와야 해서, 특정 Domain 영역과 그것을 의존하는 Service, Infra 를 동시에 고치는 방식으로 수정했다.
- 병합 충돌을 줄이려고 각자 익숙한 패키지를 배타적으로 나눠 맡고 진행 상태를 볼 수 있는 장표를 만들었다.
- Kotlin Migration 공유 문서를 만들어 전환 중 겪은 이슈와 알게 된 것을 정리했다. 같은 문제를 다른 팀원이 또 조사하는 시간을 줄이기 위해서다.
- 이슈 1은 nullable 과 Optional 의 충돌이다. Java 에서 Optional 이던 타입을 Kotlin nullable 로 바꾸면 아직 전환되지 않은 Java 쪽에서 문제가 생긴다.
- 이슈 2는 static 메서드다. IntelliJ 의 자동 변환은 companion object 로 바꿔놓는데, Java 에서 기존처럼 호출하려면 어노테이션이 필요하고 아니면 Companion 을 거쳐야 한다. 이미 전환된 Kotlin 을 고치기보다 전환 예정인 Java 를 바꾸는 쪽이 유리하다고 판단했다.
- 이슈 3은 테스트다. Kotlin 클래스는 기본이 final 이라 Java 용 모킹 라이브러리로는 모킹이 안 된다. 이를 가능하게 해주는 도구가 필요하다.
- 이슈 4는 Jackson 이다. Primary Constructor 안에서 프로퍼티를 선언하는 방식이 기본 생성자가 없다는 오류를 부르며, 별도 모듈로 해결한다.
- 이슈 5는 인터페이스다. Java 인터페이스의 default 메서드와 Kotlin 인터페이스 구현 메서드는 바이트코드가 다르다. Kotlin 은 내부적으로 DefaultImpls 클래스를 만들어 구현한다.
- 호환용 컴파일 옵션을 켜는 것은 지양했기에, CrudRepository 를 상속한 인터페이스를 스프링 컴포넌트로 선언하고 클래스에 주입하는 우회로 해결했다.
- 가장 신경 쓴 부분은 Java 코드에 명시돼 있지 않은 null 가능성을 전부 확인하는 일이었다. API 요청과 외부 API 응답의 null 여부를 알아야 프로퍼티를 nullable 로 둘지 정할 수 있다.
- 그래서 전환한다면 요청부터 Controller, Service, Domain, Infra, 외부 API 응답까지 수직으로 훑는 방식을 권한다.
- Java 라이브러리에 붙은 nullability 어노테이션이 전환에 큰 도움이 되니 꼭 확인하라고 덧붙인다.
- 얻은 것으로는 getter 가 사라져 프로퍼티에 점으로 접근해 실제 속성처럼 느껴진다는 점, 그리고 생성자 기본값과 이름 붙인 인자 덕에 여러 개의 생성자를 크게 줄일 수 있었다는 점을 꼽는다.
왜 읽나Java 프로젝트를 Kotlin 으로 옮기려는 백엔드 팀에게, 혼용 구간에서 반드시 만나는 다섯 가지 충돌과 전환 순서에 대한 권고를 정리해주는 기록.