모종닷컴

Mapper 성능 비교 본문

Programming/Spring

Mapper 성능 비교

모종 2023. 2. 26. 00:44
반응형

헥사고날 아키텍처 설계에 따라 프로젝트를 하다 보니 각 영역에 전달해 줄 때 데이터 매핑 작업이 필수가 되었습니다. 데이터 매핑을 할 때 쓰는 라이브러리나 함수들이 많은데 각각의 성능을 비교해보고자 합니다.

테스트에 사용할 클래스 생성

data class Food(
    var name: String = "",
    var price: Int = 0,
    var calories: Int = 0,
    var color: String = "NONE"
)

data class FoodDto(
    var name: String = "",
    var price: Int = 0,
    var calories: Int = 0,
    var color: String = "NONE"
)

ModelMapper

implementation("org.modelmapper:modelmapper:3.1.1")

위 디펜던시 지정해준상태에서 시작하겠습니다.

테스트 코드를 만들고 실행해보도록 하겠습니다.

@Test
fun modelMapperTest() {
    val modelMapper = ModelMapper()
    val food = Food("치킨", 20_000, 2_000, "RED")
    for (i in 1..10_000_000) {
        modelMapper.map(food, FoodDto::class.java)
    }
}

13초 22가 소요되었습니다.

Spring BeanUtil

프로퍼티 복사에 사용되는 것중에 BeanUtil이라는 것이 있습니다. 이 유틸클래스를 통해서 테스트를 진행해 보도록 하겠습니다.

@Test
fun springBeanUtilsTest() {
    val food = Food("치킨", 20_000, 2_000, "RED")
    for (i in 1..10_000_000) {
        val dto = FoodDto()
        BeanUtils.copyProperties(food, dto)
    }
}

17초 191가  소요되었습니다. Model Mapper보다 낮은 성능을 보이고 있습니다.

MapStruct

map struct는 설정이 참 귀찮네요.. 아래처럼 kapt 플러그인 추가해줘야 한다고 합니다. kapt 플러그인에 대해 궁금할 수 있는데 kapt는 코틀린 어노테이션 프로세서를 실행하기 위한 Gradle 플러그인입니다. 코틀린 코드에 대해서 코드 생성 및 변환을 수행할 수 있습니다. 즉, 코틀린 어노테이션 프로세서를 사용하면 코드를 자동으로 생성할 수 있습니다. MapStruct는 내부적으로 코드 생성기를 사용한다고 하는데 그래서 이 Kapt가 필수로 설정이 되어야 하는 것 같습니다.

plugins {
    kotlin("jvm") version "1.6.21"
    kotlin("kapt") version "1.6.21"
}

dependencies {
    implementation("org.mapstruct:mapstruct:1.5.3.Final")
    kapt("org.mapstruct:mapstruct-processor:1.5.3.Final")
}

그레이들에 설정을 하였으면 다음으로는 Mapper 인터페이스 클래스를 만들어줘야 합니다.

@Mapper
interface FoodMapper {
    @Mappings
    fun toDto(food: Food): FoodDto

    companion object {
        val INSTANCE: FoodMapper = Mappers.getMapper(FoodMapper::class.java)
    }
}

이렇게 인터페이스로 정의된 FoodMapper는 그레이들이 빌드하면서 kapt를 이용하여 Implement 클래스를 만들어주게 되는 건가 보네요. 따라서 빌드를 돌린 이 후에 build/generated 폴더로 들어가 보면 생성된 코드가 보입니다.

@Test
fun mapStructTest() {
    val food = Food("치킨", 20_000, 2_000, "RED")
    for (i in 1..10_000_000) {
        FoodMapper.INSTANCE.toDto(food)
    }
}

테스트 코드를 실행해보겠습니다.

음.. 이래도 되는 건가 싶은데 15ms 시간이 소요되었습니다. 

Handmade

손으로 직접 작성을 해보겠습니다. 아래처럼 Util 클래스를 하나 작성해 줍니다.

object FoodMapperUtil {
    fun toDto(food: Food) = FoodDto(
        name = food.name,
        price = food.price,
        calories = food.calories,
        color = food.color
    )
}
@Test
fun manualMappingTest() {
    val food = Food("치킨", 20_000, 2_000, "RED")
    for (i in 1..10_000_000) {
        FoodMapperUtil.toDto(food)
    }
}

바로 테스트를 해보면

13 ms 밖에 소요가 안됩니다.

결과 요약

전체 테스트를 한 번에 돌렸을 때 결과를 보았습니다. SpringBeanUtils, ModelMapper는 상당히 느린 모습을 보이고 있고, MapStruct와 수동으로 매핑해 주는 성능은 동일해 보입니다. 만약 매핑해야 하는 클래스가 단순한 구조와 적은 필드를 보유하고 있다면 수동으로 매핑 유틸을 만들어주는 게 훨씬 이득일 것 같고, 복잡한 구조를 지닌다면 MapStruct를 고민해 봐도 좋을 것 같습니다.

반응형