dto에서 entity를 변환해주는 mapping 작업을 어딘가에서는 반드시 해줘야 한다. 그래서 처음에는 service 계층에서 이러한 mapping 로직을 만들었는데, 이는 많은 문제를 발생시킨다. 특히, dto는 api 스펙이 변할 때마다 변경될 수 있으므로 dto의 수정에 따라 service 로직도 수정해야 되는 문제가 있다. 그렇기 때문에 MapperService라는 하나의 service class를 만들어서 로직을 분리해주었다.
@RequiredArgsConstructor
@Service
public class FacilityService {
private final MapperService mapperService;
private final UserService userService;
private final FacilityRepository facilityRepository;
@Transactional
public Facility createFacility(CreateFacilityRequestDto dto) {
FacilityDto facilityDto = mapperService.toFacilityDto(dto);
return facilityRepository.save(Facility.create(getLoginUser(), facilityDto));
}
//...
}
@RequiredArgsConstructor
@Service
public class MapperService {
public FacilityDto toFacilityDto(CreateFacilityRequestDto dto) {
return FacilityDto.builder()
.address(createAddress(dto))
.category(dto.getCategory())
.description(dto.getDescription())
.build();
}
//...
}
또한, 살펴봐야 할 점은 dto에서 바로 entity로 변환하지 않고 FacilityDto라는 또 다른 dto로 변환해주었다. 이것이 의도하는 바는 entity 계층으로 들어가는 객체는 반드시 통일된 dto의 형태로 만들기 위함이다. FacilityDto는 Facility와 완전히 똑같은 필드를 가지고 있지만, entity는 아니다. 말 그대로 dto일 뿐이다. 그렇다면 이렇게 반문할 수 있을 것 같다. entity를 중간에 생성해서 entity의 static 메서드 argument로 넣어주면 되지 않을까? 개인적인 생각으로 entity는 DB나 영속성 컨텍스트에서 꺼내 오는 경우에만 그 의미가 뚜렷해진다고 본다. 따라서 데이터를 전달하는 용도로만 쓰이는 dto를 생성해서 entity의 static 메서드로 전달하는 것이 책임과 역할이 분명하다.

이렇게 설계 했을 때 api 스펙이 변할 때마다 dto가 자주 수정되더라도 entity에는 영향이 없다. 즉, api 스펙에 최적화된 dto와 entity가 바라보는 dto를 분리하는 것이다.
그러나 또 다른 문제는 ‘과연 MapperService의 메서드 시그니처가 변하지 않는다는 보장이 있을까’라는 점이다. 다시 말해, MapperService로 mapping 로직을 분리하였지만 FacilityService가 MapperService의 변경으로부터 안전하지 못할 수도 있다는 것이다. 그렇기 때문에 mapping 로직은 웬만하면 controller 계층으로 옮기는 편이 좋을 것이다.
public class FacilityController implements FacilityApi {
private final FacilityService facilityService;
private final MapperService mapperService;
@Override
public ResponseEntity<CreateFacilityResponseDto> registerFacility(CreateFacilityRequestDto requestDto) {
FacilityDto facilityDto = mapperService.toFacilityDto(requestDto);
Facility facility = facilityService.createFacility(facilityDto);
return new ResponseEntity<>(mapperService.toCreateResponseDto(facility), HttpStatus.OK);
}
//...
}