Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

밤빵's 개발일지

[TIL]20241204 DTO와 Entity 간 변환 방식 고민하기 본문

개발Article

[TIL]20241204 DTO와 Entity 간 변환 방식 고민하기

최밤빵 2024. 12. 4. 02:20

프로젝트를 진행하면서 DTO와 Entity 간의 변환 방식에 대해 고민하게 되었다. 변환의 필요성은 크게 두 가지로 첫째, Entity는 데이터베이스와 직접 연관된 구조이기 때문에 외부 API 응답으로 적합하지 않다. 둘째, DTO를 이용해 비즈니스 로직의 변화에 유연하게 대처하자 였다. 다양한 변환 방식이 존재하는데, 각각의 장단점을 비교해보며 개발 일지로 정리하기로 했다. 


1. of, from 정적 메서드 패턴

of, from 같은 정적 팩토리 메서드를 활용해 변환을 구현하는 방식은, 코드의 가독성을 높이고 변환 로직을 명확히 정의할 수 있다.

public class UserDto {
    private String name;
    private String email;

    public static UserDto from(UserEntity entity) {
        UserDto dto = new UserDto();
        dto.name = entity.getName();
        dto.email = entity.getEmail();
        return dto;
    }
}

→ 이 방식은 각 클래스 내부에 변환 메서드를 정의하기때문에 코드의 일관성을 유지하는 데 도움이 된다. 특히 단일 객체 변환에 적합하고, 코드의 가독성을 높여준다. 하지만, 엔티티와 DTO 간의 변환 로직이 서로 종속되어 있기 때문에 유지 보수 시 양쪽 클래스를 동시에 수정해야 하는 번거로움이 발생할 수 있다. 또한 변환 로직이 단일 객체에 한정되고, 복잡한 변환 로직이나 다수 객체 변환에는 적합하지 않다는 단점이 있다. 

변환이 단순하고 클래스가 서로 1:1로 매핑되는 경우에 적합. 코드 가독성을 높이고 간단한 상황에서 쉽게 적용할 수 있다.

2. Stream API를 이용한 변환

특히 컬렉션 형태의 데이터를 변환해야 할 때 Stream API를 이용하면 간결하고 깔끔하게 처리할 수 있다.

List<UserDto> userDtos = userEntities.stream()
                                     .map(UserDto::from)
                                     .collect(Collectors.toList());

→ Stream API를 이용하면 가독성이 높고 코드가 간결해진다. 특히, 여러 개의 데이터를 일괄적으로 변환해야 할 때 큰 도움이 된다. 다만, 단일 객체 변환 시에는 오히려 복잡해 보일 수 있다는 단점이 있고, 변환 로직이 컬렉션 처리에 집중되기때문에 변환 로직 재사용이 어렵다는 점과, 변환로직이 복잡해지면 코드가 난해해질 수 있다. 주로 컬렉션 데이터 변환에 적합한 방식이라고 생각된다.

List 나 컬렉션 데이터를 일괄 변환하는 상황에서 적합. 간결한 코드와 병렬 처리가 장점이다. 

3. Converter 클래스 활용

Converter 클래스를 별도로 정의해서 변환 로직을 분리하는 방식도 있다. 이 방식은 단일 책임 원칙을 준수하고, 변환 로직을 한 곳에 집중시켜 유지 보수성을 높여준다.

public class UserConverter {
    public static UserDto convertToDto(UserEntity entity) {
        return new UserDto(entity.getName(), entity.getEmail());
    }

    public static UserEntity convertToEntity(UserDto dto) {
        return new UserEntity(dto.getName(), dto.getEmail());
    }
}

→ Converter 클래스는 변환 로직을 독립적으로 관리할 수 있기 때문에, 여러 클래스 간의 결합도를 낮추고 재사용성을 높일 수 있다는 장점이 있다. 다만, 별도의 클래스를 만들어야 하기 때문에 코드 구조가 복잡해질 수 있다. 특히 프로젝트 규모가 커질수록 구조가 복잡해질 수 있기 때문에, 적절한 클래스 설계와 관리가 필요하다.

변환로직이 복잡하거나 규모가 큰 에플리케이션에서 재사용성과 유지보수성을 높일 수 있는 좋은 선택이 될 수 있다. 

4. MapStruct와 같은 라이브러리 사용

MapStruct와 같은 매핑 라이브러리를 사용하는 방법도 있다. 이 방식은 어노테이션 기반으로 자동화된 코드를 생성하여 변환을 도와준다. 변환 코드 작성에 대한 수고를 덜어주며, 컴파일 타임에 오류를 검출할 수 있다는 장점이 있다.

@Mapper
public interface UserMapper {
    UserDto toDto(UserEntity entity);
    UserEntity toEntity(UserDto dto);
}

→ 라이브러리를 사용하면 개발 생산성이 높아지고 코드의 중복을 줄일 수 있다. 하지만 추가적인 의존성을 도입해야 하고, 라이브러리 자체에 대한 학습이 필요하다는 점에서 처음에는 진입 장벽이 있을 수 있다. 특히, 변환 로직을 자동으로 생성하는 라이브러리를 사용하는 겨우, 예외 처리 등 세부적인 제어가 어려울 수 있다는 점에서 초기 설정 및 학습이 필요하다.프로젝트의 복잡성이나 변환 작업의 빈도에 따라  Convert 방식이 적절한 선택이 될 수 있다.


단일 객체 변환 시에는 of, from 메서드 패턴을 사용하고, 컬렉션 형태의 변환 시에는 Stream API를 사용하는 것이 적절하다고 느꼈다. 실제로 of, from 메서드 패턴은 단일 객체의 변환에 적합하다는 평가를 받고 있기도 하고, 코드 가독성과 직관성을 높이는 데 큰 장점이 있다. 만약 변환 로직이 점점 복잡해지고 재사용성이 중요해진다면 Converter 클래스로의 확장을 고려할 수 있을 것이다. 이번 고민을 통해 각 변환 방식의 장단점을 이해하게 되었고, 상황에 맞는 적절한 선택의 중요성을 알게 되었다. 앞으로도 코드의 유지 보수성과 효율성을 고려하며 더 나은 방식을 지속적으로 고민하고 공부해가는게 좋을 것 같다!