본문 바로가기
프로그래밍

[오픈소스] MapStruct 소개와 사용법

by kdohyeon (김대니) 2023. 2. 8.
반응형

MapStruct

클린 아키텍쳐, 멀티 모듈, 많은 계층 등으로 구성되어 있는 어플리케이션을 개발하다 보면 데이터 모델 간 객체 변환이 꼭 필요하다. 이 때, 한땀한땀 필드를 맵핑해줄 수도 있지만 이 작업을 대신 해주는 오픈소스인 MapStruct 에 대해 알아보도록 하자.

예시 소개

아래와 같은 구조로 프로젝트가 설계되어 있다고 가정을 해보자. 내부 Domain 에서 사용하는 클래스는 Blog, application-layer 와 web-layer 가 서로 통신을 할 때에는 BlogDto, 그리고 외부 HTTP 통신을 할 땐 NaverBlogResponse, KakaoBlogResponse 등으로 데이터를 주고 받을 수 있다. 모두 다 같은 의미의 데이터를 포함하고 있지만 각 계층을 구분짓기 위해 클래스를 분리했다.

이렇기 때문에 각 계층 간 데이터를 주고 받을 때는 클래스 변환이 필수적이다.

Blog 와 BlogDto 는 아래와 같다.

public class Blog {
    private String blogName;
    private String title;
    private String thumbnail;
    private String url;
}

public class BlogDto {
    private String blogName;
    private String title;
    private String thumbnail;
    private String blogUrl;
}

 

MapStruct 없이 수작업으로 데이터 모델을 변환한다면?

두 모델은 동일한 데이터를 가지고 있으며, 중복같지만 계층을 분리하기 위해서는 꼭 필요하다 (가짜 중복). 하나의 계층에서 또 다른 계층으로 옮겨갈 때, 모델을 변환해주는 클래스를 별도로 생성하게 되는데 보통 xxxConverter.java, yyyGenerator.java, zzzMapper.java 등으로 네이밍해서 생성을 할 수 있다.

public class BlogDtoConverter {
    public BlogDto(Blog source) {
        return BlogDto(
            source.getBlogName(),
            source.getTitle(),
            source.getThumbnail(),
            source.getUrl()
        )
    }
}

만약 필드가 많아지고, 이동해야 할 계층이 많아진다면 작성해야 할 Converter 코드는 훨씬 더 많아지게 되고 필드를 맵핑할 때, 휴먼 에러가 발생할 가능성도 자연스럽게 높아진다.

MapStruct

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 는 두 자바 빈의 필드 매핑을 쉽게 구현해주는 코드 생성기입니다.)

어떻게 사용할 수 있는지 알아보자.

의존성 추가

build.gradle 에서 MapStruct 에 대한 의존성을 추가한다.

dependencies {
    ...
    implementation 'org.mapstruct:mapstruct:1.5.3.Final'
    annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final'
}

Mapper 작성하기

아래는 MapStruct 를 활용하여 구현한 Converter 클래스이다.

@Mapper // 1.
public interface BlogDtoConverter { // 2.
    @Mapping(source = "url", target = "blogUrl") // 3.
    BlogDto convert(Blog source)
}

 

  1. @Mapper 어노테이션이 부여된 Interface 를 대상으로 빌드를 할 때 매핑 구현체를 자동으로 생성해준다.
  2. Class 가 아닌 Interface 로 작성해야 한다.
  3. 매핑의 출처가 되는 원본 클래스를 Source, 대상 클래스를 Target 이라고 한다. 변환하고자 하는 두 모델에서 다른 이름으로 변수명이 작성되어 있다면 @Mapping 어노테이션을 통해 두 변수를 매핑해줄 수 있다.

주의할 점

1. boolean 변수를 조심하자

클래스 변수 중에 boolean 변수가 isXyz 형태로 네이밍이 되어 있는 경우에는 데이터 변환이 잘 되는지 확인해보아야 한다. 빌드를 하면 구현체도 정상적으로 생성도 되고, 어플리케이션도 제대로 실행되지만 기능은 정상적으로 작동 안할 수 있으니 주의해야 한다. 변환하고자 하는 두 모델 간 동일한 변수명을 사용하고 있더라도, MapStruct 에서는 일반적인 코드 컨벤션에 기반하여 작동되기 때문에 변환이 제대로 안될 수 있다.

따라서, is... 형식의 변수들은 직접 매핑을 해주어야 한다. @Mapping(source = "xyz", target = "isXyz") 처럼 직접 매핑을 해주어야 기대처럼 잘 작동한다.

2. 구현체가 제대로 생성이 안되면 @MapperConfig 를 활용해보자

Interface 로 만든 @Mapper 를 빌드해도 스프링 빈으로 등록이 되지 않거나, 구현체가 제대로 생성되지 않으면 @MapperConfig 를 활용해볼 수 있다.

@MapperConfig(
        componentModel = MappingConstants.ComponentModel.SPRING
)
public class MapStructConfig {
}

 

반응형

댓글