클린 아키텍쳐, 멀티 모듈, 많은 계층 등으로 구성되어 있는 어플리케이션을 개발하다 보면 데이터 모델 간 객체 변환이 꼭 필요하다. 이 때, 한땀한땀 필드를 맵핑해줄 수도 있지만 이 작업을 대신 해주는 오픈소스인 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)
}
@Mapper
어노테이션이 부여된Interface
를 대상으로 빌드를 할 때 매핑 구현체를 자동으로 생성해준다.Class
가 아닌Interface
로 작성해야 한다.- 매핑의 출처가 되는 원본 클래스를
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 {
}
'프로그래밍' 카테고리의 다른 글
Interface 를 활용한 Enum 리팩토링 (0) | 2023.06.21 |
---|---|
[번역] 반복적인 DTO-Domain 변환 처리하기 (feat. Kotlin Flow) (0) | 2023.03.31 |
[티스토리] highlight.js 를 활용해서 티스토리 코드블럭 꾸미기 (0) | 2023.02.10 |
[디자인패턴] 실무에서 사용해본 전략 패턴 (Strategy Pattern) (0) | 2023.02.02 |
[프로그래밍] 검색과 조회에 대한 Naming Convention (0) | 2023.02.02 |
댓글