Backend/Spring Framework

[Spring Boot] 자바로 작성된 프로젝트를 코틀린으로 리펙토링 하면서 느낀 점

mopil 2022. 7. 18. 16:13
반응형

# 서론

스프링 부트를 자바로 작성하다보면 DTO를 만들때 클래스가 매우 많이 생성되서 한 눈에 보기 불편한 경우가 생겼다.

 

DTO를 코틀린으로 작성하면 얻는 이점 : 하나의 코틀린 파일에 여러개의 DTO 클래스를 data class로 작성할 수 있다.

 

그래서 코틀린을 도입하여 리펙토링을 진행해보려 하면서 느낀점을 정리하려고 글을 쓴다.

* Ektour 리뉴얼 프로젝트에 적용함으로써 느낀 느낀점을 작성함.

 

참고한 글

https://d2.naver.com/helloworld/6685007

 

 

# 코틀린을 도입하면서 직면한 문제

 

우선, DTO를 간략화 하기위해서 코틀린을 도입했으므로, DTO 부분 먼저 리펙토링을 진행하였다.

 

0. 코틀린으로 static 메소드 작성하기

코틀린으로 작성된 객체를 편의 메소드 (엔티티 -> DTO 변환 메소드 등)를 작성하려 할 때, 보통 static 메소드로 구현을 했는데 코틀린에서는 static 문법이 없기 때문에 companion object를 활용해서 구현해야한다.

@Getter @AllArgsConstructor
public class ErrorResponse {

    private String code;
    private String message;

    public static ErrorResponse convertJson(FieldError error) {
        return new ErrorResponse(ErrorCode.VALIDATION_ERROR, error.getDefaultMessage());
    }

    public static ArrayList<ErrorResponse> convertJson(List<FieldError> bindingResults) {
        ArrayList<ErrorResponse> result = new ArrayList<>();
        for (FieldError e : bindingResults) {
            result.add(ErrorResponse.convertJson(e));
        }
        return result;
    }
}

기존 자바로 작성된 ErrorResponse 공통 DTO 클래스이다. BindingResult 결과를 Json으로 변환해서 내리는 역할을 수행하는 편의메소드 convertJson을 코틀린으로 구현하려면 다음과 같이 해야한다.

data class ErrorResponse(
    val code: String = "",
    val message: String = "",
) {
    companion object {
        fun convertJson(error: FieldError): ErrorResponse {
            return ErrorResponse(VALIDATION_ERROR, error.field + " / " + error.defaultMessage)
        }

        fun convertJson(bindingResults: List<FieldError?>): ErrorListResponse {
            val result = ArrayList<ErrorResponse>()
            for (e in bindingResults) {
                e?.let { this.convertJson(it) }?.let { result.add(it) }
            }
            return ErrorListResponse(result)
        }
    }

}

그런데 이러면 해당 메소드를 호출할 때, ErrorResponse.Companion.convertJson() 형태로 호출해야하서 다소 불편하다.

따라서 함수 앞에 @JvmStatic 어노테이션을 붙혀주면 기존 자바 static 처럼 사용이 가능하다.

data class ErrorResponse(
    val code: String = "",
    val message: String = "",
) {
    companion object {
        @JvmStatic fun convertJson(error: FieldError): ErrorResponse {
            return ErrorResponse(VALIDATION_ERROR, error.field + " / " + error.defaultMessage)
        }

        @JvmStatic fun convertJson(bindingResults: List<FieldError?>): ErrorListResponse {
            val result = ArrayList<ErrorResponse>()
            for (e in bindingResults) {
                e?.let { this.convertJson(it) }?.let { result.add(it) }
            }
            return ErrorListResponse(result)
        }
    }

}

 

1. 코틀린에서 롬복 사용 불가능

이는 컴파일 시점에 관련된 문제로, 코틀린 코드를 컴파일러가 컴파일하고, 자바 컴파일러가 자바를 컴파일 한 뒤, Annotation Processing 단계에서 롬복 어노테이션이 적용된다. 따라서 코틀린에서는 자바에서 작성된 롬복 관련 코드를 접근 할 수 없다.

 

이 문제는 엔티티를 DTO로 변환하는 메소드에서 빌더를 사용해서 만들때 발생한다. (현재 엔티티는 자바로, DTO는 코틀린으로 작성되어있다.)

data class EstimateSimpleResponse(
    var id: Long = 0L,
    var name: String = "",
    var travelType: String = "",
    var departPlace: String = "",
    var arrivalPlace: String = "",
    var vehicleType: String = "",
    var createdDate: String = "",
) {
    companion object {
        @JvmStatic fun createDto(e: Estimate) {
            return EstimateSimpleResponse(id = e.id)
        }
    }
}

이런식으로 자바로 작성된 엔티티인 Estimate의 id에 접근하려면, 롬복의 도움을 받아 @Getter가 생성되어 있어야 하는데, 컴파일 시점의 차이로인해 Estimate의 id에 코틀린 코드에서는 접근할 수 없다.

 

그래서 다음 두 가지 방법을 생각했다.

  1. 엔티티도 코틀린으로 리펙토링 한다.
  2. 엔티티에 Getter를 롬복 도움없이 작성한다.

1번 방법은 다음과 같은 문제를 야기시켰다.

현재, 프론트엔드를 리액트로 작성된 폴더를 스프링 부트에 통합시켜서 빌드, 배포 하고 있다. 이 때 통합빌드 스크립트를 Gradle에 Groovy DSL로 작성되어있는데, 코틀린 환경에서 JPA를 사용하려면 몇 가지 추가적인 세팅을 해줘야한다. (이 때 Kotlin DSL로 작성한다.) 따라서 현재 작성된 Gradle 스크립트를 마이그레이션해야하는데, Kotlin DSL를 사용을 안 해봐서... 공부를 더 해야한다.

 

2번 방법은 Getter를 너무 많이 생성해서 코틀린을 도입해서 간략화 하는데 의미가 사라지기 때문에 논외다.

 

 

3. Service, Controller 단을 코틀린으로 리펙토링 해야할까?

DTO는 하나의 코틀린 파일로 작성하면 한 눈에 보기 쉽워서 간략화 하는데 도움이 되지만, 굳이 잘 작성된 자바 Service나 Controller를 코틀린으로 리펙토링 해야하는지에 대한 의문이 생겼다.

만약 코틀린을 도입하면 롬복을 사용할 수 없으므로, @Slf4J를 사용할 수 없다. 즉, 로거를 직접 만들어서 사용해야하는 번거로움이 존재한다.

그리고 코틀린으로 리펙토링해도 큰 차이가 없음을 발견해서 해당 부분은 그냥 자바 코드로 유지하기로 했다.

 

 

# 결론

  • DTO나 Custom Exception 처럼 반복되고 간략하며 다른 자바 코드와 결합도가 낮은 클래스들은 코틀린 파일로 하나의 파일로 작성하는게 좋은 것 같다.
  • 기존 자바로 작성된 Controller, Service 단은 코틀린을 도입해서 드라마틱한 리펙토링이 이루어지지 않는 이상, 코틀린으로 얻는 이점이 생각보다 크지 않는 것 같다.
  • build.gradle을 build.gradle.kts로 마이그레이션 하는 것은 조금 까다롭기 때문에 애초에 처음부터 코틀린으로 프로젝트를 생성하자.

 

 

 
반응형