Post

DataMapper와 MapStruct

Presentation Layer 뿐만 아니라 어느 layer에서든 사용 할 수 있다. (e.g., Controller, Service, Repository 내에서)

Data Mapper

요약 ) ‘Record<>Domain Model 매핑 로직’, ‘DTO<>Domain Model 매핑 로직’을 전담.

  • Data를 다른 Data로 Mapping해주는 책임을 가진 구현체이면, 모두 Data Mapper다.
  • DTO<>Domain Model 변환
  • Record<>Domain Model 변환
    • app 단의 data structure와 DataSource의 data structure가 1:1로 딱 들어맞지 않을 수 있기 때문에 이에 대한 변환이 필요하다.
    • persistence layer 내에서 호출하는 경우, persistence layer의 역할 자체가 DataSource 추상화이므로, Data Mapper에도 DataSource specific한 코드가 들어갈 수 있다.
  • Data Mapper를 하나의 layer로 보기 보다는, AOP 같은 느낌으로, 모든 layer에 걸쳐 Mapping이 필요한 부분에서 접근 가능한 개체로 보는 것이 더 유연해보인다.

MapStruct의 @Mapper

요약 ) 매핑 로직을 직접 작성 할 필요 없게, 자동으로 생성해주는 도구 (build/generated에 생성되는 방식)

필요성

  • layered architecture 지켜서 개발하다 보면, 기본적으로 presentation DTO -> Domain Model -> persistence DTO 3단계 변환이 일어나고, 응답 반환에서도 똑같이 3단계 변환이 일어난다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// presentation DTO
data class OutstandingRow(
    val field1: String
) {
    fun toModel(): Outstanding {
        return Outstanding(
            field1 = this.field1
        )
    }
    companion object {
        fun from(model: Outstanding) {
            return OutstandingRow(
                field1 = model.field1
            )
        }
    }
}

// domain model
data class Outstanding(
    val field1: String
)

// persistence DTO
data class OutstandingRecord(
    val field1: String
) {
    fun toModel(): Outstanding {
        return Outstanding(
            field1 = this.field1
        )
    }
    companion object {
        fun from(model: DomainModel) {
            return OutstandingRecord(
                field1 = model.field1
            )
        }
    }
}
  • 위와 같이 DTO<>Domain Model 변환 메서드를 직접 작성하게 되면, 매핑 작업이 너무 많아 비효율적이다. (Data Mapper를 분리하지 않고 DTO에 두었는데, Data Mapper를 분리해도 귀찮은 것은 똑같다.)
    • 간단한 작업인 경우, 매핑에 쓰는 시간이 전체 코드 작성 시간에서 너무 큰 포션을 차지해버린다.
    • 도메인 모델에 field2가 추가되고, 이 것이 presentation/persistence DTO에도 추가되어야 하는 경우 매핑 함수도 다 고쳐주어야 한다. (간단한 코드임에도, 고쳐야 하는 포인트가 너무 많아 bad smell로 느껴진다)
  • 이런 매핑 작업을 대신해주는 라이브러리가 MapStruct다.

예제

  • https://mapstruct.org/의 Mapper interface 참조
    1
    2
    3
    4
    5
    
    @Mapper  
    interface OutstandingMapper {  
      fun toPresentation(model: Outstanding): OutstandingRow
      fun fromRecord(record: OutstandingRecord): Outstanding
    }
    
    1
    2
    
    // usage
    private val outstandingMapper = Mappers.getMapper(OutstandingMapper::class.java)
    

참고

This post is licensed under CC BY 4.0 by the author.