MVC 아키텍처

정재원·2026년 3월 30일
post-thumbnail

프론트엔드에서 아키텍처를 왜 고를까

개발을 하다 보면 기능만 만드는 것이 아니라, 코드를 어떻게 나눌지도 결정해야 한다. 이때 나오는 것이 아키텍처다.

아키텍처는 어렵게 말하면 설계 방식이고, 쉽게 말하면 "이 코드는 어디에 둘 것인가에 대한 약속" 이다. 구조 없이 만들면 처음에는 빨라 보이지만, 기능이 늘어날수록 코드가 얽히고 수정할 때 다른 곳까지 같이 깨진다.

예를 들어 자취방이 작을 때는 옷, 책, 충전기를 대충 둬도 찾을 수 있다. 그런데 짐이 많아지면 옷장은 옷장, 책상은 책상, 서랍은 서랍대로 나눠야 덜 망가진다. 코드도 똑같다.

프론트엔드 아키텍처 중 하나: MVC

프론트엔드에서 볼 수 있는 대표적인 아키텍처 패턴 중 하나가 MVC다. MVC는 역할을 세 가지로 나눈다.

  • Model
    데이터와 데이터 처리 로직을 담당한다.

  • View
    사용자에게 보이는 화면을 담당한다.

  • Controller
    사용자의 입력을 받아서 Model과 View를 연결한다.

즉, MVC는 데이터, 화면, 입력 처리를 분리하는 구조라고 보면 된다.

MVC를 쉽게 보면

예를 들어 버튼을 누르면 화면의 글자가 바뀌는 기능이 있다고 해보자.

View는 화면에 보이는 요소를 담당한다.

val HomeView {
    val button1
    val text1
}

Model은 데이터를 담당한다.

val HomeModel {
    val data = "빈 문자열"
}

Controller는 사용자의 동작을 처리한다.

val HomeController {
    button1Clicked {
        text1 = "문자열"
    }
}

이렇게 나누면 화면에 보이는 것은 View, 데이터는 Model, 버튼 클릭 같은 동작 처리는 Controller가 맡게 된다.

다만 위 코드는 개념 설명용에 가깝고, 실제 MVC에서는 보통 Controller가 Model을 바꾸고, View는 그 결과를 다시 보여주는 방식으로 이해하는 것이 더 정확하다.

예를 들면 이런 흐름이다.

class HomeModel {
    var text: String = ""
}

class HomeController(private val model: HomeModel) {
    fun onButtonClick() {
        model.text = "문자열"
    }
}

class HomeView(
    private val controller: HomeController,
    private val model: HomeModel
) {
    fun render() {
        println(model.text)
    }
}

핵심은 하나다. 보이는 것, 데이터, 제어를 나눠서 관리한다는 점이다.

MVC의 장점

MVC의 장점은 단순하다.

첫째, 역할이 나뉘어서 코드가 한 군데에 몰리지 않는다.
둘째, 구조가 직관적이라 처음 배울 때 이해하기 쉽다.
셋째, 규모가 작은 프로젝트에서는 빠르게 적용할 수 있다.

즉, MVC는 코드를 분리해서 생각하는 첫 구조로는 꽤 괜찮다.

MVC의 한계

문제는 프로젝트가 커질 때다.

버튼 하나를 눌렀다고 해도 실제 앱에서는 보통 이런 일이 같이 일어난다.

  • 입력값 검사
  • 로딩 처리
  • API 호출
  • 성공/실패 분기
  • 에러 메시지 표시
  • 화면 갱신

이게 전부 Controller 쪽에 몰리기 시작하면 Controller가 너무 커진다. 처음에는 교통정리 역할이었는데, 나중에는 혼자 주문 받고, 요리하고, 계산까지 다 하는 사람처럼 된다.

즉, MVC는 작을 때는 깔끔하지만, 기능이 늘어나면 Controller 비대화 문제가 자주 생긴다.

그래서 안드로이드에서는 MVVM을 많이 쓴다

안드로이드, 특히 Jetpack Compose에서는 MVC보다 MVVM을 더 많이 쓴다.

MVVM은 다음처럼 나뉜다.

  • Model
    데이터와 비즈니스 로직

  • View
    화면 UI

  • ViewModel
    View와 Model 사이의 중간다리, 상태 관리 담당

즉, View는 화면을 그리고, ViewModel은 상태를 관리하고, Model은 실제 데이터를 다룬다.

왜 Compose에서는 MVVM이 더 잘 맞을까

Jetpack Compose는 상태가 바뀌면 화면이 다시 그려지는 방식이다. 그래서 누가 상태를 들고 있을지가 중요하다.

이 구조에서는 MVC보다 MVVM이 더 자연스럽다.

왜냐하면

  • View는 화면만 그리면 되고
  • ViewModel은 상태를 관리하면 되고
  • Model은 데이터만 다루면 되기 때문이다

즉, MVC가 입력 처리 중심이라면 MVVM은 상태 관리 중심에 더 가깝다.

예를 들어 이름 상태를 관리한다면 이런 식이다.

class ProfileViewModel : ViewModel() {
    private val _name = MutableStateFlow("")
    val name: StateFlow<String> = _name

    fun updateName(newName: String) {
        _name.value = newName
    }
}

그리고 화면에서는 그 상태를 받아서 보여준다.

@Composable
fun ProfileScreen(viewModel: ProfileViewModel) {
    val name by viewModel.name.collectAsState()
    Text(text = name)
}

이 구조가 좋은 이유는 분명하다. 화면은 그리기만 하고, 상태는 ViewModel이 관리하고, 데이터는 따로 분리할 수 있기 때문이다.

Repository 패턴은 왜 같이 쓰나

실무에서는 데이터 출처가 바뀔 수 있다.

예를 들어 처음에는 Firebase를 쓰다가 나중에는 REST API로 바꿀 수 있다. 이때 ViewModel이 Firebase 코드까지 직접 알고 있으면 백엔드가 바뀔 때 ViewModel도 같이 뜯어고쳐야 한다.

그래서 Repository를 둔다.

interface UserRepository {
    suspend fun getProfile(uid: String): UserProfile
    suspend fun saveProfile(profile: UserProfile)
}

그러면 ViewModel은 그냥 "프로필 가져와"만 요청하면 된다. 실제로 Firebase에서 가져오든, REST API에서 가져오든 그건 Repository가 책임진다.

이건 배달앱으로 치면 주문 화면은 그대로인데, 뒤에서 연결되는 음식점만 바뀌는 것과 비슷하다.

정리

MVC는 프론트엔드 아키텍처를 처음 이해할 때 좋은 구조다. 화면, 데이터, 제어를 나누는 기본 개념을 잡기에 적합하다.

하지만 현대 프론트엔드, 특히 안드로이드 Compose에서는 단순한 입력 처리보다 상태 관리와 데이터 흐름이 더 중요하다. 그래서 MVC보다 MVVM을 더 많이 쓴다.

정리하면 이렇다.

  • MVC: 역할 분리의 기본 개념을 익히기 좋은 구조
  • MVVM: Compose와 잘 맞고, 상태 관리에 유리한 구조
  • Repository 패턴: 데이터 출처가 바뀌어도 ViewModel을 보호하는 구조

결국 아키텍처를 선택하는 이유는 멋있어 보이기 위해서가 아니다. 기능이 늘어나도 덜 망가지게 만들기 위해서다.

한 줄 결론

프론트엔드 아키텍처는 코드를 나누는 기술이 아니라, 프로젝트가 커져도 유지보수 가능하게 만드는 설계 방식이다.

profile
비전공 프론트개발자입니다.

0개의 댓글