왜 API에서 Persistence Entities를 노출하는 것이 위험한 지름길인가
Source: Dev.to
예시 컨트롤러
@PostMapping("/books")
public BookItem create(@RequestBody BookItem bookItem) {
return bookService.save(bookItem);
}
왜 그렇게 느꼈는가
- 엔티티에 이미 모든 필드가 존재했다.
- 추가 클래스를 작성할 필요가 없었다.
- 개발 속도가 빨라졌다.
- 작은 프로젝트에 DTO는 불필요한 보일러플레이트처럼 보였다.
엔티티를 직접 노출할 때의 문제점
엔티티를 요청 본문으로 직접 사용할 경우 모든 필드가 쓰기 가능해진다. 이는 클라이언트가 다음을 할 수 있게 만든다:
- 데이터베이스 ID를 직접 지정한다.
- 절대 제어해서는 안 되는 필드를 수정한다.
- 내부 로직 전용 값들을 전송한다.
프레임워크는 의도를 알지 못하고 단순히 필드를 매핑하므로, API가 클라이언트를 지나치게 신뢰하게 된다.
엔티티별 고려사항
- 내부 플래그.
- 감사 타임스탬프.
- 클라이언트에게는 무관하거나 위험한 필드.
클라이언트가 이러한 필드를 사용하기 시작하면, 해당 필드는 공개 계약의 일부가 된다—예상치 못한 경우라도 말이다. 이것이 가장 치명적인 문제이며, 보통 너무 늦게 드러난다.
예시 엔티티 및 JSON 페이로드
@Entity
public class BookItem {
@Id
private String id;
private String title;
private String author;
private int pages;
}
{
"id": "123",
"title": "Clean Code",
"author": "Robert Martin",
"pages": 464
}
클라이언트는 이 응답을 기반으로 로직을 구성한다.
엔티티를 변경하면 API가 깨진다
예를 들어 pages가 부정확하다고 판단하고 wordCount로 교체했다고 가정해 보자:
@Entity
public class BookItem {
@Id
private String id;
private String title;
private String author;
private int wordCount;
}
컨트롤러는 수정하지 않았지만, 응답은 이제 다음과 같이 된다:
{
"id": "123",
"title": "Clean Code",
"author": "Robert Martin",
"wordCount": 150000
}
pages가 사라졌다.wordCount가 새로 등장했다.
결과: 클라이언트가 깨지고, 프론트엔드가 충돌하며, 모바일 앱이 실패한다. 이는 엔티티가 API 역할을 했기 때문에 발생한 일이다.
DTO 사용의 장점
DTO는 내부 모델과 외부 소비자 사이에 안정적인 경계를 만든다.
- 클라이언트는 허용된 필드만 전송할 수 있다.
- 응답은 안전한 데이터만 노출한다.
- 엔티티는 자유롭게 변경할 수 있다.
- API는 안정성을 유지한다.
결론
엔티티를 API에 직접 사용하는 것은 처음엔 생산적으로 보일 수 있지만, 실제 비용은 나중에 요구사항이 바뀌고 리팩터링이 위험해질 때 나타난다. 엔티티는 API가 아니다. DTO는 몇 개의 추가 클래스를 요구할 뿐이며, 시스템을 조용한 파손과 미래의 고통으로부터 보호한다.