REST API 개발 시 엔티티(Entity)를 그대로 노출하는 경우, 프론트엔드 개발자와의 협업에서 문제가 발생할 수 있습니다. 특히, 엔티티의 필드명이 변경되거나 데이터 형식이 변경될 경우, API를 사용하는 프론트엔드 코드도 함께 수정해야 하기 때문에 많은 비용과 혼란을 초래합니다. 이를 해결하기 위해 DTO(Data Transfer Object)를 사용하는 것이 중요합니다.
엔티티의 필드명이 예를 들어 createdAt에서 createDate로 변경되면, 프론트엔드 코드는 이를 반영하기 위해 반드시 수정되어야 합니다.
API 양식이 자주 변경되면 프론트엔드 개발자는 지속적인 코드 수정을 강요받아 업무 효율이 떨어집니다.
DTO(Data Transfer Object)는 데이터 전송을 위해 정의된 객체입니다. REST API에서는 엔티티 대신 DTO를 사용하여 데이터를 반환함으로써 API 형식의 일관성과 안정성을 유지할 수 있습니다.
API의 안정성 보장: 엔티티 구조가 변경되어도 DTO 구조는 유지될 수 있어 프론트엔드에 영향을 최소화합니다.
보안 강화: 민감한 데이터를 포함하지 않고, 필요한 데이터만 반환할 수 있습니다.
프론트엔드 요구사항 반영: 프론트엔드에서 필요한 데이터 형식이나 추가 계산된 데이터를 포함할 수 있습니다.

REST API는 다건조회(리스트)와 단건조회(상세)에서 서로 다른 데이터를 반환할 수 있습니다. 이 경우에도 DTO를 활용하면 API를 효과적으로 설계할 수 있습니다.
다건조회는 보통 리스트 형태로 반환됩니다. 필요한 핵심 데이터만 포함하는 DTO를 설계해야 합니다.
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class PostSummaryDto {
private Long id;
private String title;
private String summary;
private LocalDateTime createdAt;
}
Post 엔티티의 모든 데이터를 반환할 필요가 없습니다. 예를 들어, 게시글 본문(content)은 제외하고, 제목, 요약, 생성일 등의 핵심 정보만 반환합니다.@RestController
@RequestMapping("/api/v1/posts")
public class ApiV1PostController {
private final PostService postService;
@GetMapping
public List<PostSummaryDto> getAllPosts() {
return postService.getAllPosts()
.stream()
.map(post -> new PostSummaryDto(post.getId(), post.getTitle(), post.getSummary(), post.getCreatedAt()))
.collect(Collectors.toList());
}
}
단건조회는 상세 데이터를 반환하므로, 더 많은 정보를 포함할 수 있습니다.
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class PostDetailDto {
private Long id;
private String title;
private String content;
private String authorName;
private LocalDateTime createdAt;
private LocalDateTime modifiedAt;
}
@RestController
@RequestMapping("/api/v1/posts")
public class ApiV1PostController {
private final PostService postService;
@GetMapping("/{id}")
public PostDetailDto getPostById(@PathVariable Long id) {
Post post = postService.getPostById(id);
return new PostDetailDto(
post.getId(),
post.getTitle(),
post.getContent(),
post.getAuthor().getName(),
post.getCreatedAt(),
post.getModifiedAt()
);
}
}
DTO는 데이터 전송을 위한 객체이므로, 클라이언트가 필요로 하는 데이터만 포함해야 합니다.
DTO는 데이터 전달이 목적이므로 가변성을 최소화해야 합니다. 가능하면 final 필드를 사용하거나, 생성자로 초기화하여 불변성을 유지합니다.
DTO는 엔티티와 완전히 분리되어야 합니다. 엔티티의 변경이 DTO에 영향을 미치지 않도록 설계합니다.
ModelMapper 또는 MapStruct 라이브러리를 사용할 수 있습니다.@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
PostSummaryDto postSummaryDto = modelMapper.map(post, PostSummaryDto.class);
public static PostSummaryDto fromEntity(Post post) {
return new PostSummaryDto(
post.getId(),
post.getTitle(),
post.getSummary(),
post.getCreatedAt()
);
}
즉, 잘 설계된 REST API는 DTO를 사용하여 다건/단건 조회를 효율적으로 처리합니다.
정의: 서버와 클라이언트 간의 통신을 효율적으로 처리하는 아키텍처 스타일.
역할: 클라이언트가 리소스를 요청(GET, POST 등)하고 서버가 이에 응답(JSON 등)을 반환하는 방식을 설계.
목표: 일관된 인터페이스와 확장성을 가지는 API 설계.
정의: 데이터 전송에 특화된 객체 설계.
역할: REST API가 데이터(엔티티)를 클라이언트에 반환하거나 클라이언트로부터 데이터를 받을 때, 불필요한 정보 노출을 방지하고 데이터 구조를 맞추기 위해 사용.
목표: 엔티티(Entity)와 분리된 데이터 전송 객체를 만들어 API 안정성을 높이고, 클라이언트와 서버 간의 데이터를 효율적으로 주고받음.
정의: REST API에서 데이터를 반환하는 요청 유형.
역할:
목표: 요청의 유형에 따라 필요한 데이터를 클라이언트에 제공.
[Spring Boot] 레이어드 아키텍처, REST API
dto(Data Transfer Object)를 사용하는 이유