Spring Boot 핵심 개념 - MVC, 3계층 아키텍처, DI
Spring Boot의 MVC 패턴과 3계층 아키텍처가 무엇인지, 왜 이 구조로 나누는지 이해한다.
Spring Boot란?
Spring은 서버 프로그램을 만들 때 쓰는 프레임워크다. 프레임워크란 "자주 필요한 기능을 미리 만들어둔 도구 모음"이다.
Spring Boot는 Spring을 더 쉽게 쓸 수 있게 만든 버전이다. 복잡한 설정 없이 바로 시작할 수 있다.
[Spring Boot 서버]
├── 요청 받기 (HTTP 서버 내장)
├── 요청을 적절한 코드로 연결
├── 데이터베이스 연동
└── 응답 만들어서 돌려주기
위 기능들을 직접 만들 필요 없이, Spring Boot가 다 해준다. 우리는 "어떤 요청이 왔을 때 어떤 데이터를 돌려줄지"만 작성하면 된다.
MVC 패턴
Spring Boot는 MVC(Model-View-Controller) 패턴을 기반으로 한다.
MVC는 코드를 역할에 따라 3가지로 나누는 설계 방식이다.
| 이름 | 역할 | Spring Boot에서 |
|---|---|---|
| Model | 데이터와 비즈니스 로직 | data class, Service, Repository |
| View | 사용자에게 보여주는 화면 | REST API에서는 JSON 응답 |
| Controller | 요청을 받아 Model과 View를 연결 | @RestController |
REST API에서의 MVC
REST API — URL과 HTTP 메서드(GET, POST, DELETE 등)로 요청을 구분하고, 결과를 JSON으로 돌려주는 방식. 자세한 내용은 4편에서 다룬다.
웹 화면을 렌더링하는 전통적인 MVC와 달리, REST API 서버에서는 View 대신 JSON 데이터를 응답으로 돌려준다.
클라이언트(앱/웹)
│
│ GET /users/1
▼
[Controller] ← 요청을 받아서 어떤 Model을 쓸지 결정
│
▼
[Model] ← 데이터 조회/처리
│
▼
[Controller] ← 처리 결과를 받아서
│
▼
JSON 응답 ← View 역할 (화면 대신 데이터)
│
▼
클라이언트(앱/웹)
Spring에서 @RestController는 응답을 HTML이 아닌 JSON으로 자동 변환해준다.
3계층 아키텍처 (3-Layer Architecture)
MVC를 서버 내부 구조에 적용한 것이 3계층 아키텍처다. Model 영역을 Service와 Repository로 더 세분화한 구조다.
┌──────────────────────────────────────────┐
│ Presentation Layer (표현 계층) │
│ Controller │
│ - HTTP 요청/응답 처리 │
│ - 파라미터 유효성 1차 검사 │
├──────────────────────────────────────────┤
│ Business Layer (비즈니스 계층) │
│ Service │
│ - 실제 비즈니스 로직 │
│ - 여러 Repository 조합 │
│ - 트랜잭션 관리 │
├──────────────────────────────────────────┤
│ Data Access Layer (데이터 접근 계층) │
│ Repository │
│ - DB 조회/저장/수정/삭제 │
│ - SQL 또는 JPA 사용 │
└──────────────────────────────────────────┘
│
▼
Database
왜 3개로 나누는가?
하나의 클래스에 모든 코드를 넣으면 어떻게 될까?
// 나쁜 예 - 하나의 클래스에 다 몰아넣기
@RestController
class UserController {
@GetMapping("/users/{id}")
fun getUser(@PathVariable id: Long): User {
// 1. DB 연결
// 2. SQL 실행
// 3. 비즈니스 로직 (이메일 마스킹, 권한 체크 등)
// 4. 응답 변환
// 이 함수가 수백 줄이 된다
}
}
이렇게 쓰면 문제가 많다:
- "DB 관련 코드가 어디 있지?" 찾기 어렵다
- 로직 하나 수정하면 다른 기능에 영향을 줄 수 있다
- 같은 로직이 여러 Controller에 중복된다
3계층으로 나누면:
// Presentation Layer
@RestController
class UserController(private val userService: UserService) {
@GetMapping("/users/{id}")
fun getUser(@PathVariable id: Long) = userService.getUser(id)
// Controller는 짧고 단순하게 유지
}
// Business Layer
@Service
class UserService(private val userRepository: UserRepository) {
fun getUser(id: Long): User {
// 비즈니스 로직만 여기에
return userRepository.findById(id)
?: throw NotFoundException("유저 없음")
}
}
// Data Access Layer
@Repository
class UserRepository {
fun findById(id: Long): User? { /* DB 접근만 여기에 */ }
}
각 계층이 자신의 역할만 담당한다. DB 쿼리를 바꾸려면 Repository만, 비즈니스 로직을 바꾸려면 Service만 수정하면 된다.
계층 간 규칙
계층은 반드시 위에서 아래 방향으로만 호출한다.
Controller → Service → Repository ✅ 올바른 방향
Repository → Service ❌ 하위가 상위를 호출하면 안 됨
Controller → Repository ❌ 계층을 건너뛰면 안 됨
Repository가 Service를 호출하거나, Controller가 Repository를 직접 호출하면 계층 분리의 의미가 없어진다.
어노테이션(@)이란?
코드 위에 붙이는 "메모" 같은 것이다. Spring에게 이 클래스/함수의 역할을 알려준다.
@RestController // "이 클래스는 Presentation Layer의 Controller다"
@RequestMapping("/api") // "이 클래스는 /api 경로를 담당한다"
class UserController {
@GetMapping("/hello") // "GET /api/hello 요청을 이 함수가 처리한다"
fun hello(): String {
return "안녕하세요!"
}
}
3계층 각각에 붙이는 어노테이션:
| 어노테이션 | 계층 | 의미 |
|---|---|---|
@RestController | Presentation | REST API Controller |
@Service | Business | 비즈니스 로직 클래스 |
@Repository | Data Access | DB 접근 클래스 |
프로젝트 구조
3계층 아키텍처를 반영한 실제 프로젝트 구조다.
src/main/kotlin/com/example/myapp/
├── MyAppApplication.kt
│
├── controller/ ← Presentation Layer
│ └── UserController.kt
│
├── service/ ← Business Layer
│ └── UserService.kt
│
├── repository/ ← Data Access Layer
│ └── UserRepository.kt
│
├── entity/ ← DB 테이블 대응 클래스
│ └── User.kt
│
└── dto/ ← 요청/응답 데이터 구조
├── CreateUserRequest.kt
└── UserResponse.kt
entity는 DB 테이블과 1:1로 대응하는 클래스, dto(Data Transfer Object)는 요청이나 응답에 사용하는 클래스다. 이 둘을 분리하는 이유는 DB 구조와 API 응답 구조가 달라질 수 있기 때문이다.
서버 실행
IntelliJ에서 MyAppApplication.kt를 열고 ▶ 버튼을 누르면 서버가 시작된다.
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.x.x)
Started MyAppApplication in 2.3 seconds
이 로그가 나오면 서버가 뜬 것이다. 브라우저에서 http://localhost:8080에 접속해볼 수 있다.
간단한 API 만들어보기
@RestController
class HelloController {
@GetMapping("/hello")
fun hello(): String {
return "Hello, Spring Boot!"
}
@GetMapping("/greet")
fun greet(@RequestParam name: String): String {
return "안녕하세요, $name!"
}
}
http://localhost:8080/hello→Hello, Spring Boot!http://localhost:8080/greet?name=철수→안녕하세요, 철수!
@RequestParam은 URL의 ?name=철수 부분을 함수 파라미터로 받는다.
정리
| 개념 | 설명 |
|---|---|
| MVC | 코드를 Model/View/Controller로 나누는 설계 패턴 |
| 3계층 아키텍처 | Presentation / Business / Data Access로 분리 |
| Controller | Presentation Layer. HTTP 요청/응답 담당 |
| Service | Business Layer. 비즈니스 로직 담당 |
| Repository | Data Access Layer. DB 접근 담당 |
| 어노테이션(@) | 클래스/함수의 역할을 Spring에게 알려주는 표시 |
IoC, DI, 인터페이스 추상화는 다음 편에서 다룬다.
시리즈: Kotlin + Spring Boot 입문
- 개발 환경 설치 - IntelliJ, JDK, 첫 Spring Boot 실행
- Kotlin 기초 - 변수, 함수, 클래스, 인터페이스
- Spring Boot 구조 - MVC, 3계층 아키텍처 ← 현재 글
- IoC, DI, 인터페이스 추상화
- REST API 만들기 - Controller, Service, Repository
- JPA와 데이터베이스 연동