반응형
개발을 하면서 모델간 매핑을 많이 한다.
DTO, VO, Entity별로도 하고 DTO간 DTO 변환에도 사용한다.
개발자마다 각각 다양한 방식으로 이 부분에 대해 개발을 하는데 문제는 팀 내에서는 동일한 방식을 사용해야 한다.
팀 생산성상 중요하고, 코드리뷰에서 불필요하게 지적하는 시간을 줄일 수 있다.
중요한 점은 팀 내에서 협의하여 공통된 방식에 대해서 합의가 되어야 한다.
정해진 답은 없고, 프로젝트의 성격에 따라 다르겠지만 어느 정도 공통된 점들은 있다.
Model Object
시작하기에 앞서
모델 객체애 대해서 다시 살펴보면
DTO, VO, Entity, Domain Model로 나누어 볼 수 있다.
DTO
- Data transfer object
- 목적 : 데이터의 전달
- 데이터의 전달을 위한 생성자와 getter, setter 로직만 들어가야 하며 기타 로직이 들어가서는 안됨
VO
- Value Object
- 목적 : 불변 객체로서 데이터 전달
- 개발자들간 대화를 하다보면 VO에 대해서 특별한 정의를 없이 사용하며 DTO와 크게 다르지 않게 사용하는 경우가 있다.
- 그러나 프로젝트 내에서 DTO와 VO를 특별한 의미없이 사용해서는 안된다. 나름대로의 목적을 정해야만 함
- VO의 통상적으로 사용되는 의미는 불변(Immutable)의 의미
- DDD에서 VO를 불변 객체로 소개
불변(Immutable) 객체
- 한번 생성된 이후 수정되지 않는 의미
- 데이터를 다룰 때, 종종 수정되어서는 안되는 데이터가 있는데 이를 개발단에서 중간에 수정되지 않게 막음
- 동시성 프로그래밍(parallel programming)에서 멀티 스레드 처리시 발생하는 문제에서 벗어날 수 있음
- 생성자 또는 빌더를 통해서만 값을 객체 생성 초기에 넣고, 그 이후로는 getter를 통해서 값을 갖고 오도록 처리
Entity & Domain Model
- Etity는 ORM에서 DB Table와 매핑되는 모델 객체
- Entity는 데이터 전달의 목적보다 DB Table과 객체를 매핑함으로서, 기존 JDBC API를 이용한 경우 데이터 정보만 전달하게 되면서 객체 지향적이지 않게 생기는 패러다임 불일치 문제를 해결하는 점이 중요
- DDD를 할 경우에는 Entity를 도메인 모델로 사용한다. 이 경우 Entity에는 도메인 로직이 들어감
- DDD 관점에서 볼 때, Domain Layer에 위치
매핑 개발
Service
여러 서비스에서 사용시 중복코드 발생
서비스 객체에 모델간 매핑 로직은 서비스 객체의 목적에 부합하지 않음
Model Object
- 장점
- 빠른 개발시 간편함
- 단점
- 서비스가 커지고, 모델간 다양한 매핑이 있을 경우 점점 모델 객체안에 메서드가 많아짐
- 모델 객체에 모델간 매핑 로직은 모델 객체의 목적에 부합하지 않음
- Entity안에 VO변환 로직을 넣고, VO안에 Entity 변환 로직을 넣게 된다면 (이러면 안되지만), 양방향 의존으로 인한 스파게티 코드 발생
Mapper
- 모델간 매핑에 대한 책임
- 역할 : 모델간 모델 매핑
mapper를 이용한 모델 객체를 사용하게 될 때, 다시 한번 결정을 해야 하는 것이 있다.
라이브러리들을 이용해서 모델간 매핑을 자동화를 할지, 아니면 직접 Mapper를 이용해서 모델간 매핑을 처리할지이다.
여러가지 라이브러리가 있는데 그 중에서 ModelMapper와 MapStruct에 대해 얘기해본다.
ModelMapper
- 내부적으로 리플렉션을 사용. 그래서 사용을 하지 않는 것이 좋다.
- 리플렉션으로 인한 성능 이슈 그리고 실제 동작시 발생하다보니 자칫 예상치 못한 결과가 발생할 수 있다.
- 리플렉션을 쓰는 대표적인 것 중 하나로 Jackson ObjectMapper가 있는데, 이는 모델 변경이 아닌 모델의 포맷에 대한 변경이고 대안이 없기 때문에 사용한다.
MapStruct
- 리플렉션 사용하지 않음
- 컴파일시 생성
- 레퍼런스
- 국내는 적지만, 미국에서 점점 사용도가 높아지고 있음
MapStruct Example
MemberMapper.java
import org.mapstruct.Mapper;
@Mapper(componentModel = "spring")
public interface MemberMapper {
MemberVO toVO(Member member);
}
Member.java
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Getter
@Setter
@EqualsAndHashCode(of = { "memberId" }, callSuper = false)
@Table(name = "member")
public class Member extends BaseEntity {
@Id
@Column
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long memberId;
@Column
private String memberName;
}
MemberVO.java
import lombok.Builder;
import lombok.Value;
import java.time.LocalDateTime;
@Builder
@Value
public class MemberVO {
private Long memberId;
private String memberName;
}
Kotlin에서 모델 매핑
- Kotlin에서도 MapStruct를 쓰기도 함
- 객체 생성 후에 기존 객체에 확장메서드(extenstion function)를 이용한 매퍼
반응형
'Devlopment > Java' 카테고리의 다른 글
static final Logger에 대한 고촬 (0) | 2018.02.08 |
---|---|
MessageFormat의 숫자 대신 문자열 키값을 이용해서 값을 넣고 싶을 때 (0) | 2018.01.12 |
Jackson, ObjectMapper 알지 못하는 프로퍼티도 허용하기 (0) | 2017.08.21 |
Clean Code - Null 리턴 (0) | 2016.02.26 |
[Gradle] Build 오류 - Could not fetch model of type 'EclipseProject' using Gradle distribution (0) | 2014.10.24 |
Java JAR, WAR파일에 버전 심고 확인하기 (0) | 2013.05.25 |
Java Network Framework, Netty (0) | 2013.04.23 |
JBoss 설치 및 연동 (0) | 2013.04.19 |
Mavn 및 플러그인 설치 (0) | 2013.04.19 |
Java 이전 버전을 받을 수 있는 URL (0) | 2013.03.28 |