배달 어플 프로젝트를 진행 중 데이터를 객체 타입으로 전달하기 위해 DTO(Data Transfer Object)의 생성 방식에 대해 고민이 생겼다. Java에서 DTO를 생성하는 방법엔 대표적으로 2가지 방법이 존재한다.
- Builder 패턴 (예: CartResponseDto.builder())
- 직접 생성자 호출 (예: new MenuAdminResponseDto(menu))
이 글에서는 두 접근법을 금융 IT와 보안 관점에 더 중점을 두고 어떤 게 더 적합한지 글을 작성해보려고 한다.
✅ DTO 생성 방식
@Getter
@AllArgsConstructor
@Builder
public class MenuAdminResponseDto {
private final Long menuId;
private final Category category;
private final String menuName;
private final String content;
private final MenuStatus menuStatus;
private final LocalDateTime createdAt;
public MenuAdminResponseDto(Menu menu) {
this.menuId = menu.getId();
this.category = menu.getCategory();
this.menuName = menu.getName();
this.content = menu.getContent();
this.menuStatus = menu.getStatus();
this.createdAt = menu.getCreatedAt();
}
}
위의 코드가 있을땐 두가지의 방법으로 DTO를 생성할 수 있다.
♐ Builder 패턴
- Builder 패턴은 Lombok 같은 라이브러리나 수동으로 구현하여 객체를 체이닝 방식으로 생성한다.
- 예를 들어, Builder 패턴의 생성은 다음과 같다.
MenuAdminResponseDto.builder()
.menuId(menu.getId())
.category(menu.getCategory())
.menuName(menu.getName())
.content(menu.getContent())
.menuStatus(menu.getStatus())
.createdAt(menu.getCreatedAt())
.build();
- 이 방식은 가독성이 높고, 복잡한 DTO를 유연하게 생성할 수 있다.
♐ 직접 생성자 호출
- 직접 생성자 호출은 Menu 객체를 사용자 정의 생성자에 전달하여 MenuAdminResponseDto를 생성한다:
new MenuAdminResponseDto(menu);
또는 @AllArgsConstructor를 사용해 모든 필드를 직접 전달할 수도 있다:
new MenuAdminResponseDto(menu.getId(), menu.getCategory(), menu.getName(), menu.getContent(), menu.getStatus(), menu.getCreatedAt());
- 두 경우 모두 생성자 호출 방식으로, 간단하지만 필드가 많아지면 가독성과 유지보수성이 떨어질 수 있다.
✅ Builder 패턴 vs 직접 생성자 호출
- 금융 시스템에서 DTO는 거래 내역, 결제 정보, 사용자 데이터 등 민감한 정보를 다루므로, 생성 방식의 선택은 보안과 유지보수성에 큰 영향을 미친다. MenuAdminResponseDto를 기준으로 두 방식의 장단점을 비교해보자.
1️⃣ 가독성과 유지보수성
Builder 패턴
- MenuAdminResponseDto는 현재 6개 필드를 가지지만, 금융 시스템에서는 규제나 비즈니스 요구사항(예: auditTrail, securityToken 추가)으로 필드가 늘어날 가능성이 크다.
- Builder 패턴은 각 필드를 명시적으로 지정하므로 가독성이 뛰어나며, 개발자의 실수를 줄인다.
직접 생성자 호출
- new MenuAdminResponseDto(menu)는 Menu 객체의 필드 매핑을 내부적으로 처리하여 간결하다. 하지만 필드 추가 시 사용자 정의 생성자 내부 로직을 수정해야 한다.
- 예를 들어, modifiedAt를 추가하려면 생성자를 업데이트해야 하며, 이는 기존 코드를 사용하는 모든 곳에 영향을 미칠 수 있다
💡 결론: 복잡하고 자주 변경되는 DTO를 다루는 금융 시스템에서 Builder 패턴은 가독성과 유지보수성 면에서 우수
📋 사례
글로벌 금융 플랫폼의 DTO 확장2021년, PayPal은 새로운 규제(예: EU의 PSD2)에 대응하기 위해 DTO에 authToken 필드를 추가했다. Builder 패턴을 사용한 덕분에 기존 코드 수정 없이 필드를 확장할 수 있었다.
2️⃣ 보안성과 데이터 무결성
Builder 패턴
- Builder는 불변 객체를 생성하기 쉽다.
- MenuAdminResponseDto는 final 필드를 사용해 불변성을 보장하며, Builder를 통해 .menuId(Objects.requireNonNull(menu.getId())) 같은 유효성 검사를 체이닝 단계에서 추가할 수 있다.
- 예를 들어, menuId가 null이거나 잘못된 값이면 결제나 메뉴 처리에 오류가 발생할 수 있다.
직접 생성자 호출
- MenuAdminResponseDto의 사용자 정의 생성자는 불변성을 보장하지만, 유효성 검사(예: menu.getId()가 null인지 확인)가 없다.
- @AllArgsConstructor를 사용하면 모든 필드를 수동으로 전달해야 하므로 실수로 null 값을 넣을 위험이 있다.
결론: 보안과 데이터 무결성을 위해 Builder 패턴은 더 안전한 선택
3️⃣ 성능
Builder 패턴
- MenuAdminResponseDto.builder()는 객체 생성 시 약간의 오버헤드가 있다. 하지만 금융 시스템에서 DTO 생성은 I/O 작업(예: 데이터베이스 쿼리)이나 네트워크 호출에 비해 훨씬 적은 시간을 차지하므로, 이 오버헤드는 무시할 만하다.
직접 생성자 호출
- new MenuAdminResponseDto(menu)는 생성자 호출이 간단하므로 약간의 성능 이점이 있다.
결론: 성능 차이는 미미하며, 금융 시스템에서 보안과 유지보수성은 성능보다 우선순위가 높으므로 Builder 패턴은 적합
4️⃣ 확장성과 재사용성
Builder 패턴
- MenuAdminResponseDto는 새로운 필드를 추가하거나 기존 필드를 제거할 때 유연하다.
- 예를 들어, PSD2 규제 대응을 위해 authToken 필드를 추가하려면 .authToken(menu.getAuthToken())만 추가하면 된다.
직접 생성자 호출
- new MenuAdminResponseDto(menu)는 새로운 필드를 추가하려면 사용자 정의 생성자를 수정하거나 새로운 생성자를 만들어야 한다.
✅ 결론
- 금융 IT 시스템에서 DTO는 복잡하고, 보안 요구사항은 엄격하며, 규제 변화로 인해 자주 수정된다.
- 따라서 가독성,보안성,유지보수성,확장성에 유리한 Builder 패턴이 적합하다.
'Spring' 카테고리의 다른 글
[Spring]N+1 문제 (1) | 2025.05.08 |
---|---|
[Spring] 트랜잭션 내부 호출 문제 (1) | 2025.05.01 |
[Spring]오프셋 페이징보다 커서 페이징을 써야하는 이유 (1) | 2025.04.25 |
[Spring/JPQL] JPQL이 무엇인지 알고 사용하자 (1) | 2025.04.18 |
[Spring]Converter 보다 세부적으로 타입변경하는 법 (0) | 2025.04.16 |