객체 변환(Object Mapping)은 소프트웨어 개발에서 중요한 작업입이다. 특히, 도메인 객체와 DTO(Data Transfer Object) 간의 변환은 서비스 계층과 프레젠테이션 계층 간의 데이터 이동을 효율적으로 처리하기 위해 필수적이다.
이런 객체변환 작업에 있어서 지금까지 직접 코드를 작성하여 변환을 했었다. 그런데 이러한 작업을 도와주는 라이브러리가 있다는 것을 일주일 전에 있다는 것을 알아서 각 방법에 대하여 어떤 장점들이 있는지 확인을 할려고 한다.
// Member 클래스
@Getter
public class Member {
private Long id;
private String name;
private Long age;
@Builder
public Member(Long id, String name, Long age) {
this.id = id;
this.name = name;
this.age = age;
}
}
// DTO 클래스
@Getter
public class MemberDto {
private Long id;
private String fullName;
private Long age;
@Builder
public MemberDto(Long id, String fullName, Long age) {
this.id = id;
this.fullName = fullName;
this.age = age;
}
// MemberDto를 Member로 변환
public Member toEntity() {
return Member.builder()
.id(this.id)
.name(this.fullName)
.age(this.age)
.build();
}
// Member를 MemberDto로 변환
public static MemberDto fromEntity(Member member) {
return MemberDto.builder()
.id(member.getId())
.fullName(member.getName())
.age(member.getAge())
.build();
}
}
// Member 객체 생성
Member member = Member.builder()
.id(1L)
.name("John Doe")
.age(30L)
.build();
// Member 객체를 MemberDto로 변환
MemberDto memberDto = MemberDto.fromEntity(member);
// MemberDto 객체를 Member로 변환
Member memberFromDto = memberDto.toEntity();
이러한 방식으로 모든 필드에 대하여 직접 매핑을 하면서 코드를 작성을 해야한다.
(예제에서는 필드가 적어서 크게 작성할 부분이 없다.)
직접 코드를 작성하여 객체를 변환하는 방식은 변환로직을 직접적으로 명확하게 제어가 가능하다는 장점이 있다. 만약 필드의 이름이 변경이 되거나 필드가 추가를 한다면 매핑하는 코드 또한 같이 수정을 해줘야 한다는 부분과 반복적인 코드가 생길 수 있다는 단점이 존재한다.
또한 필드가 많아질 수록 잔 실수가 생기기도 하였다.
ModelMapper modelMapper = new ModelMapper();
// 필드 이름이 다를 때 매핑 규칙을 명시적으로 정의
modelMapper.addMappings(new PropertyMap<Member, MemberDto>() {
@Override
protected void configure() {
map().setFullName(source.getName());
}
});
Member member = Member.builder()
.id(1L)
.name("John Doe")
.age(30L)
.build();
MemberDto memberDto = modelMapper.map(member, MemberDto.class);
Member와 MemberDto에서 이름과 관련된 필드명을 다르게 설정을 했다. 그렇기에 PropertyMap을 설정하여 Member의 Name필드와 MemberDto의 FullName필드를 매핑하도록 설정을 해줬다.
만약 필드명이 전부 동일하다면 별도의 설정을 하지 않고 사용을 하여도 ModelMapper에서 자동으로 매핑을 처리해준다.
modelMapper.map(sourceObject, DestinationClass.class)을 이용하여 정의된 매핑규칙에 의거하여 객체변환을 해준다.
ModelMapper은 설정이 간편하여 작성에 있어 용이하고, 동적 매핑을 지원하면서 런타임에 매핑 규칙을 정의할 수 있다는 장점이 있다. 하지만 런타임 성능에 오버헤드가 있을 수 있으며 복잡한 매핑 로직의 경우 설정이 까다로울 수 있다는 단점이 존재한다.
MapSturct을 사용하기 위해서는 인터페이스를 하나 생성을 해야한다. 해당 인터페이스를 선언할 때 아래와 같이 @Mapper어노테이션을 선언해줘야 한다. 그러면 자동으로 Mapper의 구현체를 생성해준다.
@Mapper
public interface MemberMapper {
MemberMapper INSTANCE = Mappers.getMapper(MemberMapper.class);
@Mapping(target = "fullName", source = "name")
MemberDto toMemberDto(Member member);
}
위 코드에서 Mappers.getMapper(MemberMapper.class); 부분은 매퍼 클래스에서 어떤 객체로 매핑을 할 것인지 명시하기 위해서 작성을 한다. 그리고 해당 코드 아래에 있는 @Mapping어노테이션은 필드명이 다를 때 어느 필도로 매핑을 할 것인지 명시하기 위해서 작성된 것이다.
이 처럼 코드를 작성을 했다면 이제 사용을 해보자.
Member member = Member.builder()
.id(1L)
.name("John Doe")
.age(30L)
.build();
MemberDto memberDto = MemberMapper.INSTANCE.toMemberDto(member);
MapSturct의 경우 컴파일 타임에 매핑 코드가 생성되며 런타임 성능이 좋고, 매핑 규칙을 명확히 정의된다는 장점이 있다. 하지만 전혀 다른 형태의 필드로의 매핑에 있어서 로직이 다소 복잡할 수 있다는 점과 변경이 불가능한 필드에 대하여 매핑을 제공하지 못한다는 단점이 있다. 또한 Lombok라이브러리와의 충돌이 발생할 가능성이 있다고 한다.