반응형

개발을 하면서 모델간 매핑을 많이 한다.
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)를 이용한 매퍼
반응형

Comments

  1. 이수민 2019.09.06 14:29 Permalink Modify/Delete Reply

    좋은 글 감사합니다~

    혹시 mapper 는 보통 어느 레이어에 두시나요?

    domain.model.member.Member
    domain.model.member.MemberDomainService
    application.member.dto.MemberDto
    application.member.MemberApplicationService

    domain.model.member.MemberMapper
    application.member.MemberMapper
    application.member.dto.MemberMapper
    application.mapper.MemberMapper
    mapper.member.MemberMapper

    Application Layer 에 위치하는게 가장 좋아보이기는 하는데,, 이런거는 정답도 없어서 더 고민되네요

    • 아틴 2019.09.06 15:19 신고 Permalink Modify/Delete

      Mapper는 특정 레이어에 종속적이라고 생각하지 않습니다. Mapper의 목적이 오브젝트와 오브젝트를 변환해주는 것이 목적이기 때문인데요.

      Domain Layer
      Application Layer
      Presentation Layer

      모델 오브젝트의 위치가 중요하다고 생각합니다.
      DTO1을 DTO2로 변경한다고 했을 때, 두 DTO가 모두 Presentation Layer로 같은 Layer에 있다고 하면 Mapper는 Presentation Layer에 있어야 한다고 보구요.

      DTO1은 Domain Layer에 있고
      DTO2는 Application Layer에 있다고 하면 Application Layer에 Mapper가 있어야 한다고 봅니다.

      단순히, 두 모델 오브젝트를 의존할 수 있는 Layer에 Mapper를 두는 것이 맞다고 생각하는 부분입니다.

      DTO1은 Domain Layer에 있고
      DTO2는 Application Layer에 있을 경우
      Mapper가 Domain Layer에 있으면 해당 Mapper는 DTO2를 참조할 수 없어서 매핑을 할 수가 없습니다. (레이어를 패키지로 분리했다면 의존할 수 있겠지만.)
      Application Layer는 Domain Layer를 의존하고 있지만 Domain Layer는 Application Layer를 의존하지 않아야 하기 때문입니다.

      domain.member.dto
      application.member.dto
      application.member.mapper

    • 이수민 2019.09.10 10:49 Permalink Modify/Delete

      모델 오브젝트의 위치에 따라 단순히 생각하면 되겠군요!

      감사합니다

  2. A.S.L 2019.09.09 21:20 신고 Permalink Modify/Delete Reply

    MapStruct 한글로 정리된 글 찾다보니 여기까지 왔네요.
    좋은글 감사해요~

Leave a Comment


to Top