프로젝트를 진행하면서 아래와 같은 코드리뷰가 달렸다.

이 코드는 컨트롤러 코드로, 생성된 Diary를 객체를 DiaryResponse라는 DTO로 감싸서 응답하는 코드이다.
하지만 점점 Diary의 멤버 변수가 많아지다보니 이를 DTO로 조립하는 코드가 지저분해졌다 🥲
이를 해결하기 위해 고민하던 중 두 가지 방법을 생각할 수 있었다.
정적 팩토리 메서드 (from) 사용
정적 팩토리 메서드(Static Factory Method)란?
"팩토리"라는 용어에는 객체를 생성하는 역할을 분리하겠다는 취지가 담겨있다.
즉, 정적 팩토리 메서드란 객체 생성의 역할을 하는 클래스 메서드라는 의미이다.
DTO 내부에서 정적 팩토리 메서드의 from 메서드를 정의하여 아래와 같이 객체를 조립할 수 있다.
public class DiaryResponse {
private Long id;
private String content;
private LocalDateTime createdDate;
public static DiaryResponse from(Diary diary) {
return DiaryResponse.builder()
.id(diary.getId())
.content(diary.getContent())
.createdDate(diary.getCreatedDate())
.build();
}
}
static 메서드이기 때문에 컨트롤러에서 바로 사용할 수 있다.
public class DiaryController {
...
@PostMapping()
public ResponseEntity<DiaryResponse> createDiary(
@AuthenticationPrincipal UserDetails userDetails,
@RequestBody @Valid DiarySaveRequest diarySaveRequest
) {
Diary diary = diaryService.createDiary(...);
DiaryResponse response = DiaryResponse.from(diary) // ✅
return ApiResponse.created(response);
}
}
Assembler 클래스 정의
객체를 조립하는 용도로 클래스를 따로 분리한다. 이때 @Component 어노테이션으로 스프링 빈으로 등록해 주어야 한다.
@Component
public class DiaryResponseAssembler {
public DiaryResponse toResponse(Diary diary) {
return DiaryResponse.builder()
.id(diary.getId())
.content(diary.getContent())
.build();
}
}
DiaryResponseAssembler는 컨트롤러에서 의존성을 주입받아서 활용할 수 있다.
public class DiaryController {
...
private final DiaryResponseAssembler diaryResponseAssembler; // ✅
@PostMapping()
public ResponseEntity<DiaryResponse> createDiary(
@AuthenticationPrincipal UserDetails userDetails,
@RequestBody @Valid DiarySaveRequest diarySaveRequest
) {
Diary diary = diaryService.createDiary(...);
DiaryResponse response = diaryResponseAssembler.toResponse(diary); // ✅
return ApiResponse.created(response);
}
}
ModelMapper 라이브러리 사용
ModelMapper 라이브러리를 사용하기 위해서는 아래처럼 의존성을 추가해야 한다.
implementation 'org.modelmapper:modelmapper:2.4.4'
ModelMapper는 별다른 작업 없이 바로 컨트롤러에서 사용할 수 있다.
public class DiaryController {
...
ModelMapper modelMapper = new ModelMapper(); // ✅
@PostMapping()
public ResponseEntity<DiaryResponse> createDiary(
@AuthenticationPrincipal UserDetails userDetails,
@RequestBody @Valid DiarySaveRequest diarySaveRequest
) {
Diary diary = diaryService.createDiary(...);
DiaryResponse response = modelMapper.map(diary, DiaryResponse.class); // ✅
return ApiResponse.created(response);
}
}
정리
정적 팩토리 메서드
- DTO 클래스에 정의하며, 간단한 변환 작업을 처리할 때 유용하다.
- 클래스 내에 변환 메서드를 유지하므로 코드의 가독성이 높아진다.
- DTO에 변환 로직이 섞여 단일 책임 원칙에 위배된다.
Assembler 클래스
- 복잡한 작업을 처리할 때 유용하다.
- 모델과 DTO 간 의존성을 최소화할 수 있다.
- 변환 로직을 별도의 클래스에 캡슐화하므로 코드의 재사용성과 유지 보수성이 향상된다.
- 변환 작업을 명시적으로 분리하여 코드 베이스를 깔끔하게 유지할 수 있다.
- 추가적인 클래스를 구현해야 하며 사용하기 위해 를 작성해야 하므로 작업량이 늘어난다.
ModelMapper 라이브러리
- 객체 간 필드 이름이나 유형을 기반으로 자동 매핑 하기 때문에 변환 로직을 수동으로 작성할 필요 없이 간단하게 처리할 수 있다.
- 객체 간 변환 로직이 한 곳에 집중되므로 코드 베이스가 깨끗하고 유지 보수가 쉽다.
- 컴파일 타임에 타입 검사를 지원하여 타입 관련 오류를 미리 감지할 수 있다.
- 자동 매핑을 위해 리플렉션(Reflection)을 사용하므로 매핑 과정에서 약간의 성능 오버헤드가 발생할 수 있다.
그리고 나는 이러한 답글을 달았다.

참고 자료
정적 팩토리 메서드(Static Factory Method)는 왜 사용할까?
ModelMapper를 이용해 DTO와 Entity Converting 하기
'✍️ 개발 기록' 카테고리의 다른 글
| [🌸다시, 봄] @ControllerAdvice를 이용하여 중앙 집중식으로 예외 처리하기 (0) | 2023.10.25 |
|---|---|
| [🎵MIML] 서브모듈(Submodule)을 사용하여 개발 효율 개선하기 (0) | 2023.10.23 |
| [🎵MIML] 리다이렉트 지옥 탈출한 썰..😵💫 (with. SessionCreationPolicy) (0) | 2023.10.20 |
| [🎵MIML] Spotify 소셜 로그인 하기 (0) | 2023.10.04 |
| [🌸다시, 봄] cursor 기반 pagination으로 무한스크롤 구현하기 🛠 (0) | 2023.09.21 |