Backend/Kotlin

[Kotlin] 코틀린에서 Mapstruct 사용하기

findmypiece 2022. 4. 5. 14:26
728x90

Mapstruct 를 사용할 경우 객체간 매핑을 자동으로 해주기 때문에 JPA 를 사용하는 환경처럼 DTO, Entity 간 변환이 잦은 경우 유용하게 사용될 수 있다. 하지만 코틀린 환경에서는 몇가지 제약사항이 있다.

 

자바 환경에서는 아래와 같이 사용할 수 있었다.

@Mapper(componentModel = "spring",
        unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface PushReqInfoMapper {

    @Mapping(target = "eventId", source = "makeIdReqInfo.eventType")
    @Mapping(target = "pushOtherInfo", expression = "java(toPushOtherInfo(makeIdReqInfo))")
    PushReqInfo toPushReqInfo(MakeIdReqInfo makeIdReqInfo, SendCnd sendCnd);

    default PushReqInfo.PushOtherInfo toPushOtherInfo(MakeIdReqInfo makeIdReqInfo) {
        return PushReqInfo.PushOtherInfo.builder()
                .val((String)makeIdReqInfo.getLinkInfo().get("val"))
                .licon((String)makeIdReqInfo.getUserDefine().get("largeIcon"))
                .action((String)makeIdReqInfo.getUserDefine().get("action"))
                .imgUrl((String)makeIdReqInfo.getLinkInfo().get("imgUrl"))
                .msgType((String)makeIdReqInfo.getLinkInfo().get("msgType"))
                .build();
    }

}

Mapstruct 는 위와 같이 매핑을 담당할 인터페이스를 만들고 그 안에 source 를 파라미터로 하고 target을 리턴타입으로 한 추상메소드만 정의해 놓으면 된다. 이렇게 되면 변수명이 동일할 경우 자동으로 값이 매핑되어 변환된다.

 

그런데 변수명이 다르거나 변환로직을 커스텀하게 가져가고 싶은 경우 추상메소드에 @Mapping 어노테이션을 추가해서 souce 쪽에서 참조할 변수를 수동으로 지정하거나 expression 으로 변환값을 만드는 함수를 직접 지정할 수도 있었다.

 

코틀린에서 사용할 경우 일단 Gradle 기준 필요한 의존성은 아래와 같다.

implementation("org.mapstruct:mapstruct:1.4.2.Final")
kapt("org.mapstruct:mapstruct-processor:1.4.2.Final")
kaptTest("org.mapstruct:mapstruct-processor:1.4.2.Final")

 

첫번째 제약사항은 추상메소드에 @Mapping 를 중복으로 정의할 수 없다는 것이다. 이에 아래와 같이 @Mappings 어노테이션을 정의하고 그 안에 Mapping을 정의해야 한다.

@Mappings(
    Mapping(target = "purPushEvent", source = "purPushEvent"),
    Mapping(target = "pushOtherInfo", expression = "java(pushReqInfo.toPushOtherInfoJson())"),
    Mapping(target = "regerId", source = "pushReqInfo.regerId"),
    Mapping(target = "updtrId", source = "pushReqInfo.regerId")
)
fun toEntity(purPushEvent: PurPushEvent, pushReqInfo: PushReqInfo): PurPushReqInfo

 

두번째 제약사항은 Mapping 정의시 expression 에 코틀린 함수를 지정할 수 없다는 것이다. 구문에서 알다시피 java만 지원한다. 코틀린에서 인터페이스에 정의하는 본체가 있는 메소드는 자바의 default 메소드와는 다르기 때문에 expression 에서 사용할 수 없다. 이에 expression 에서 사용할 변환함수는 별도 클래스에 둬야 하고 그나마 적절한 곳이 target 또는 source 클래스다.

 

그런데 이렇게 되면 뭔가 난잡한 느낌이다. 매퍼가 별도로 있는데도 불구하고 그곳에서만 사용될 변환함수를 Entity나 DTO에 포함시켜야 하기 때문이다. 과거 자주 사용하던 of 패턴을 떠올리게 된다. 

 

물론 그럼에도 변수명이 동일할 경우 자동으로 매핑을 해주기 때문에 코딩량과 실수가 현저히 줄어들긴 한다. 하지만 expression 은 컴파일러의 관리를 받을 수 없기 때문에 오타가 있더라도 실제 실행을 하기 전까지는 이를 알 수 없다는 단점이 있다. 

 

728x90

'Backend > Kotlin' 카테고리의 다른 글

[Kotlin] WebClient 에 동기식으로 사용하기  (0) 2022.05.10
[Kotlin] thread-safe Collection  (0) 2022.04.14
[Kotlin] 로깅  (0) 2022.03.08
[Kotlin] RequestBody 유효성 검증로직 추가하기  (0) 2022.03.07
Coroutine timeout 설정  (0) 2022.02.04