DTO와 Entity

3/11/2024


DTO와 Entity

DTOEntity라는 용어를 많이 들어보았지만, 구체적인 구분없이 사용하다보니 정확한 개념이나 용도를 모르고 있었다.
JPA에 대해서 접하게 되면서 점차 그 구분이 필요할 것 같음을 느끼게 되었는데, 이번 기회에 조금 살펴보면서 나 나름대로의 구별을 해보고자 한다.
본문의 내용은 개인의 생각일 뿐이며, 아직 배워야 할 것이 많은 개발자의 짧은 견해이므로 옳고 그름을 말하는 것이 아님을 유의하자.
(잘못된 정보나 정정할 의견이 있다면 언제든지 프로필의 메일로 전달해주시길 바란다.)

Entity

EntitySpring에서 JPAMybatis등을 통해서 DB와 데이터를 주고받기 위한 객체이다.
즉, DB에게 질의할 쿼리의 내용이 되거나 조회 결과의 데이터를 담기 위한 것이다.
JPA를 사용한다면, Entity 클래스를 만드는 것으로 시작하여 Repository로 데이터를 저장하고 조회하는 모든 행위에 Entity를 통하게 된다.
JPA를 알기 전, MybatisiBatis 같은 프레임워크를 사용하면서는 조회한 데이터를 API결과로 전달하는 것에 있어서 별 다른 고민이 없었다.
그러한 프레임워크는 데이터를 조회할 때, 내가 작성한 SELECT문에 있는 컬럼만 가져오기 때문에 별 다른 문제점을 느끼지 못했을 것이다.
하지만 JPA를 사용하다보면 내가 직접 쿼리를 작성하지 않기 때문에 생기는 문제가 있다.
조회 결과로 받은 Entity를 그대로 API의 결과로 응답하게 된다면, 해당 API의 기능(목적)과를 무관한 정보가 함께 전달될 수 있다.
예) 사용자 목록 조회 시 민감한 정보들이 불필요하게 노출
(비밀번호, 생년월일, 주민번호, 전화번호 등)
때문에 DB 조회 결과 데이터와는 다른 객체를 API 응답으로 전달하는 것이 좋다.
이 때 사용하는 다른 객체가 DTO이다.

DTO

DTO는 프로젝트의 각 Layer마다 주고받는 목적의 Data Transfer Object이다.
즉, 꼭 API의 결과로만 쓰이는 것이 아니라, Controller <-> Service, Service <-> Repository, Controller <-> View 등 많은 관계에서 쓰일 수 있는 것이다.
다만, 서버 내부에서는 굳이 EntityDTO로 변환하지 않아도 문제가 될 소지가 적기 때문에 특정 목적을 위한 것이 아니라면 주로 Controller단에서 활용하는 것이다.
이렇게 사용하면 MVC패턴에서 Controller의 역할에도 부합하는 로직이라고 볼 수 있다.

Controller의 역할

Controller는 요청받을 내용을 내부 비즈니스 로직에 맞게 데이터를 검증, 정리, 가공하여 Service에 전달하고
Service가 수행한 결과를 목적에 맞게 다시 검증, 정리, 가공하여 알맞는 결과 데이터를 응답으로 내보낸다.
여기서 Controller가 주고받는 요청과 응답 데이터는 DTO, 이를 가공해서 Service에 전달 혹은 결과로 받는 데이터는 Entity가 된다. (물론 DTO나 VO일 수도 있다.)

물론 예외상황은 늘 존재한다.
중간에 Service에서 Entity데이터를 만들어야 하는 경우도 있고, Service애서 Entity를 DTO로 먼저 변환하는 것이 효율적인 로직이 있을 수도 있다.
결국 어디에서 DTO <-> Entity 변환을 거칠 것인지는 중요하지 않다. 다만 목적과 용도에 맞게 그 개념을 분리해서 사용해야 한다는 것이다.

DTO <-> Entity 변환

그렇다면 변환은 코드는 어떻게 구성하면 좋을까?
필자는 간단하게 Entity를 받아서 DTO를 만드는 DTO의 생성자 메서드를 만드는 방식을 이용한다.

@Entity
public class SampleEntity {
    private long id;
    private String name;
    private String phoneNumber;
    private String password;
}

@Data
public class SampleDTO {
    private long id;
    private String name;
    public SampleDTO(SampleEntity entity){
        this.id = entity.getId();
        this.name = entity.getName();
    }
}
SampleEntity sample = sampleRepository.findBy...(...);
SampleDTO sampleDTO = new SampleDTO(sample);

return sampleDTO;
List<SampleEntity> sampleList = sampleRepository.findAll();
List<SampleDTO> dtoList = sampleList.stream().map(sample->new SampleDTO(sample)).toList();

return dtoList;

번외

간혹 성능상의 이슈로 DTO를 직접 사용하여 Repository로부터 DB를 조회해야 하는 경우도 있다고 한다.
예를들어 게시글 정보 목록을 조회할 때, 게시글의 내용은 필요하지 않고 나머지 메타데이터만 필요하다고 해보자.
게시글 목록을 조회하는 데, Entity와 그 Repository를 그대로 사용한다면 DB는 해당 게시물들의 내용까지 조회하게 된다.
게시글의 내용이 길지 않거나 존재하는 글 수가 적다면 문제가 되지 않을 수 있겠지만 그 반대라면 심각한 성능 이슈가 발생할 수 있다.
이럴 때 JPA에도 필요한 컬럼만 조회하는 방법이 여럿 있는데,
그 중에는 Entity가 아니라 DTO를 직접 조회 결과로 받아 필요없는 컬럼을 애초부터 배제하는 방법도 있다. JPA에서 DTO를 직접 조회하는 방법은 다른 포스트에서 다루도록 하겠다.

develop

Inhyeok Kim

Email : inhyeok.kim@icloud.com

GithubPortfolio