Kotlin + Spring Boot 입문(4/6)
Kotlin/Spring

Spring Boot 핵심 개념 - MVC, 3계층 아키텍처, DI

Spring Boot의 MVC 패턴과 3계층 아키텍처가 무엇인지, 왜 이 구조로 나누는지 이해한다.

2026-03-18
9 min read
#Spring Boot#Kotlin#MVC#3계층#아키텍처#DI

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계층 각각에 붙이는 어노테이션:

어노테이션계층의미
@RestControllerPresentationREST API Controller
@ServiceBusiness비즈니스 로직 클래스
@RepositoryData AccessDB 접근 클래스

프로젝트 구조

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/helloHello, Spring Boot!
  • http://localhost:8080/greet?name=철수안녕하세요, 철수!

@RequestParam은 URL의 ?name=철수 부분을 함수 파라미터로 받는다.


정리

개념설명
MVC코드를 Model/View/Controller로 나누는 설계 패턴
3계층 아키텍처Presentation / Business / Data Access로 분리
ControllerPresentation Layer. HTTP 요청/응답 담당
ServiceBusiness Layer. 비즈니스 로직 담당
RepositoryData Access Layer. DB 접근 담당
어노테이션(@)클래스/함수의 역할을 Spring에게 알려주는 표시

IoC, DI, 인터페이스 추상화는 다음 편에서 다룬다.


시리즈: Kotlin + Spring Boot 입문

  1. 개발 환경 설치 - IntelliJ, JDK, 첫 Spring Boot 실행
  2. Kotlin 기초 - 변수, 함수, 클래스, 인터페이스
  3. Spring Boot 구조 - MVC, 3계층 아키텍처 ← 현재 글
  4. IoC, DI, 인터페이스 추상화
  5. REST API 만들기 - Controller, Service, Repository
  6. JPA와 데이터베이스 연동