java and sping

[Spring] DTO를 사용하는 이유?

코딩하는 공부방 2023. 3. 18. 01:03

안녕하세요
오늘은 Spring 공부를 하면서
DTO로 이용해서 데이터 간의 이동이 많이 일어나는데요

DTO를 자세하게 쓰는 방법이랑 개념에 대해서 자세하게 살펴보겠습니다.

 


 Entity 란? 

Entity 클래스는 DB 테이블에 존재하는 Column들을 필드로 가지는 객체를 말합니다. Entity는 DB의 테이블과 1대 1로 대응되며 때문에 가지지 않는 칼럼을 필드로 가져서는 안 됩니다. 또한 Entity 클래스는 다른 클래스를 상속받거나 인터페이스의 구현체여서는 안 된다.

예를 들어 Member라는 테이블이 id, name, email, phoneNumber 라는 칼럼들을 가지고 있다면, 이에 대응하는 클래스인 Post는

@Entity
public class Post {
    private Long id;
    private String name;
    private String email;
    private String phoneNumber;
}

와 같이 post 테이블의 컬럼들을 필드로 가져야 합니다.

JPA로 수동으로 DDL를 생성해서 Entity를 참고하여서 테이블을 만들어주기도 한다. 밑에 글을 참고하자

2023.02.16 - [java and sping] - [Spring] JPA 데이터베이스 스키마 자동 생성 - 속성

 

[Spring] JPA 데이터베이스 스키마 자동 생성 - 속성

JPA는 데이터베이스 스키마를 자동 생성하는 기능을 지원한다. 그래서 DB에 미리 만들어 놓지 않아도 JPA를 통해 코드를 작성해서 자동으로 생성시켜줘서 편하다. 클래스의 매핑 정보를 보면 어떤

nosechild.tistory.com

 

JPA를 사용할 때 Entity 클래스에는 @Entity 어노테이션을 붙여서 Entity임을 명시해 줘야 하며, 내부의 필드에는 @Column, @Id 어노테이션 등을 사용한다. Entity는 외부에서 최대한 Entity의 Getter를 사용하지 않도록 내부에 로직을 구현하는데, 이때 Domain 로직만 구현하고 Presentation 로직은 구현하지 않는다.

 

EntityGetter 사용을 최대한 피하라고 했지만, 기본적으로 Entity를 만들 때 Getter는 만들어줘야 합니다. Entity 클래스에서는 Setter를 만드는 것 피하라고 배웠습니다...

 

Setter를 무분별하게 사용하게 되면, Entity의 인스턴스 값들이 언제 어디서 변하는지 명확히 알 수 없다. 따라서 Setter 대신 다른 방법으로 필드에 값을 넣어 주는 것이 좋다. 그런데 일반적으로 생각할 수 있는 인스턴스의 생성 시점에 생성자로 필드에 값을 넣어주는 방법 또한 그 다지 좋지 않은 방법일 수 있는데, 생성자에 현재 넣는 값 어떤 필드인지 명확히 알 수 없고 매개변수끼리의 순서가 바뀌더라도 코드가 모두 실행되기 전까지는 문제를 알 수 없다는 단점이 있다.

따라서 Builder 패턴을 사용하는 것이 좋다. 멤버 변수가 많아지더라도 어떤 값을 어떤 필드에 넣는지 코드를 통해 확인할 수 있고, 필요한 값만 집어넣는 것이 가능하기 때문이다.

@Getter
@Entity
@NoArgsConstructor
public class Membmer member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;
    @Column(length = 13, nullable = false)
    private String phoneNumber;
 
    @Builder
    public Member(Long id, String name, String email, String phoneNumber) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.phoneNumber = phoneNumber;
    }
}

// 사용 방법

Member member = new member.builder()
        .name("홍길동")
        .email("test@gmail.com")
        .phoneNumber("010-1234-5678")
        .build();

 DTO(Data Transfer Object)란? 

DTO(Data Transfer Object)란 계층 간 데이터 교환을 위한 사용하는 객체(Java Beans)입니다.

DTO를 사용하는 이유는, 자바 domain 객체를 접근하지 않기 위해서입니다.
간단하게 생각해서, 테이블을 조작하기 위한 한 단계의 보호막이 있다고 생각하면 됩니다.

DTO는 서로 다른 레이어 간 데이터 전송을 용이하게 합니다. 예를 들어 Spring에서는 일반적으로 데이터베이스와 컨트롤러, 뷰, 서비스 등 다양한 레이어를 사용합니다. 각 레이어에서 데이터를 처리하는 방식이 다르기 때문에, DTO를 사용하여 데이터 전송을 간단하게 처리할 수 있습니다. DTO는 레이어 간에 필요한 데이터만 포함하고 있기 때문에, 불필요한 데이터가 전송되지 않아 불필요한 데이터 처리를 최소화할 수 있습니다.

DTO는 데이터 전송을 보다 안전하게 처리할 수 있도록 도와줍니다. DTO는 자바 Bean 형태로 작성되어 있으며, 필드가 private이고 getter/setter 메소드를 사용하여 데이터를 가져오고 설정합니다. 이렇게 하면 일관성과 무결성을 보장할 수 있습니다. 또한, DTO는 직렬화/역직렬화를 통해 데이터 전송을 보다 안전하게 처리할 수 있습니다.

따라서, Spring에서 DTO를 사용하는 것은 데이터 전송의 안전성과 일관성을 유지하면서 레이어 간 데이터 전송을 간단하게 처리하기 위함입니다.

마틴파울러 - 객체 지향 분석 및 설계 / Patterns of Enterprise Application Architecture(P of EAA)

The main reason for using a Data Transfer Object is to batch up what would be multiple remote calls into a single call

마틴 파울러가 말하는 DTO의 주요 목적은 한번 호출로 여러 매개 변수로 일괄 처리해서 서버의 왕복을 줄이는 것입니다. 해당 호출에 관련된 모든 데이터를 가지고 있는 DTO 객체를 만들어서 네트워크 비용을 줄인다는 의미입니다.


 Entity와 Dto 분리 이유 

1. 역할의 분리

Entity는 데이터베이스에서 사용되는 데이터를 모델링하는 역할을 하고, Dto는 비즈니스 로직에서 사용되는 데이터를 모델링하는 역할을 합니다. 즉, Entity는 데이터베이스와 직접적인 관계를 가지고 있는 반면, Dto는 데이터베이스와 관계없이 비즈니스 로직에서 사용되는 데이터를 모델링합니다. 이렇게 역할을 분리함으로써 코드의 가독성과 유지보수성을 높일 수 있습니다.

2. 보안의 강화

Entity는 데이터베이스에 직접적으로 연결되어 있기 때문에, 보안상의 이유로 Entity를 외부로 노출시키는 것은 좋지 않습니다. 반면, Dto는 Entity와 다르게 비즈니스 로직에서만 사용되기 때문에, 외부에 노출해도 보안에 큰 문제가 없습니다.

3. 유연성의 증가

Entity Dto를 분리함으로써, Entity가 변경되어도 Dto는 그대로 유지될 수 있습니다. 만약 Entity가 변경되었을 때, Dto도 변경해야 한다면, 변경이 발생한 모든 코드를 찾아서 수정해야 하는 번거로움이 있습니다. 이런 문제를 예방하기 위해 Entity와 Dto를 분리하는 것입니다.

4. API 설계의 향상

API를 설계할 때, Entity를 그대로 사용하면 데이터베이스 구조가 외부로 노출되는 문제가 있습니다. 하지만 Dto를 사용하면 API 설계에서 필요한 필드만 선택해서 노출시킬 수 있습니다. 이렇게 필요한 필드만 노출시키면, API 사용자는 필요하지 않은 정보를 받을 필요가 없어지고, API의 성능도 향상됩니다.

 


 Entity → DTO 

  ModelMapper 라이브러리 사용하기  

ModelMapper는 객체 간 매핑을 쉽게 할 수 있도록 도와주는 라이브러리입니다.
ModelMapper를 사용하면 EntityDto 간의 매핑을 간단하게 처리할 수 있습니다.
예를 들어, 다음과 같은 User Entity가 있다고 가정해 봅시다.

@Entity
public class User {
    @Id
    private Long id;
    private String username;
    private String password;
    private String email;
    // ...
}

EntityDto로 변환하기 위해서는 다음과 같이 ModelMapper를 사용할 수 있습니다.

@Service
public class UserService {
    private final ModelMapper modelMapper;

    public UserService(ModelMapper modelMapper) {
        this.modelMapper = modelMapper;
    }

    public UserDto convertToDto(User user) {
        UserDto userDto = modelMapper.map(user, UserDto.class);
        return userDto;
    }
}

위 코드에서 ModelMappermap 메소드를 사용하여 User EntityUserDto로 변환합니다.

 수동으로 변환  

수동으로 EntityDto로 변환하는 방법도 있습니다. 이 방법은 ModelMapper를 사용하는 것보다는 번거로울 수 있지만, 세밀한 조정이 필요한 경우에는 유용합니다.

예를 들어, 다음과 같은 User Entity가 있다고 가정해 봅시다.

@Entity
public class User {
    @Id
    private Long id;
    private String username;
    private String password;
    private String email;
    // ...
}

EntityDto로 변환하기 위해서는 다음과 같이 수동으로 변환할 수 있습니다.

@Service
public class UserService {
    public UserDto convertToDto(User user) {
        UserDto userDto = new UserDto();
        userDto.setId(user.getId());
        userDto.setUsername(user.getUsername());
        userDto.setEmail(user.getEmail());
        // ...
        return userDto;
    }
}

위 코드에서 User Entity의 필드들을 하나씩 UserDto에 설정해 주는 방법으로 수동으로 변환합니다.
위와 같이 ModelMapper 라이브러리를 사용하거나 수동으로 변환하는 방법을 사용하여 EntityDto로 변환할 수 있습니다.


 DTO → Entity 

 ModelMapper 사용 

User EntityUserDto의 클래스가 있다고 가정하자

@Entity
public class User {
    @Id
    private Long id;
    private String username;
    private String password;
    private String email;
    // ...
}

public class UserDto {
    private Long id;
    private String username;
    private String email;
    // ...
}

DtoEntity로 변환하기 위해서는 다음과 같이 ModelMapper를 사용할 수 있습니다.

@Service
public class UserService {
    private final ModelMapper modelMapper;

    public UserService(ModelMapper modelMapper) {
        this.modelMapper = modelMapper;
    }

    public User convertToEntity(UserDto userDto) {
        User user = modelMapper.map(userDto, User.class);
        return user;
    }
}

위 코드에서 ModelMappermap 메소드를 사용하여  UserDtoUser Entity로 변환합니다.

 수동으로 변환 

수동으로 DtoEntity로 변환하는 방법도 있습니다. 이 방법은 ModelMapper를 사용하는 것보다는 번거로울 수 있지만, 세밀한 조정이 필요한 경우에는 유용합니다.

예를 들어, 다음과 같은 User Entity, UserDto가 있다고 가정해 봅시다.

@Entity
public class User {
    @Id
    private Long id;
    private String username;
    private String password;
    private String email;
    // ...
}

public class UserDto {
    private Long id;
    private String username;
    private String email;
    // ...
}

EntityDto로 변환하기 위해서는 다음과 같이 수동으로 변환할 수 있습니다.

@Service
public class UserService {
    public User convertToEntity(UserDto userDto) {
        User user = new User();
        user.setId(userDto.getId());
        user.setUsername(userDto.getUsername());
        user.setEmail(userDto.getEmail());
        // ...
        return user;
    }
}

위 코드에서 UserDto의 필드들을 하나씩 User Entity에 설정해 주는 방법으로 수동으로 변환합니다.
위와 같이 ModelMapper 라이브러리를 사용하거나 수동으로 변환하는 방법을 사용하여 DtoEntity로 변환할 수 있습니다.


참고 ※

https://thalals.tistory.com/215
https://velog.io/@ohzzi/Entity-DAO-DTO%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EB%A9%B0-%EC%99%9C-%EC%82%AC%EC%9A%A9%ED%95%A0%EA%B9%8C
https://w97ww.tistory.com/m/98