Spring 레이어 설계: Controller / Service / Repository 역할 정리

태수·2025년 12월 29일

웹 구조 탐구

목록 보기
3/5

기존에 저는 JSP 기반 MVC2 구조에서
Controller → Service → DAO(또는 MyBatis Mapper)
형태로 웹 애플리케이션을 구성해 왔습니다.

그러다 Spring Boot를 혼자 공부하면서 JPA 예제를 접하게 되었고,
기존에 사용하던 DAO 대신 Repository라는 계층이 등장하는 것을 처음 알게 되었습니다.

처음에는 DAO와 무엇이 다른지,
왜 굳이 이름이 바뀌었는지 혼란스러웠지만,
정리해보니 Repository는 JPA 환경에서
영속성 계층을 표현하는 표준적인 역할 이름에 가깝다는 것을 알게 되었습니다.

이번 글에서는 Spring에서 흔히 쓰는 레이어 구조인
Controller / Service / Repository가 각각 무엇을 책임지는지 정리해 보겠습니다.
핵심은 “각 레이어의 책임을 분리해서 변경에 강한 구조를 만든다”는 점입니다.


1) 레이어별 책임 요약

Controller (웹 계층)

Controller는 HTTP 요청/응답을 다루는 곳입니다.

  • URL 매핑, HTTP Method 처리 (GET/POST/PUT/DELETE)
  • Request 파라미터/바디 받기 (@PathVariable, @RequestParam, @RequestBody)
  • 입력값 검증 트리거
  • Service 호출
  • 응답 반환

Controller의 목표는 한 문장으로 요약하면
“웹 요청을 애플리케이션 로직으로 안전하게 연결하고, 결과를 다시 변환한다” 입니다.


Service (애플리케이션 계층)

Service는 비즈니스 로직을 처리하는 중심입니다.

  • 유스케이스(업무 시나리오) 구현
    예: “상품 등록”, “재고 차감”, “주문 생성”
  • 트랜잭션 경계 설정 (@Transactional)
  • 여러 Repository 조합, 도메인 규칙 적용
  • 예외 상황을 비즈니스 관점으로 처리
    예: “재고 부족”, “권한 없음”

Controller는 "어떻게 들어오고 나가는지"에 집중한다면,
Service는 "무엇을 해야 하는지”를 결정하는 계층입니다.


Repository (영속성 계층)

Repository는 DB 접근을 담당합니다.

  • 엔티티 저장/조회/수정/삭제
  • 쿼리 실행
  • DB와의 매핑/영속성 컨텍스트 관리

Repository는 비즈니스 규칙을 알 필요가 없고,
“데이터를 어떻게 가져오고 저장할지”에만 집중하는 것이 이상적입니다.

Repository라는 용어는 JPA(Spring Data JPA)에서 공식적으로 굳어진 개념입니다.
JpaRepository를 사용하면 기본적인 CRUD와 단순 조회 로직을
비교적 간단한 코드로 구현할 수 있다는 점이 꽤 인상적이었습니다.
이 부분은 나중에 별도의 글에서 더 자세히 다뤄보도록 하겠습니다.


2) Controller를 “얇게” 만드는 이유

Controller가 두꺼워질수록 아래 문제가 쉽게 생깁니다.

  • HTTP 처리 로직과 비즈니스 로직이 섞여서 읽기 어려워집니다.

  • 같은 로직이 여러 Controller에 복붙되기 쉽습니다.

  • 테스트가 어려워집니다(웹 환경 없이 로직만 검증하기 힘듦).
    반대로 Controller를 얇게 유지하면 장점이 큽니다.

  • Service 중심으로 로직이 모이므로 재사용이 쉬워집니다.

  • 테스트(단위 테스트/서비스 테스트)가 쉬워집니다.

  • 화면 반환 방식이 바뀌어도 핵심 로직은 덜 흔들립니다.


3) DTO / Entity를 왜 분리하나?

Entity를 그대로 요청/응답에 쓰면 생기는 문제

  • 보안: 민감한 필드가 의도치 않게 노출될 수 있습니다.
  • 결합도: API 스펙이 DB 모델에 종속됩니다.
  • 변경 비용: 엔티티 변경이 곧 API 변경으로 이어지기 쉽습니다.
  • 검증/표현 문제: 요청 검증 규칙과 엔티티의 제약이 충돌할 수 있습니다.

그래서 일반적으로는

  • 요청은 Request DTO
  • 응답은 Response DTO
  • DB는 Entity
    로 분리하는 것이 안정적입니다.

4) DTO ↔ Entity 변환은 어디서 하는 게 좋을까?

정답은 하나로 고정되어 있진 않지만, 기준을 세우면 일관성을 만들 수 있습니다.

추천 기준: “Controller는 DTO만, Repository는 Entity만”

  • Controller: DTO를 받고 DTO를 반환
  • Repository: Entity만 저장/조회
  • Service: DTO ↔ Entity 변환 및 비즈니스 로직 처리의 중심

이렇게 두면 계층의 경계가 명확해집니다.


변환 위치 선택 기준 (현실적인 가이드)

A) Controller에서 변환하는 방식

  • 장점: Service는 엔티티 중심으로 깔끔해 보일 수 있습니다.
  • 단점: Controller가 비대해지고, 변환 로직이 여러 곳으로 퍼지기 쉽습니다.

Controller가 얇아야 한다는 목표와 충돌하기 쉽습니다.

B) Service에서 변환하는 방식 (가장 흔한 선택)

  • 장점: 변환 로직이 서비스에 모여 재사용/테스트가 쉽습니다.
  • 장점: Controller는 입력/출력만 담당하기 쉬워집니다.
  • 단점: Service가 커질 수 있으므로, 변환이 복잡해지면 Mapper를 분리해야 합니다.

대부분의 CRUD/중간 규모 프로젝트에서는 이 방식이 가장 무난합니다.

C) 별도의 Mapper 계층을 두는 방식

  • 장점: 변환이 복잡할수록(조합/중첩/필드 가공) 코드가 깔끔해집니다.
  • 장점: Service는 “업무 규칙”에 집중할 수 있습니다.
  • 단점: 파일이 늘어나고 초기에는 과해 보일 수 있습니다.

프로젝트가 커지거나 응답 구조가 복잡해지면 자연스럽게 선택되는 방향입니다.


5) 결론: 제가 잡는 실전 규칙

  • Controller는 최대한 얇게: 요청 받고 검증하고 Service 호출하고 응답만 반환
  • Service가 유스케이스의 중심: 트랜잭션 + 규칙 + 조합 + 변환
  • Repository는 DB 접근만: 엔티티 단위로 저장/조회

DTO ↔ Entity 변환은 기본적으로 Service(또는 Mapper)에 두고,
Controller에는 변환 로직이 쌓이지 않도록 관리하는 것을 목표로 하겠습니다.

마무리

Spring의 계층 분리는 각 역할의 책임을 명확히 하고,
변경에 강한 구조를 만들 수 있다는 점에서
개인적으로 흥미롭게 느껴집니다.

이 글에서 정리한 기준을 바탕으로,
이후 글에서는 JPA와 Entity 설계 전반을 정리해보겠습니다.

0개의 댓글