한 번의 글로 이해하는 소프트웨어 아키텍처 패턴 ( MVC, MVP, MVVM )

2023. 4. 18. 00:57기본에 충실하자

안드로이드 개발자가 되기 위해 채용 공고를 보면 자주 보이는 게 하나 있습니다.

 

mvvm 패턴 기반의 앱?? mvvm 패턴?? 여기서 말하는 mvvm 패턴은 무엇일까요?? 구글에 검색해 보겠습니다.

 

구글에 검색해 보니 아주 많은 글들이 존재합니다. 그중에 하나를 살펴보니 mvvm은 아래와 같다고 합니다.

https://builtin.com/software-engineering-perspectives/mvvm-architecture

위의 한 단락의 글을 정리해 보면,

MVVM은 아키텍처 패턴이고 궁극적인 목표는 뷰를 애플리케이션의 비즈니스 로직과 완전히 독립적으로 만드는 것이라고 합니다.

 

여기서 또 의문이 듭니다. 아키텍처 패턴이 무엇이고 비즈니스 로직은 무엇일까?

mvvm 패턴에 대해 알기 위해선 선행적으로 위의 두 가지를 알아야 할 것 같습니다. 

 

Q. 아키텍처 패턴( Architecture Patterns )이란 무엇인가요??? 

아키텍처 패턴은 주어진 Context에서 소프트웨어 아키텍처에 자주 발생하는 문제에 대한 대표적이고 재사용할 수 있는 솔루션이라 합니다. 쉽게 말해 패턴처럼 반복적으로 나오는 문제에 대한 풀이집이라고 보시면 될 것 같습니다.

 

Q. 비즈니스 로직은 무엇일까요???

 

비즈니스 로직은 소프트웨어 시스템이 수행해야 하는 비즈니스 요구사항을 정의하는 부분을 말합니다.
소프트웨어의 핵심 기능을 결정하며, 시스템이 수행해야 할 특정 작업을 기술합니다.

예를 들어, 은행 시스템의 비즈니스 로직은 계좌 개설, 입출금, 이체, 대출 등과 같은 주요 기능을 정의할 수 있습니다. 다른 예로 온라인 쇼핑몰에서 상품을 주문하는 경우를 생각해 보겠습니다.

사용자가 상품을 선택하고 결제를 진행하면, 애플리케이션은 다음과 같은 일련의 과정을 거칩니다.

  1. 상품 정보 확인
    1. 사용자가 선택한 상품의 재고 수량을 확인합니다.
    2. 재고 수량이 충분하지 않으면, 주문을 거부합니다.
  2. 결제 정보 처리
    1. 사용자가 입력한 결제 정보를 검증합니다.
    2. 결제가 성공하면, 결제 정보를 데이터베이스에 저장합니다.
  3. 배송 정보 처리
    1. 결제가 성공한 경우, 배송 정보를 입력받습니다.
    2. 배송 정보를 데이터베이스에 저장합니다.
  4. 주문 처리
    1. 주문 정보를 데이터베이스에 저장합니다.
    2. 상품 재고 수량을 감소시킵니다.

1번 단계는 상품의 재고 수량을 확인하는 비즈니스 로직입니다.

2번 단계는 결제 정보를 처리하는 비즈니스 로직입니다.

3번 단계는 배송 정보를 처리하는 비즈니스 로직입니다.

4번 단계는 주문 정보를 처리하는 비즈니스 로직입니다.

 

이처럼 비즈니스 로직은 애플리케이션은 핵심이며 성능과 안정성에 큰 영향을 미칩니다. 

 

 

위에서 아키텍처 패턴과 비즈니스 로직을 알아보았습니다. 

한 가지 또 알아야 하는 게 나왔습니다. 바로 소프트웨어 아키텍처입니다.

 

MVVM을 통해 아키텍처 패턴, 비즈니스 로직을 거쳐 이제 소프트웨어 아키텍처에 도달했습니다.

 

Q. 소프트웨어 아키텍처란 무엇인가요???

 

소프트웨어 아키텍처란 시스템을 구성하는 소프트웨어 요소와 그 요소들의 외부적으로 보이는 속성, 그리고 그들 간의 관계로 구성된 시스템의 구조 의미합니다. 일반적으로 시스템의 고수준 개념 및 기능을 정의하고, 시스템의 각 부분이 어떻게 작동하고 상호 작용하는지에 대한 세부적인 설계를 수행합니다. 이를 통해 소프트웨어 개발자와 관련 이해관계자들은 시스템의 전반적인 구조를 이해하고, 시스템의 복잡도를 관리하며, 기능 변경이나 확장에 대한 영향을 예측하고 관리할 수 있습니다.

 

말 그대로 소프트웨어 아키텍처는 소프트웨어의 구조 설계입니다.

 

 

지금까지 MVVM -> 소프트웨어 아키텍처 패턴, 비즈니스 로직 -> 소프트웨어 아키텍처의 순서로 들어오며 파악하였습니다.

 

이제 다시 반대로 소프트웨어 아키텍처 -> 아키텍처 패턴, 비즈니스 로직 -> MVVM 순서로  MVVM에 대해 정리하면 다음과 같아집니다.

 

MVVM은 시스템의 구조를 설계하는 소프트웨어 아키텍처에서 시스템이 수행해야 하는 비즈니스 요구사항을 정의하는 부분을 뷰와 분리시켜 독립적으로 사용하는 문제에 대한 대표적이고 재사용할 수 있는 솔루션 중에 하나다라고 정의할 수 있게 됩니다.

 

더 간단하게 정리하면 설계 시에 비즈니스 로직과 뷰를 분리하기 위한 문제를 해결하는 여러 방법들 중에 한 가지입니다.

 

Q. 왜 이렇게 분리하고 패턴을 이용해 설계를 할까요???

설계가 안 좋은 프로그램에 새로운 기능을 추가, 삭제한다고 하면 당연히 시간적 비용, 경제적 비용 모두 많아질 확률이 높습니다. 제품의 설계가 잘 이루어지지 않는다면 결국 인적, 물적 비용 문제를 발생시키고 초기 설계 시 많은 투자로 견고한 아키텍처를 설계하면 후에 비용이 절감되기 때문에 효율과 경제성을 모두 잡을 수 있습니다.

 

Q. 비즈니스 로직과 뷰를 분리하기 위해 사용하는 대표적인 아키텍처 패턴들은 뭐가 있을까??

1. mvc
2. mvp
3. mvvm

 

여기서 mvc , mvp, mvvm, mvi 등이 나오게 됩니다. 이제 각각의 패턴들이 어떻게 비즈니스 로직과 뷰를 분리하는지 알아보고 작동 과정을 이해해 보겠습니다. 

 

1. MVC (  Model - View - Controller ) 

MVC 패턴을 찾아보면 1979년에 Trygve Mikkjel Heyerdahl Reenskaug라는 컴퓨터 과학자가 처음 소개했다고 합니다. 그럼 그는 왜 이걸 만들었을까요?? 그분은 복잡한 응용프로그램을 관리하기 좋게 더 작은 구성 요소로 나누는 방법을 고민하다가 MVC 패턴을 만들었다고 합니다. MVC 패턴은 Small Talk라는 언어에서 처음 사용되었고, 요즘 날에 유명한 루비, ASP.NET , Angular, Laravel에서 많이 사용하는 패턴이라고 합니다.

 

mvc 패턴의 목적은 비즈니스 로직과 뷰를 분리하는 것입니다.

 

Q. mvc 패턴은 어떻게 비즈니스 로직과 뷰를 분리할까요??

 

아래에 그림이 하나 있습니다. 그림을 보시면 여러 개의 VIEW가 있고 가운데 CONTROLLER가 있습니다. 마지막으로 맨 아래에 MODEL이 있습니다.

그림만 봐도 대략적인 역할이 무엇인지 감이 오실 겁니다.

 

각각의 역할은 다음과 같습니다.

 

View : 사용자 인터페이스를 담당합니다. 모델의 데이터를 보여주고, 사용자 입력을 받아 Model에 전달합니다.

 

Controller : 모델과 뷰를 연결하고, 애플리케이션의 흐름을 제어합니다. 뷰에서 입력을 받아 모델에 전달하거나, 모델에서 변경된 데이터를 가져와 뷰에 반영합니다.

 

Model : 애플리케이션의 데이터와 비즈니스 로직을 담당합니다. 데이터를 가져오고 변경하는 메서드를 가지고 있으며, 데이터 변경 시에는 이를 관리하는 컴포넌트로 알립니다.

 

인터넷에 mvc 패턴에 대한 그림이 아주 많이 있지만 조금씩 달라서 헷갈렸습니다. 뭐가 제일 답에 가까울까 생각해 보니 결국 목적에 맞게 그리자 하였습니다. 어떤 글에서는 이런 그림도 있습니다. 아래 그림도 맞을 수 있지만 저는 헷갈리더군요. model과 view를 분리하기 위한 패턴인데 왜 model과 view가 연결되어 있고, cotroller에서 view를 update 하지 않지??라는 생각이 들었습니다. 저렇게 그린 이유가 무엇일까 생각해 보았습니다.

https://www.geeksforgeeks.org/mvc-design-pattern/

 

이제 코드를 하나 보시겠습니다. 그럼 왜 위와 같이 그렸을까 조금은 이해가 되실 거라 생각합니다.

 

이 코드에서는

UserModel 클래스가 데이터를 캡슐화하고,

UserView 클래스가 사용자 인터페이스를 구현합니다.

UserController 클래스는 이 둘을 중재하는 컨트롤러 역할을 합니다.

 

위의 코드에는 문제점이 있습니다. 바로 컨트롤러가 뷰와 모델을 모두 알고 있어야 한다는 것입니다. UserController가 뷰와 모델을 직접 참조하고 있으므로, 뷰나 모델이 변경될 경우 UserController의 코드도 함께 변경되어야 합니다. 또 다른 문제는 UserView가 직접 UserModel을 참조하고 있다는 것입니다. 예시 코드에서 UserController 클래스에  UserModel과 UserView 클래스의 인스턴스가 생성되고, displayUserInfo() 함수의 userView 인스턴스에 UserView 클래스 내부에서 UserModel의 내부 데이터인 userModel.name, userModel.email을 직접적으로 참조하는 부분이 있습니다. 이 부분에서 UserView 클래스와 UserModel 클래스가 강하게 결합되어 있어서 만약 UserModel의 속성 이름이 변경된 경우 UserView도 함께 변경되어야 합니다. 이러한 문제점들은 MVC 패턴은 대규모 애플리케이션에서는 복잡성을 증가시키고 유지보수를 어렵게 만드는 경우가 많습니다. 

 

위의 그림도 위와 같은 이유에서 저렇게 그린게 아닐까 추측해 봅니다. 뷰와 모델의 참조로 인해 강한 결합도를 야기하고 한 클래스의 변경이 다른 클래스에도 영향을 미칠 수 있다는 걸 의미하여 그림에는 Model의 update가 view에 영향을 주게 됩니다.  

 

또한 왜 대규모에서 문제가 발생하는지 그림을 통해 보겠습니다. 

위의 그림에 화살표 몇 개만 추가하였습니다.

각각의 VIEW에서 필요한 MODEL의 연결이 화살표 색깔별로 나눠져 있습니다. 뷰가 확장되고 모델이 많아질수록 각 VIEW가 필요로 하는 MODEL이 서로 복잡하게 얽히게 됩니다. 이렇게 될 경우 당연히 복잡도가 늘어나고 결합도 또한 계속 증가할 것입니다. 

 

Q. 그럼 Controller를 여러 개 두면 해결되지 않을까??

컨트롤러를 여러 개 두면 일부 문제점을 해결할 수 있지만 또 다른 문제점이 발생할 수 있습니다. 예를 들어 하나의 모델이 여러 컨트롤러에서 사용되면, 각 컨트롤러에서 모델의 상태를 일관되게 유지하기 어렵고 복잡해질 수 있습니다. 또한 컨트롤러 간의 데이터 공유가 어려워지며, 모든 컨트롤러가 불필요한 중복 코드를 가질 가능성이 있습니다. 이는 유지보수성을 저하시키고 코드의 재사용성을 낮출 수 있습니다.

 

따라서, 여러 개의 컨트롤러를 사용하는 경우, 각 컨트롤러가 독립적이고 책임이 명확하게 구분되도록 설계하는 것이 중요합니다. 또한 모델의 상태를 일관되게 유지하기 위해 컨트롤러 간의 공유 및 협력 방식을 고려하며, 중복 코드를 최소화하도록 해야 합니다. 또한 나누더라고 Controller에서 VIew와 Model의 강한 결합도는 똑같기 때문에 단순히 Controller의 개수가 늘어남으로 문제가 해결되진 않다고 생각합니다.

 

그래서 이러한 MVC 패턴의 문제점을 해결하기 위해 MVP와 MVVM 패턴과 같은 다른 아키텍처 패턴이 등장하게 되었습니다. 아래에서 다른 아키텍처 패턴에 대해 알아보겠습니다.

 

2. MVP 

MVP 패턴은 MVC 패턴에서 발생할 수 있는 문제를 해결하고 개선하기 위해 만들어졌습니다. 먼저 MVP의 구조는 다음과 같습니다.

 

MODEL : 애플리케이션의 비즈니스 로직과 데이터를 담당하는 부분

VIEW : 사용자 인터페이스를 담당하는 부분. 모델에 직접 접근하지 않으며, 프레젠터를 통해 모델과 상호작용합니다.

Presenter : 뷰와 모델 사이의 중간자 역할을 합니다. 뷰로부터 입력을 받아 모델에 전달하고, 모델로부터 데이터를 받아 뷰에 전달합니다.

 

그림으로 표현하면 아래와 같습니다.

1. 사용자는 View를 통해 애플리케이션에 대한 용청을 보냅니다.

2. 뷰는 요청을 Presenter에 전달합니다.

3. Presenter는 요청을 처리하기 위해 Model에 액세스 합니다.

4. Model은 데이터를 검색하고 Presenter에 반환합니다.

5. Presenter는 받은 데이터를 가공하고 View에 반환합니다.

6. View는 반환된 데이터를 표시합니다.

 

6개의 과정을 통해 MVP 패턴은 비즈니스 로직과 VIew를 분리합니다. 

예를 하나 더 들어 간단한 To-Do 리스트 앱을 만들면 다음과 같습니다.

 

1. 사용자가 새로운 할 일을 추가하려면, 뷰는 사용자에게 입력을 받고 받은 입력을 Presenter에게 전달합니다.

2. Presenter는 요청을 처리하기 위해 모델에 새로운 할 일을 추가하도록 요청합니다.

3. Model은 할 일을 DB에 저장합니다.

4. Model은 Presenter에 저장된 할 일을 반환합니다.

5. Presenter는 모델로부터 받은 할 일을 가공하여 View에 반환합니다.

6. View는 반환된 할 일을 목록에 추가하여 표시합니다.

 

MVC에서 MVP로 Controller가 Presenter로 바뀌면서 어떤 차이가 발생할까요??

앞서 MVC에서의 문제는 Controller에서 View와 Model의 강한 결합이 문제였습니다. 비즈니스 로직과 View를 분리하기 위한 문제를 해결하기에는 부족했던 MVC 패턴을 MVP에서는 어떻게 개선시킬 수 있을까요?

 

아래에 간단한 코드를 통해 설명하겠습니다. 아주 간단한 코드를 보면 아래와 같습니다.

 

 

간단하게 어떤 과정을 통해 비즈니스 로직과 뷰를 분리하는지 보겠습니다.

먼저 위의 코드에서 model과 view를 interface로 구현하였습니다. Presenter는 View와 Model 사이에서 중개자 역할을 합니다. fetchData() 함수는 Model에서 데이터를 가져와 View에 전달합니다. 이를 통해 View와 model 간의 직접적인 의존성을 방지할 수 있습니다. interface를 통해 구현하여 상속받고 구현체를 따로 만들어주어야 하지만 인터페이스의 존재가 View와 Model이 서로를 직접적으로 참조하지 않아 결합을 낮추게 됩니다. 위 코드에서 예외처리나 의존성 주입은 수행하지 않아 개선 사항이 필요하지만 기본적이 과정만을 보기 위해 작성했습니다. Presenter를 interface로 구현하여 따로 상속받아도 결국 목적은 비즈니스 로직과 뷰의 분리이기 때문에 문제가 없습니다.

 

그렇다면 MVP 패턴의 단점은 무엇이 있을까요???

 

1. 인터페이스의 중복 코드

MVP 패턴에서는 View와 Presenter 간의 통신을 위해 인터페이스를 사용합니다. 이 인터페이스는 뷰에서 발생하는 이벤트를 Presenter로 전달하고, Presenter는 처리 후 결과를 다시 뷰로 전달합니다. 그러나 이러한 구현 과정에서 중복 코드가 많이 발생할 수 있습니다. 예를 들어, View와 Presenter 간의 통신을 위해 여러 개의 인터페이스를 구현해야 하는 경우 각각의 인터페이스에서 비슷한 코드를 반복해 작성해야 하므로 코드의 가독성과 유지 보수성이 떨어질 수 있습니다.

2. Presenter의 증가

특수한 경우가 아니면 View와 Presenter는  1:1의 관계를 가집니다. 만약, 뷰가 많아진다면 그만큼 Presenter가 많아지기 때문에 코드가 증가하고 그에 맞는 각각의 인터페이스가 생성되면서 복잡도가 증가할 수 있습니다. 

 

 

3. MVVM  ( Model - View - ViewModel )

MVC, MVP 패턴을 확인했습니다. 이제 MVVM을 알아보겠습니다.

MVVM은 비즈니스 로직과 View를 어떻게 분리할까요??

 

먼저, MVVM은 Model, View, ViewModel 3가지로 구성됩니다. 각각의 역할은 다음과 같습니다.

 

1. Model

애플리케이션의 데이터와 비즈니스 로직을 담당하는 부분입니다.

DB, Network, File 등의 데이터 소스와 상호 작용하여 데이터를 가져오고 저장합니다.

ViewModel과 View는 Model에 직접적으로 접근할 수 없으며, Model은 View나 ViewModel의 존재를 알지 못합니다.

 

2. ViewModel

Model과 View 사이에서 중개자 역할을 수행하는 부분입니다.

View에 필요한 데이터를 Model로부터 가져와서 가공하거나 변환하여 View에 전달합니다.

View에서 발생하는 이벤트를 처리하고, Model로부터 데이터가 변경될 때마다 View에 이를 알려 View의 상태를 업데이트합니다.

View와의 상호작용과 생명주기를 관리합니다.

 

3. View

사용자 인터페이스를 담당합니다.

ViewModel로부터 전달받은 데이터를 사용하여 UI를 구성합니다.

이벤트가 발생하면 ViewModel에 전달합니다. 

View는 ViewModel에 대한 참조를 가지고 있지만, ViewModel은 View에 대한 참조를 가지고 있지 않습니다.

 

그림을 보시면 위의 MVP와 비슷하면서 조금 차이가 있습니다.

MVVM 패턴의 중요한 점은 바로 View와 ViewModel 간의 연결입니다.

 

Q. View와 ViewModel은 어떻게 연결되어 있을까요??

바로 데이터 바인딩을 통해 연결되어 있습니다. 

 

Q. Data Binding은 그럼 무엇일까요??

데이터 바인딩이란 UI와 데이터 사이의 동기화를 담당하여 ViewModel에서 Model이 변경될 때 이를 감지하여 UI를 자동으로 업데이트합니다. 

 

Q. 어떻게 자동으로 감지하고 업데이트를 할까요??

일반적으로 데이터 바인딩을 가능하게 하기 위해 Observer 패턴을 사용합니다.

스타 옵저버

Observer 패턴이란 관찰 대상 객체와 이를 관찰하는 객체가 존재하는데, 대상 객체 자신의 상태가 변경될 때마다 등록된 모든 Observer 객체에게 알리는 역할을 하며, Observer 객체는 등록된 관찰 대상 객체의 상태 변화를 감지하고 이에 대한 처리를 수행합니다. 이를 통해 UI가 자동으로 업데이트됩니다. 신기하죠?? 

 

안드로이드에서는 XML layout과 데이터 소스를 바인딩하는 데 사용하는 데이터 바인딩 라이브러리가 있습니다. 이것과 mvvm의 데이터 바인딩의 의미가 같으면서 다릅니다. mvvm 패턴에서의 데이터 바인딩은 뷰와 뷰모델을 연결하고, 뷰와 뷰모델 간의 데이터 흐름을 처리하기 위한 것을 얘기하고 그 역할을 할 수 있는 안드로이드 라이브러리가 AAC의 data binding입니다. 결국 같으면서 다르다!

 

Q. 어떻게 data binding을 통해 view가 viewmodel의 데이터를 계속 관찰하고 있을까요??

1. Observable 객체 : 데이터가 변경될 때마다 이를 감지하여 UI를 업데이트 

2. LiveData : Lifecycle-aware 한 데이터 홀더로, 데이터가 변경될 때마다 Observer에게 알려 UI를 업데이트

3. RxJava : Observable 및 Observer를 사용하여 데이터 스트림을 처리하고 UI를 업데이트

4. Kotlin Flow : 코루틴 기반의 비동기적인 데이터 스트림 처리를 지원하며, UI 업데이트에 사용

 

각각의 사용법과 역할을 여기서 다루지 않겠습니다.

 

저렇게 대표적으로 4개의 여러 방법들이 하는 것은 데이터를 관찰하여 변화를 감지한다가 포인트입니다. 여기서 mvvm의 view와 viewmodel의 느슨한 결합으로 의존성이 감소하고 유연함과 확장 가능하게 해 줍니다. 

 

Q. MVP와의 차이점은 무엇일까요???

바로 포인트인 view와 viewmodel의 느슨한 결합입니다. MVP의 View와 Presenter 간의 의존성은 높습니다. 그러나 Data binding을 통해 MVVM 패턴은 서로의 의존성을 낮출 수 있게 됩니다. 

 

아래의 코드는 아주 간단한 MVP 코드의 예시입니다. 

Presenter가 View를 받아서 view의 인터페이스 메서드를 직접 호출합니다. 

분리하고 의존성 주입 등의 기법을 사용할 수 있지만 기본적으로는 이렇습니다.

이렇게 되면 View와 Presenter 간의 결합도가 높아지게 됩니다. 

이러한 문제를 MVVM에서는 Data binding을 통해 View와 ViewModel의 결합도를 낮추는 방식으로 개선합니다.

 

간단한 코드를 예로 보겠습니다. viewmodel에서는 repository에서 가져온 데이터를 livedata에 저장합니다. 

다른 건 일단 접어두고 observe 해서 불러온다는 부분만 생각해 봅시다.

이렇게 하면 이제 viewmodel에서 데이터가 변경되면 Livedata를 통해 UI를 자동으로 업데이트할 수 있습니다.

UI를 갱신하는 영역에 View에 관한 명령만 넣어주면 View의 처리는 될 것입니다.

 

View는 Model에서 데이터가 어떻게 처리되는지 알 수도 없고 알 필요도 없습니다. ViewModel에서 변하는 data를 보고 있다가 데이터가 변하면 UI를 업데이트하기만 하면 됩니다. 또한 ViewModel에서는 View에 대한 처리를 하지 않습니다. 데이터를 가공하고 데이터를 불러오면 어떻게 처리할지만 구현하면 됩니다. 데이터만 알맞게 바꿔주면 View가 알아서 데이터만 가져다가 업데이트하게 됩니다. 

 

 

이렇게 3개의 아키텍처 패턴을 공부하였습니다. 틀린 부분이 있다면 제 공부가 부족하므로 가르쳐 주시면 경청하겠습니다. 나름 많은 데이터를 보고 각각의 역할과 구성을 토대로 이해하기 쉽게 작성하였습니다. 도움이 되길 바라면서 이만 글을 마치겠습니다. 감사합니다.

'기본에 충실하자' 카테고리의 다른 글

객체지향 5대원칙 SOLID  (0) 2023.01.07
의존성 주입 Dependency Injection  (0) 2022.08.20