.NET으로 애플리케이션을 개발하는 과정을 진행하다보면, 아키텍처 분리를 시도하면서 필연적으로 DTO, 뷰 모델, 데이터베이스 모델 간에 중복되는 코드를 계속 만들고 유지해야 하는 고통이 따릅니다.
이 문제를 자동화하고 쉽게 풀 목적으로 각종 Mapper들이 그동안 많이 나왔습니다. 하지만 최근 .NET에 지향하는 AOT는 안타깝게도 이런 라이브러리들이 의존하는 리플렉션 때문에 제대로 쓰기 어렵습니다.
그러던 중에, AOT 시나리오에서도 mapper를 쓸 수 있게 함은 물론, 컴파일 타임에서 사전에 모델 매핑 상에서 발생할 수 있는 문제까지 잡아주는 소스 제네레이터 기반의 Model Mapper가 있어 소개드려봅니다. 바로 Riok.Mapperly 패키지입니다.
예를 들어 아래와 같은 코드가 있다고 가정해보겠습니다. Car 라는 논리 모델을 CarDto로 매핑하는 예시입니다.
#:package Riok.Mapperly@4.3.0
using Riok.Mapperly.Abstractions;
// Enums of source and target have different numeric values -> use ByName strategy to map them
[Mapper(EnumMappingStrategy = EnumMappingStrategy.ByName)]
public static partial class CarMapper
{
[MapProperty(nameof(Car.Manufacturer), nameof(CarDto.Producer))] // Map property with a different name in the target type
public static partial CarDto MapCarToDto(Car car);
}
public class Car
{
public string Name { get; set; } = string.Empty;
public int NumberOfSeats { get; set; }
public CarColor Color { get; set; }
public Manufacturer? Manufacturer { get; set; }
public List<Tire> Tires { get; } = [];
}
public enum CarColor
{
Black = 1,
Blue = 2,
White = 3,
}
public class Manufacturer
{
public Manufacturer(int id, string name)
{
Id = id;
Name = name;
}
public int Id { get; }
public string Name { get; }
}
public class Tire
{
public string Description { get; set; } = string.Empty;
}
//////////////////////////////////////////////////////////////////
public class CarDto
{
public string Name { get; set; } = string.Empty;
public int NumberOfSeats { get; set; }
public CarColorDto Color { get; set; }
public ProducerDto? Producer { get; set; }
public List<TireDto>? Tires { get; set; }
}
// Intentionally use different numeric values for demonstration purposes
public enum CarColorDto
{
Yellow = 1,
Green = 2,
Black = 3,
Blue = 4,
}
// The manufacturer, but named differently for demonstration purposes
public class ProducerDto
{
public ProducerDto(int id, string name)
{
Id = id;
Name = name;
}
public int Id { get; }
public string Name { get; }
}
public class TireDto
{
public string Description { get; set; } = string.Empty;
}
거의 모든 멤버들이 1:1로 대응되지만, 문제가 있습니다. 아래 그림처럼 CarColor와 CarColorDto enum 사이에 불일치하는 멤버들이 존재한다는 사실입니다. 이런 부분을 지금 보시는 것처럼 컴파일러 수준에서 미리 체크가 됩니다.
또한 소스 제네레이터 타입의 패키지는 파일 기반 앱 환경에서도 매우 잘 작동하므로, CommunityToolkit.Mvvm 같은 패키지와 섞어서 써도 아주 좋습니다.
이 주제로 고민 중이신 분들을 위하여 정보를 공유드립니다. ![]()
