본문 바로가기

항해 99/Spring

Entity와 DTO의 분리

Dto와 Entity를 분리해서 사용하는 이유

DTO와 Entity 구분

  • DTO(Data Transfer Object) : 클라이언트와 서버 간 데이터 전송을 위해 설계된 객체
  • Entity : 데이터베이스에 저장되는 데이터 객체로, 데이터베이스와 직접적으로 연결

 

Entity를 직접 반환할 경우 생기는 문제점

엔티티 구조 변경 시 발생하는 문제

요구사항이 변경되어 엔티티 필드 이름이 변경 될 경우, API 스펙이 변경되어 추가 작업이 요구된다.

효과적인 유지 보수가 어려워 시간과 비용이 증가함

 

 

필요한 데이터만 전송하기 어렵다

엔티티를 직접 반환하면 엔티티에 존재하는 모든 데이터가 반환 됨(트래픽이 증가할 수 있으며 성능 및 비용면에서도 현저한 차이를 가져올 수 있음)

사용자가 필요로하는 데이터만 전송하기 어려움이 있으며, 이로 인해 불필요한 데이터를 사용자에게 전달될 수 있음.

 

 

순환 참조 문제

엔티티 간에 양방향 관계가 존재할 경우, 엔티티를 반환하는 순간 순환 참조로 인한 무한 JSON 직렬화 이슈 발생 가능

 

 

보안 문제

Entity를 반환하면 테이블을 공개하는 것이나 다름 없으므로 민감한 정보가 노출될 가능성이 있음

 

 

DTO와 Entity를 분리하여 얻는 장점

View와 Model의 분리(MVC 패턴)

DTO는 View(사용자 인터페이스)와 Controller(서버) 간의 인터페이스 역할을 하며, Entity는 Model(데이터베이스)의 역할을 함

분리를 통해 MVC 패턴을 적용하여, 코드의 가독성과 유지보수를 용이하게 할 수 있다.

 

 

필요한 데이터만 선별하여 서버 용량 최소화

DTO는 서버에서 클라이언트 데이터를 전송하는 데 사용

Entity 범위에서 필요한 필드를 재정의하여 필요한 데이터만 전송될 수 있도록 할 수 있음

전송하는 데이터양과 네트워크 대역폭 사용량이 최적화되어 더 빠른 응답시간과 전송시간을 얻을 수 있다.

 

 

엔티티 구조가 변경되어도 안전

엔티티 구조가 변경되더라도 DTO를 사용하여 데이터 전송을 처리하면 이 변경 사항이 클라이언트에 직접적으로 영향을 미치지 않음.

DTO를 사용하면 엔티티의 변경으로 인한 영향을 최소화할 수 있으며, 클라이언트에 필요한 데이터만 전달할 수 있음

서버 측에서는 엔티티의 변경을 해결하고 필요한 필드를 추가하거나 수정한 DTO를 클라이언트에 노출시키면 됨

클라이언트와 서버 간의 결합도를 낮추고 유지보수가 용이해짐.

 

 

순환 참조 예방

DTO는 엔티티 간의 양방향 참조가 포함되지 않은 간단한 구조를 가짐

클라이언트와 서버 간의 통신 과정에서 복잡한 객체 간 참조가 최소화되며, 순환 참조 문제를 예방할 수 있음

 

 

Validation 코드와 모델링 코드 분리

  • validation 코드 : @NotNull, @NotEmpty, @NotBlank 등
  • 모델링 코드 : @Column, @JoinColumn, @ManyToOne, @OneToOne 등

Entity는 DB의 테이블과 매칭되는 필드가 선언되어 있어 모델링을 위한 코드가 추가 됨

Entity에 validation 코드가 들어가게 되면 엔티티 클래스는 더 복잡해지고 가독성이 떨어진다.

각각의 요청마다 다른 DTO를 만들어 상황에 따라 필요한 validation을 추가하면 Entity 클래스의 모델링에 집중할 수 있음

 

 

보안 강화

DTO와 Entity를 분리함으로써, 테이블 구조는 서버측에만 알 수 있으므로 해커들이 데이터를 조작하는 것을 어렵게 할 수 있음, DTO와 Entity를 분리하여 API 보안성을 강화시킬 수 있음

 

 

DTO와 Entity 변환 위치

Controller - 클라이언트의 요청을 받고 응답을 반환

컨트롤러에서는 DTO의 형태로 데이터를 받아 서비스에 전달

@RequiredArgsConstructor
@RestController
public class PostsApiController {
	@PostMapping("/api/v1/posts")
    public Posts save(@Validated @RequestBody PostsSaveRequestDto requestDto) {
        return postsService.save(requestDto);
    }
    
	@PutMapping("/api/v1/posts/{id}")
    public Posts update(@PathVariable Long id, @RequestBody PostsUpdateRequestDto requestDto) {
        return postsService.update(id, requestDto);
    }
	...
}

 

 

Service - 비즈니스 로직을 수행하며 데이터 처리를 담당

서비스에서는 컨트롤러에서 받은 DTO를 Entity로 변환하고, 필요한 작업을 수행한 뒤에 Repository에 Entity를 전달

@Service
@RequiredArgsConstructor
public class PostsService {
 
    private final PostsRepository postsRepository;
 
    @Transactional
    public Posts save(PostsSaveRequestDto requestDto) {
        return postsRepository.save(requestDto.toEntity());
    }
 
    @Transactional
    public Posts update(Long id, PostsUpdateRequestDto requestDto) {
        Posts posts = postsRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id = " + id));
        posts.update(requestDto.getTitle(), requestDto.getContent());
        return posts;
    }
    ...
}

 

 

Repository - 데이터베이스와 인터페이스 역할

서비스에서 Entity를 전달받아 영속화를 처리한다.

public interface PostsRepository extends JpaRepository<Posts, Long> {
}

 

 

 

Entity ↔ DTO 변환 메서드 구현

toEntity(), toDto() 같은 메서드를 DTO 클래스에 직접 변환 메서드를 구현하여 상호 변환을 수행할 수 있음

해당 변환 메서드는 해당 클래스의 인스턴스를 기반으로 상대 클래스의 인스턴스를 생성하고 반환한다.

@Getter
@NoArgsConstructor
public class PostsSaveRequestDto {
 
    @NotEmpty
    private String title;
 
    private String content;
 
    private String author;
 
    @Builder
    public PostsSaveRequestDto(String title, String content, String author) {
        this.title = title;
        this.content = content;
        this.author = author;
    }
 
    public Posts toEntity() {
        return Posts.builder()
                .title(title)
                .content(content)
                .author(author)
                .build();
    }
    
    public PostsSaveRequestDto toDto(Posts posts) {
        return PostsSaveRequestDto.builder()
                .title(posts.getTitle())
                .content(posts.getContent)
                .author(posts.getAuthor)
                .build();
    }
}

 

'항해 99 > Spring' 카테고리의 다른 글

ExceptionHandler  (0) 2024.03.11
QueryDSL  (0) 2024.03.08
lombok 주의 사항  (0) 2024.03.06
Controller, RestController 차이  (0) 2024.03.05
메서드 명 find와 get의 차이  (0) 2024.03.04