스프링을 배우기 시작하면 가장 먼저 마주치는 구조가 바로
Controller – Service – Repository 입니다.이 3계층 구조는 스프링뿐 아니라 대부분의 백엔드 시스템에서 사용되는 표준 아키텍처이며,
“유지보수성이 좋고 역할이 명확해지는 구조”입니다.이번 글에서는 각 계층의 역할, 실제 예제 코드, 데이터 흐름까지 완전히 이해할 수 있도록 설명합니다.
📌 목차
- 3계층 구조란?
- Controller의 역할
- Service의 역할
- Repository의 역할
- 계층 간 데이터 흐름
- 실전 전체 예제
- 자주 하는 실수
- 대표 태그
1️⃣ 3계층 구조란?
스프링 프로젝트는 일반적으로 아래 3개의 계층으로 구성됩니다.
Controller ← 사용자의 요청(HTTP) 처리
Service ← 비즈니스 로직 처리
Repository ← DB와 데이터 저장/조회
왜 이렇게 나눌까?
✔ 역할이 명확해짐
✔ 테스트하기 쉬움
✔ 유지보수 간편
✔ 대규모 서비스에서 확장 용이
2️⃣ Controller — HTTP 요청을 받는 문
Controller는 사용자의 요청(브라우저, 앱, 클라이언트) 을 가장 먼저 받아 처리하는 계층입니다.
✔ Controller의 역할
- 요청 URL 매핑 (@GetMapping, @PostMapping)
- 요청 데이터 전달받기
- Service 계층 호출
- 결과를 JSON 또는 View로 응답
✔ 예시 코드: HelloController
@RestController
@RequestMapping("/hello")
public class HelloController {
private final HelloService helloService;
public HelloController(HelloService helloService) {
this.helloService = helloService;
}
@GetMapping
public String hello() {
return helloService.getHelloMessage();
}
}
➡ Controller는 절대 “비즈니스 로직”을 직접 처리하지 않음
→ Service에게 위임해야 함
3️⃣ Service — 비즈니스 로직 처리
Service는 핵심 로직을 처리하는 계층입니다.
예:
- 회원가입 시 비밀번호 암호화
- 주문 시 재고 확인
- 결제 후 포인트 적립
✔ Service 예제
@Service
public class HelloService {
public String getHelloMessage() {
return "Hello Spring Service!";
}
}
✔ Controller는 Service에게 일을 시키기만 하고
✔ Service가 실제 비즈니스 로직을 수행함
4️⃣ Repository — DB 접근 계층
Repository는 데이터 저장소(DB, 파일, 메모리 등)에 접근하는 계층입니다.
스프링부트에서는 보통 Spring Data JPA 사용하지만,
입문 단계에서는 간단한 메모리 저장소로 먼저 이해하면 좋습니다.
✔ Repository 예제
@Repository
public class HelloRepository {
public String findMessage() {
return "Hello from Repository!";
}
}
추후 JPA 학습 시 이렇게 바뀜:
public interface UserRepository extends JpaRepository<User, Long> {}
5️⃣ 계층 간 데이터 흐름
요청 흐름을 한눈에 보자:
[사용자 요청]
↓
@Controller
↓
@Service
↓
@Repository
↓
DB 조회/저장
↓
응답 반환
➡ 이 흐름을 이해하면 스프링의 전체 구조가 보입니다.
6️⃣ 실전 예제 — 메시지 저장/조회 API 만들기
이해를 위해 간단한 “메시지 저장 후 조회 API”를 만들어봅니다.
✔ Repository (메모리 저장소)
@Repository
public class MessageRepository {
private String message = "기본 메시지";
public String getMessage() {
return message;
}
public void saveMessage(String msg) {
this.message = msg;
}
}
✔ Service
@Service
public class MessageService {
private final MessageRepository repo;
public MessageService(MessageRepository repo) {
this.repo = repo;
}
public String read() {
return repo.getMessage();
}
public void write(String msg) {
repo.saveMessage(msg);
}
}
✔ Controller
@RestController
@RequestMapping("/message")
public class MessageController {
private final MessageService service;
public MessageController(MessageService service) {
this.service = service;
}
@GetMapping
public String getMessage() {
return service.read();
}
@PostMapping
public String setMessage(@RequestBody String msg) {
service.write(msg);
return "저장 완료!";
}
}
✔ 실행 흐름
- GET /message → 저장된 메시지 반환
- POST /message → 새로운 메시지 저장
➡ 이 구조가 실무 서비스 개발의 기반입니다.
7️⃣ 자주 하는 실수
❌ Controller에서 비즈니스 로직 처리
→ 반드시 Service에서 처리해야 한다.
❌ Service 없이 Controller → Repository 직접 호출
→ 규모가 커지면 망함.
❌ @Autowired 필드 주입
→ 비추천. 생성자 주입을 사용해야 함.