왜 나누나 3계층 요청 흐름 계층 간 의존 관심사 분리 📝 문제풀이
◀ 이전 📋 목차 CH 07 ▶
🏛️ CHAPTER 06 · 중급

코드를 층층이 나누는 이유

한 클래스가 웹 처리 + 로직 + DB를 전부 다 하면 금방 뒤엉켜요. 스프링 백엔드는 역할별로 계층을 나눠 관심사를 분리합니다. 이번 장에서 그 3계층 구조를 코드로 잡아봐요.

🎯 이 장을 끝내면
😵
왜 나누나
한 클래스가 다 하면 왜 힘들까
문제를 알면 "계층"이라는 해법이 자연스럽게 이해돼요.
🔤
계층구조(Layered Architecture) — 코드를 역할별 "층(layer)"으로 나눠 쌓는 설계예요. 위층은 바로 아래층에만 일을 맡기고, 각 층은 자기 역할에만 집중해요.

하나의 클래스가 웹 요청 처리 + 비즈니스 로직 + 데이터베이스 접근을 한꺼번에 담당한다고 해봐요. 처음엔 편해 보이지만, 기능이 늘수록 코드가 복잡해지고, 중복이 생기고, 테스트하기 어려워져요. 웹 코드와 DB 코드가 뒤섞여 있어서, 로직 하나만 바꾸려 해도 전부 얽혀 있거든요.

🔤
비즈니스 로직(business logic) — 앱이 진짜로 처리하려는 업무 규칙과 계산이에요. 예를 들어 주문 금액을 계산하거나 회원 자격을 검증하는 것처럼, "그 서비스가 실제로 해야 하는 일"을 말해요.
🍝 모든 걸 한 곳에서 — "스파게티" 코드 웹 파라미터를 받는 코드, 계산·검증 로직, SQL 쿼리가 한 메서드 안에 뒤엉켜 있으면, 무엇이 무엇을 하는지 알기 어렵고 재사용·테스트가 거의 불가능해요. 해법은 간단해요. 역할별로 계층을 나눠 관심사를 분리하는 거예요. 각 계층은 자기 일만 하고, 옆 계층에 나머지를 맡깁니다.
🔤
관심사 분리(Separation of Concerns) — 성격이 다른 일(웹 처리·로직·DB)을 한데 섞지 않고 각각 다른 계층에 맡기는 설계 원칙이에요. 각 계층이 자기 "관심사(역할)"에만 집중하게 만들어요.
Controller Service Repository 3계층 관심사 분리 DI MVC
🔤
MVC (Model-View-Controller) — 화면(View)·데이터(Model)·요청 처리(Controller)로 역할을 나누는 대표적인 설계 패턴이에요. 이번 장에서 배울 Controller·Service·Repository 3계층도 "역할을 나눠 관심사를 분리한다"는 같은 정신에서 나온 구조랍니다.
🧭
이번 장의 큰 그림. "요청이 들어오면 어느 층이 무엇을 하는가"만 잡으면 돼요. 세 계층의 역할 구분흐름 방향, 이 두 가지가 핵심입니다.
🖼️ 그림으로 보기 — 요청이 흐르는 3계층
요청 흐름 → 응답은 역순 (←) 🌐 클라이언트 브라우저·앱 Controller @Controller 요청 받기·응답 (입력 검증) Service @Service 비즈니스 로직 트랜잭션 Repository @Repository DB 접근(CRUD) 🗄️ DB 데이터베이스
요청은 Controller → Service → Repository → DB 순으로 내려가고, 결과(응답)는 그 역순으로 올라와요. 각 층은 자기 일만 하고 옆 층에 나머지를 맡겨요(관심사 분리). Controller가 DB를 직접 건드리지 않는 게 핵심이에요.
🏛️
3계층
Controller · Service · Repository
스프링 백엔드의 대표적인 3계층 구조예요.

스프링 애플리케이션은 보통 세 개의 계층으로 역할을 나눠요. 각 계층은 딱 자기 책임만 갖고, 필요한 나머지는 아래 계층에 위임합니다.

🍽️
식당으로 비유하면. Controller는 손님의 주문을 받고 음식을 내어주는 홀 직원, Service는 주문대로 요리하는 주방장, Repository재료 창고를 관리하는 사람이에요. 홀 직원이 직접 창고에 들어가지 않고 주방장에게 맡기듯, Controller도 DB(창고)를 직접 건드리지 않고 반드시 Service를 거쳐요.
🔤
CRUD — 데이터를 다루는 네 가지 기본 동작인 생성(Create)·조회(Read)·수정(Update)·삭제(Delete)의 앞글자를 딴 말이에요. Repository가 담당하는 "저장·조회·수정·삭제"가 바로 이 CRUD랍니다.
계층어노테이션역할
Controller
(컨트롤러)
@Controller웹 진입점. 클라이언트의 요청을 받고, 결과를 응답으로 돌려줘요. 로직은 직접 하지 않고 Service에 맡겨요.
Service
(서비스)
@Service비즈니스 로직·규칙을 처리해요. 계산·검증·조합 등 "진짜 일". 트랜잭션 경계가 보통 여기예요.
Repository
(리포지토리)
@RepositoryDB 접근 담당. 데이터를 저장·조회·수정·삭제해요. 데이터베이스와 대화하는 유일한 층.
🧱 그림으로 보기 — 위에서 아래로 쌓인 3계층
🚪
Controller @Controller
요청을 받고 응답을 돌려주는 웹 진입점
아래층에 위임
🧠
Service @Service
비즈니스 로직·규칙 처리, 트랜잭션 경계
아래층에 위임
🗄️
Repository @Repository
DB 접근 — 저장·조회·수정·삭제
💾
Database
실제 데이터가 저장되는 곳
위에서 아래로 한 방향으로만 일을 맡겨요. 위층은 바로 아래층만 사용하고, 아래층은 위층을 알지 못해요(단방향 의존).
🔤
트랜잭션(transaction) — 여러 DB 작업을 "모두 성공 아니면 모두 취소"로 묶는 하나의 단위예요. 중간에 하나라도 실패하면 앞선 작업까지 되돌려(rollback) 데이터가 어긋나지 않게 해요. 보통 Service 계층이 이 경계를 담당해요.
💡
한 줄 요약. Controller는 "받고 돌려주는 문지기", Service는 "일하는 두뇌", Repository는 "DB 창고지기"예요. 역할이 겹치지 않게 나누는 게 핵심이에요.
➡️
요청 흐름
요청은 어떻게 층을 타고 흐를까
들어갈 땐 위 → 아래, 나올 땐 아래 → 위(역순)예요.

클라이언트가 요청을 보내면, Controller → Service → Repository → DB 순서로 내려가요. 그리고 결과(응답)는 그 역순으로 다시 올라와 클라이언트에게 돌아갑니다.

🌐
클라이언트
요청 보냄
🚪
Controller
요청 받기
🧠
Service
로직 처리
🗄️
Repository
DB 접근
🔁 응답은 역순으로 Repository가 DB에서 데이터를 가져오면 → Service가 그것으로 로직을 마무리하고 → Controller가 결과를 받아 응답으로 클라이언트에게 돌려줘요. Controller → Service → Repository → DB로 내려갔다가, 그 결과가 역방향으로 올라와 클라이언트에게 도착해요. 방향과 순서를 꼭 기억하세요.
📌
Controller가 Repository를 직접 호출하지 않는 게 원칙이에요. 반드시 Service를 거쳐 내려가요. 그래야 비즈니스 규칙·트랜잭션이 Service 한 곳에 모여요.
💉
계층 간 의존
계층끼리는 DI로 연결돼요
Controller는 Service를, Service는 Repository를 주입받아 써요.

한 계층이 아래 계층을 쓰려면, 그 객체를 직접 new 하지 않고 스프링에게서 주입받아(DI) 사용해요. Controller는 Service를, Service는 Repository를 생성자로 주입받습니다. (CH 02의 DI가 여기서 그대로 쓰여요.)

🗄️ Repository — DB 접근 계층 데이터를 저장·조회하는 층이에요. @Repository로 스프링 빈이 돼요.
@Repository
public class UserRepository {
    public User findById(Long id) {
        // DB에서 사용자 조회 (여기선 예시)
        return new User(id, "태호");
    }
}
🧠 Service — 비즈니스 로직 계층 Repository를 생성자로 주입받아 로직을 처리해요. @Service로 빈이 됩니다.
@Service
public class UserService {
    private final UserRepository userRepository;

    // Repository를 주입받음 (DI)
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User getUser(Long id) {
        // 비즈니스 로직 처리 후 Repository 사용
        return userRepository.findById(id);
    }
}
🚪 Controller — 웹 진입점 계층 요청을 받아 Service를 주입받아 호출하고, 결과를 응답으로 돌려줘요. @Controller로 빈이 됩니다.
@Controller
public class UserController {
    private final UserService userService;

    // Service를 주입받음 (DI)
    public UserController(UserService userService) {
        this.userService = userService;
    }

    public User user(Long id) {
        // 요청을 받아 Service에 위임하고 응답
        return userService.getUser(id);
    }
}
💉
세 코드의 공통점: 필요한 아래 계층을 final 필드로 두고, 생성자로 주입받아요. new UserRepository() 같은 코드가 보이지 않죠? 그게 바로 스프링이 대신 연결해주는 DI예요.
관심사 분리
나누면 무엇이 좋아질까
관심사 분리(Separation of Concerns)의 실제 이점이에요.
이점왜 좋아지나
유지보수고칠 곳이 명확해요. 로직은 Service, DB는 Repository만 보면 되니 수정 범위가 좁아져요.
테스트계층이 분리돼 있어 각 계층을 따로 테스트할 수 있어요. Service만 떼어내 로직만 검증 가능.
재사용같은 Service를 여러 Controller가 쓰거나, 같은 Repository를 여러 Service가 공유할 수 있어요.
🔧
유지보수 쉬움
고칠 곳이 명확해요. 로직은 Service, DB는 Repository만 보면 돼요.
🧪
테스트 쉬움
계층이 나뉘어 있어 Service만 떼어내 로직만 따로 검증할 수 있어요.
♻️
재사용 쉬움
같은 Service·Repository를 여러 상위 계층이 함께 나눠 쓸 수 있어요.
🏛️
이렇게 역할을 층으로 나누는 것이 바로 관심사 분리예요. 각 계층은 자기 일에만 집중하고, 나머지는 옆 계층에 맡기죠. 코드가 커져도 깔끔하게 유지되는 비결이에요.
🧠 이 장 핵심 요약
📝
문제풀이 · 점검
배운 걸 가볍게 점검해봐요
시험이 아니라 "내가 이해했나" 확인용이에요. 틀려도 바로 해설이 나와요.
🧪
문제를 풀면 즉시 정답과 해설이 나오고, 위쪽 바에 점수가 쌓여요. 네 가지 유형(객관식 · O/X · 빈칸 · 개념)을 섞어 두었어요.
CH 07 🌐 REST 컨트롤러 — 요청을 받아 JSON으로 응답하기