본문 바로가기

Newly I Learned

왜 Entity를 그대로 반환하면 안 될까? – 백엔드 계층 분리의 이유


Spring 프로젝트를 시작하면 자연스럽게 마주치는 구조가 있다:  
Controller, Service, Repository, DTO, Entity...

처음에는 각 구성 요소의 이름은 달라도, 역할이 비슷해 보여 혼란스러울 수 있다.  
특히 Entity와 DTO는 구조도 비슷하고, 필드까지 겹치는 경우가 많아 자주 혼동된다.

하지만 이 각각의 구성은 명확한 목적과 책임을 가지고 있으며,  
분리해야 하는 이유도 소프트웨어 설계 원칙에 기반해 분명하게 존재한다.  
이번 글에서 그 이유와 구조적 배경을 하나씩 짚어보자.



처음엔 헷갈릴 수밖에 없다


DTO와 Entity는 구조적으로 닮았다. 아래처럼 생겼다면, 굳이 두 개로 나눠야 하나? 하는 생각이 들 수 있다.


DTO

public class UserRequest {
    private String name;
    private String email;
}



Entity

@Entity
public class User {
    private String name;
    private String email;
}



작은 프로젝트에서는 Entity를 @RequestBody나 @ResponseBody로 그대로 써도 동작에는 문제가 없다.  
그래서 입문 단계에서는 “굳이 분리해야 하나?”라는 의문이 생기기 쉽다.

하지만 프로젝트 규모가 커지거나, 보안 · 유지보수 · 유연한 API 설계가 중요해질수록
DTO와 Entity의 분리는 선택이 아니라 설계 원칙에 가까운 필수가 된다.



DTO와 Entity, 뭐가 다른가?

  • 목적  
      - Entity: DB 저장용 도메인 객체  
      - DTO: 요청/응답 전용 데이터 객체
  • 위치  
      - Entity: Service, Repository 내부  
      - DTO: Controller ↔ 클라이언트 사이

  • 예시  
      - Entity: Item, User  
      - DTO: ItemCreateRequest, ItemResponse
  • 특징 
    - Entity: JPA 매핑, 연관관계 포함 가능
    - DTO: 순수 데이터 전달 객체, 로직 없음


Entity는 DB와 직접 매핑되는 JPA 객체이고,  
DTO는 외부 요청을 받을 때나 응답을 보낼 때 사용하는 전용 전달 객체다.  
DTO에는 비즈니스 로직이 들어가면 안 된다.


왜 나눠야 할까?


1. 보안  
Entity에는 비밀번호, 권한 같은 민감한 데이터가 포함되기 쉽다.  
이걸 그대로 외부에 반환하면, 불필요한 정보가 클라이언트에 노출될 수 있다.

2. 유지보수  
Entity는 DB 구조에 종속되므로, 컬럼이 바뀌면 API 응답까지 영향을 줄 수 있다.  
DTO를 중간에 두면 외부 인터페이스를 안정적으로 유지할 수 있다.

3. 책임 분리 (SRP)  
SRP: Single Responsibility Principle (단일 책임 원칙)  
"클래스는 하나의 책임만 가져야 하며, 변경의 이유는 단 하나뿐이어야 한다."

- DTO: 표현 전용  
- Entity: 저장 전용  
- Controller: 요청/응답 처리  
- Service: 로직 담당  
- Repository: DB 접근만


객체지향프로그래밍 수업(+소프트웨어공학)에서 배운 내용과 연계해보자.

 

프로그래밍에서 클래스를 설계할 때, 어떤 역할을 맡기는지가 중요하다.  
클래스마다 ‘데이터를 담을 건지’, ‘로직을 처리할 건지’, ‘입출력을 담당할 건지’와 같은 책임이 다르기 때문이다.

이런 책임을 구분하기 위해 객체지향 설계에서는 클래스를 세 가지 유형(Class Stereotype)으로 나눈다:

 

- Boundary: 사용자나 외부 시스템과 정보를 주고받는 역할 → 예: Controller, DTO  
- Control: 프로그램의 흐름을 제어하고 로직을 실행하는 역할 → 예: Service  
- Entity: 실제 데이터를 담고 저장하는 역할 → 예: Entity 클래스

이 개념은 UML이라는 설계 도구에서 유래한 것으로,  
클래스 하나하나가 어떤 일을 맡는지 명확하게 나눠 설계하는 방식이다.

※ 참고로 Repository는 DB와 직접 연결되어 데이터를 읽고 쓰는 기술적인 도구에 가깝기 때문에,  
위에서 말한 세 가지 역할(Class Stereotype)에는 포함되지 않는다.

Spring 프레임워크는 이 세 가지 역할 구분을 실제 코드에 잘 녹여낸 구조를 가지고 있다.  
예를 들어, Controller는 사용자 요청을 받고, Service는 로직을 처리하며, Entity는 데이터 자체를 표현한다.  
이렇게 역할이 나눠지면 각 클래스가 하나의 책임만 가지게 되어,  
코드 변경이 생기더라도 영향을 최소화할 수 있고 유지보수도 쉬워진다.

그리고 Spring은 이보다 더 넓은 설계 기준도 함께 따른다.  
바로 3-Tier 아키텍처와 MVC 패턴이다.

- 3-Tier 아키텍처는 프로그램 전체를 세 부분으로 나누는 방식이다:  
  화면을 담당하는 영역 (Presentation Layer),  
  비즈니스 로직을 처리하는 영역 (Business Logic Layer),  
  데이터베이스와 연결된 영역 (Data Access Layer).  
  이렇게 나누면 화면, 로직, 데이터 처리가 분리되어 코드가 섞이지 않고,  
  기능을 수정하거나 확장할 때 다른 부분에 미치는 영향을 줄일 수 있어 유지보수가 쉬워진다.

- MVC 패턴은 주로 웹 애플리케이션에서 사용되는 구조로,  
  데이터를 다루는 Model,  
  화면 출력을 담당하는 View,  
  사용자 요청을 처리하는 Controller로 나눠 설계하는 방식이다.  
  이 구조를 따르면 화면 처리 코드와 로직 코드가 뒤섞이지 않기 때문에,  
  복잡한 프로젝트에서도 코드를 체계적으로 관리할 수 있고,  
  여러 개발자가 동시에 작업할 때 충돌을 줄일 수 있다.

Spring은 이 두 구조를 함께 반영해,  
기능별로 명확하게 계층을 나누고, 요청 흐름도 정돈된 방식으로 처리할 수 있도록 도와준다.


전체 구조의 동작 방식


Spring에서는 API 요청이 다음과 같은 흐름으로 처리된다.

[클라이언트 요청]  
    ↓  
@Controller  
  - 입력: DTO (@RequestBody)  
  - 역할: 요청 수신, 응답 반환  
  - 3-Tier: Presentation Layer  
  - MVC: Controller  
  - Class Stereotype: Boundary  
    ↓  
@Service  
  - 역할: 비즈니스 로직 처리  
  - 3-Tier: Business Logic Layer  
  - Class Stereotype: Control  
    ↓  
@Repository  
  - 역할: DB 접근 (JPA 등)  
  - 3-Tier: Data Access Layer  
    ↓  
@Entity  
  - 역할: 도메인 데이터 정의, DB 매핑  
  - 3-Tier: Data Access Layer  
  - MVC: Model  
  - Class Stereotype: Entity  
    ↓  
[DB 처리 완료]  
    ↑  
DTO로 응답 데이터 구성  
  - 출력: DTO (@ResponseBody)  
  - 3-Tier: Presentation Layer  
  - MVC: Model  
  - Class Stereotype: Boundary  
    ↑  
@Controller에서 응답 반환  
    ↑  
[클라이언트 응답]


위 흐름을 보면 각 계층이 어떤 책임을 가지고 동작하는지 명확히 드러난다.  
Controller는 외부 요청을 받고 DTO를 통해 데이터를 전달하며,  
Service는 핵심 로직을 처리하고, Repository는 DB에 접근한다.  
Entity는 실제 데이터 구조를 표현하고 저장되며,  
응답 역시 DTO로 감싸서 클라이언트에 전달된다.

이렇게 책임이 분리된 구조 덕분에,  
코드는 변경에 강하고 테스트하기 쉬운 형태로 유지될 수 있다.

 


예시: 상품 등록 API

// DTO
public class ItemCreateRequest {
    private String name;
    private int stock;
}

// Entity
@Entity
public class Item {
    @Id @GeneratedValue
    private Long id;
    private String name;
    private int stock;
}

// Service
public void createItem(ItemCreateRequest dto) {
    Item item = new Item(dto.getName(), dto.getStock());
    itemRepository.save(item);
}
```

```java
// Controller
@RestController
@RequiredArgsConstructor
public class ItemController {
    private final ItemService itemService;

    @PostMapping("/items")
    public ResponseEntity<Void> create(@RequestBody ItemCreateRequest request) {
        itemService.createItem(request);
        return ResponseEntity.ok().build();
    }
}

 


- DTO는 요청/응답용 전용 객체  
- Entity는 DB에 저장되는 핵심 모델  
- Service는 비즈니스 로직을 담당  
- Controller는 요청 흐름을 조율


마무리

Entity를 그대로 반환하지 않고 DTO를 사용하는 이유는  
단순한 스타일이나 개발자 커뮤니티의 관행 때문이 아니다.

- 객체지향 설계 원칙 (SRP)  
- 클래스 역할 분리 (Boundary / Control / Entity)  
- 3-Tier Architecture  
- MVC 패턴

이 모든 설계 철학이 녹아 있는 것이 바로 Spring의 계층 구조다.

DTO와 Entity를 나누는 것은 단순한 스타일이나 편의의 문제가 아니라,
확장성과 유지보수를 고려한 소프트웨어 설계의 핵심 원칙이다.

이 구조를 이해하고 적용하는 순간,  
코드는 더 유연해지고, 구조는 더 안정적이게 된다.