Multi-layered 로 구성되어 있는 어플리케이션을 개발하다 보면 데이터 모델 간 변환이 수시로 발생합니다.
예를 들어, DB Entity 인 Computer
라는 모델과 DTO 인 ComputerDto
가 있습니다.
public class Computer {
private String computerId;
private boolean isMacOs;
private Long numberOfCpus;
}
public class ComputerDto {
private String id;
private boolean isMacOs;
private Long numberOfCpus;
}
각 계층에서 데이터 모델을 사용하기 위해서는 위 두 모델은 동일한 데이터를 가지고 있지만 꼭 필요한 모델입니다. (가짜 중복)
위와 같이 데이터 모델을 계층 간 이동을 할 때에는 별도 변환 클래스를 생성하여 사용하게 됩니다. 보통 xxxConverter.java
, yyyGenerator.java
, zzzMapper.java
등으로 네이밍을 할 수 있습니다.
public class ComputerDtoConverter {
public ComputerDto(Computer source) {
return ComputerDto(
source.getComputerId(),
source.isMacOs(),
source.getNumberOfCpus()
)
}
}
만약 필드가 많아지고, 이동해야 할 계층이 많아진다면 작성해야 할 Converter 코드는 훨씬 더 많아지게 되고 휴먼 에러가 발생할 가능성도 높아집니다.
이를 효과적으로 하기 위해 사용하는 오픈소스가 MapStruct
입니다.
공식 사이트에서 MapStruct
를 아래와 같이 소개하고 있습니다.
MapStruct is a code generator that greatly simplifies the implementation of mappings between Java bean types based on a convention over configuration approach.
MapStruct 는 흔히 알고 있는 코드 작성 컨벤션에 기반하여 두 자바 빈의 매핑을 쉽게 구현해주는 코드 생성기입니다.
코드에 적용한 예제를 살펴보겠습니다.
공식 사이트를 참고하셔도 됩니다.
Gradle
에서 MapStruct
를 불러와보겠습니다.
현재 기준, 최신 버전은 1.5.3
입니다.
dependencies {
...
implementation 'org.mapstruct:mapstruct:1.5.3.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final'
}
아래는 MapStruct 를 활용하여 구현한 Converter 클래스 입니다.
@Mapper // 1.
public interface ComputerDtoConverter { // 2.
@Mapping(source = "computerId", target = "id") // 3.
@Mapping(source = "macOs", target = "isMacOs")
ComputerDto convert(Computer source)
}
위 코드에 대한 설명을 드리면:
@Mapper
어노테이션이 부여된 Interface
를 대상으로 매핑 구현체를 자동으로 생성하게 됩니다. 따라서 Interface
에 @Mapper
어노테이션을 부여해야 합니다.
따라서 Class
가 아닌 Interface
로 작성해야 합니다.
매핑의 출처가 되는 원본 클래스를 Source
, 대상 클래스를 Target
이라고 합니다. 변환하고자 하는 두 모델에서 다른 이름으로 변수명이 작성되어 있다면 @Mapping
어노테이션을 통해 두 변수를 매핑해줄 수 있습니다.
프로젝트 build
를 한 뒤에 작성한 Interface 의 구현체가 자동으로 생성된 것을 확인하실 수 있습니다.
직접 사용하다가 겪은 후기입니다.
클래스 변수 중에 boolean
변수가 isXyz
형태로 네이밍이 되어 있었습니다. 변환하고자 하는 두 모델 모두 동일한 변수명을 사용하고 있었기에 잘 변환이 되겠거니 별도 @Mapping
을 통해 직접 매핑을 해주지 않았습니다.
프로젝트 빌드도 잘 되고 매핑 인터페이스에 대한 구현체도 잘 생성되었습니다.
하지만 테스트를 할 때 isXyz
변수에 값이 무조건 false
로 입력되는 것이었습니다.
찾아보니 is...
형식의 변수는 직접 매핑을 해주어야 MapStruct
의 코드 생성기에서 문제가 없이 잘 생성되는 것이었습니다.
따라서, @Mapping(source = "xyz", target = "isXyz")
처럼 직접 매핑을 해주어야 기대처럼 잘 작동하게 됩니다.