새소식

Software Engineering

MVVM과 Clean Architecture

  • -

지난 3년 여 간의 경험을 토대로 두 가지 개념에 대해 정리를 해보고자 한다.

MVVM

먼저 한동안 모바일 애플리케이션 아키텍쳐에 큰 바람을 일으켰던 MVVM부터 리뷰를 해보자면 View를 사용자의 action을 입력받으면 ViewModel로 전달되고 ViewModel은 Model에 변경사항을 저장한다. 그러면 Model에 생긴 변화는 다시 ViewModel에게 전달되어 ViewModel의 state에 변화를 일으키고 이에 바인딩되어 있는 View가 다시 렌더링되어 사용자에게 보여진다. ViewModel이 Model에 데이터를 저장하고 변경된 데이터를 받아오는 과정은 데이터가 실제 물리적으로 어디에 저장되는지를 감추는 Repository라는 추상화된 데이터 저장소 객체를 사용하는 경우가 최근 일반화되고 있지만 본래 MVVM에서 정의하고 있는 부분은 아니다.

출처: https://velog.io/@mm723/MVVM-Pattern-in-Flutter

그런데 MVVM에는 사실 애플리케이션을 구성하는 요소를 모두 정의하고 있지는 않다. 그래서 활용하는 사람에 따라 의견이 분분하기도 하고 나름대로 각자 MVVM에 여러가지 요소를 덧붙이는 경우도 많았다. 경우에 따라서는 ViewModel이 단순히 View를 추상화하는 Model의 역할에서 벗어나 너무 많은 역할을 담당하게 되는 경우도 많았다. 그래서 ViewModel이 아니라 Model이 애플리케이션 로직을 가지고 있어야 한다고 주장하는 경우도 있었는데 이 또한 Model 객체에서 모든 역할을 다 가지게 되면 Model이 비대해질 우려가 있다. 또 위 그림에서는 하나의 ViewModel을 여러 개의 View에서 재사용하는 것처럼 그려져 있지만 실제로 구현하다보면 View의 구성요소를 ViewModel에 1:1로 구현하게 되고 그렇게 되면 재사용이 어려워지는 경우가 대부분이다. 완전히 동일한 View를 다른 방식(push, present 등)으로 presenting하는 경우를 제외하고는 말이다. 이는 ViewModel의 원래 역할을 생각해보면 당연한 일이다. 그런데 일각에서는 ViewModel이 재사용이 가능하다는 측면을 강조하는 경우가 꽤 있다. 그러나 ViewModel을 재사용할 수 있는 경우는 극히 제한적인 조건 하에서 뿐이다. (이는 Clean Architecture 에서도 마찬가지다.)

Clean Architecture

MVVM의 이런 비어있는 부분을 완성시켜주는 것이 Clean Architecture라고 생각할 수 있다. 다시 말하면 MVVM은 Clean  Architecture중에서 presentation layer에 포커싱되어 있다고 보면 될 것 같다.

출처: https://www.linkedin.com/pulse/clean-architecture-jo%C3%A3o-h%C3%A9lio-%C3%A1vila-miranda/

위 그림에서 View와 ViewModel을 MVVM에서의 V와 VM에 해당하는 부분으로 생각하고, Entity를 M으로 생각할 수 있다. 그리고 나머지 부분이 MVVM에서는 관심을 두지 않는 부분이다. Clean Architecture는 MVVM에서 VM에 편중되거나 무언가 다른 객체를 만들 수 밖에 없었던, VM과 M 사이에 생략되었던 부분을 Entity Layer(Domain Rules)와 Usecase Layer(Application Rules)로 명확하게 정리했다.

Interactor가 모든 추상화된 협력객체들을 가지고 애플리케이션 로직을 구현하고 있다. 여기서 Entity Gateway가 Repository라고 볼 수 있을 것 같다. 아래 그림을 보면 조금 더 상세하게 객체들의 관계를 살펴볼 수 있다.

출처: https://betterprogramming.pub/the-real-clean-architecture-in-android-part-1-s-o-l-i-d-6a661b103451

Domain Layer

이 그림에서는 가운데 있는 Domain Layer가 가장 상위 계층이고 양쪽으로 하위 계층이 표현되어 있다. 사실 이 그림에서 Domain Layer라고 표현되어 있는 부분을 더 잘게 나누자면 Domain Model과 Repository Abstraction을 Entity Layer(혹은 Domain Layer)로, 나머지를 Usecase Layer로 구분할 수 있다. Entity Layer는 애플리케이션에 따라 달라지지 않는 엔터프라이즈 업무 규칙을 구현하고, Usecase Layer는 애플리케이션에 따라 달라지는 애플리케이션 업무 규칙을 구현한다. 그리고 Presentation Layer와 Data Layer는 세부사항들이며 각각 다른 레이어라기 보다는 동일 레벨의 하나의 레이어 - Robert C. Martin은 이 레이어에 대해 특별한 이름조차 명명하지 않은 것 같다 - 로 보는 것이 타당할 것이고 각각 의존성이 없기 때문에 별도의 모듈로 만들어질 수는 있다고 본다. 또한 이 세부사항 레이어에는 Presentation 로직과 Data 로직 말고도 다른 부분이 더 존재할 가능성이 있다. 애플리케이션에 따라 파일이나 블루투스 등의 특정 디바이스의 기능을 다루는 세부 구현 로직이라든가 시스템의 정보를 얻어오는 로직들이 필요할 수 있다. 또 여러 협력 객체들의 구현체들을 조립하는 로직도 있을 것이다.

Presentation Layer

왼편에 있는 Presentation Layer를 들여다보면 이 그림에서도 역시 View와 ViewModel이 거기에 있는 것을 확인할 수 있고 그들 사이의 관계 역시 MVVM의 그것과 동일하다. 그런데 여기서 위 두 그림 사이에 달라보이는 점이 있는데 바로 Presntation Layer와 Usecase(첫번째 그림에서는 Interactor가 Usecase이다) 사이의 인터페이스 부분이다. 그러나 실제로는 그리 다르지 않다. 첫번째 그림에서는 ViewModel 과 Interactor(Usecase)사이가 상당히 복잡하게 표현되어 있다. 사용자가 View에 액션을 입력하면 Controller에게 전달되어 Request Model을 만들고 Input Boundary 인터페이스를 통해 Interactor에 전달한다. 그러면 Interactor는 여러 협력 객체를 활용하여 Response Model을 만들어서 Output Boundary 인터페이스를 통해 Presenter에 전달한다. Presenter는 마샬링을 거쳐 ViewModel을 업데이트하고 이에 바인딩되어 있는 View가 업데이트되는 구조다. 그런데 이 복잡한 표현이 두 번째 그림에서는 Controller와 Presenter의 역할이 ViewModel 안에 합쳐져 있고 Request Model, Response Model, Input/Output Boundary 인터페이스가 Usecase Abstraction 하나로 표현되어 있는 것으로 보면 된다. input과 output을 별도의 객체로 구분하지 않은 것 뿐이다.

그리고 이 그림에서는 View의 라우팅도 Presentation Layer에 두었다. 3년 동안의 프로젝트 경험에서는 라우팅을 애플리케이션 로직으로 생각해서 Usecase 내에서 정의하고 Presentation Layer에서 세부사항을 구현했지만 이후의 여러 가지 고민을 거듭한 결과 라우팅은 Presentation Layer라고 생각하는 것이 더 합당할 것 같다.

Data Layer

오른편에 있는 Data Layer에는 Repository 구현이 포함되어 있다. 그리고 Data Store Service Abstraction은 주로 서버의 REST API 서비스를 추상화한 개념으로 볼 수 있을 것 같다. 물론 로컬 스토리지일 수도 있다. 어쨋든 그 출력은 DTO로 정의되어 있는데 서비스 구현에 따라 DTO가 domain model과는 다른 형태일 수 있기 때문에 Data Mapper라는 추상화된 객체가 Domain Model과 DTO 사이를 마샬링하고 있는 것으로 보이고 Repository Implementation은 이 Data Mapper를 의존하고 있다. 그러나 내 경험에서는 DTO가 거의 100% Domain Model과 일치하도록 설계되어 있어서 Data Mapper의 존재의미가 없었다. 정확히 말하면 DTO가 Domain Model과 일치하는 JSON 문자열로 되어 있었고 이는 Model 객체 내에서 바로 decoding이 되었기 때문에, Domain Model이 DataMapper의 기능을 통합하고 있어서 Repository Implementation이 Domain Model을 의존하고 있었다고 해야 할 것 같다.

Data Store Implementation이 CoreData와 같은 로컬 DB를 사용하여 구현되어 있다면 Data Mapper를 따로 둘 수도 있겠지만 나는 주로 양쪽 Model 객체에 변환을 위한 initializer나 함수를 두는 방법을 사용하는 경우가 더 많다.

이는 상황에 따라 달라지는 문제인 것 같지만 일반화하기에는 위 그림이 더 적절할 수도 있을 것 같다.

결론

한때는 MVVM과 Clean Architecture에서 정의하는 ViewModel의 역할이 다르다고 오해한 적이 있었다. MVVM에서는 VM이 비지니스 로직을 담당해야 한다고 생각했었는데 Clean Architecture에서는 ViewModel을 단지 값 객체에 지나지 않는 것으로 표현하고 있었기 때문이다. 그러나 다시 생각해보면 MVVM에서 VM의 가장 큰 역할은 View를 추상화하는 것이지 비지니스 로직이 아니다. 다만 MVVM에서는 Presentation Layer에만 관심이 있고 비지니스 로직이 누구의 역할인지는 관심사가 아니기 때문에 명확히 정의하고 있지 않은 것 뿐이다. Clean Architecture는 오히려 비지니스 로직을 가장 중요한 부분으로 생각하고 이를 중심으로 객체들의 관계를 명확히 기술하고 있다. 이것이 둘 사이의 차이점이고 그래서 둘은 상호보완 혹은 포함관계 정도로 생각하면 좋을 것 같다.

 

==============================

수정

아래 영상을 다시 보니, 비지니스 로직은 비지니스 오브젝트인 Entity(Domain Model)가, 어플리케이션 로직은 Usecase가 구현한다는 내용이 있는데, 위 글에서 이들 용어들을 잘못 사용한 부분이 있어서 수정했다.

https://amara.org/ko/videos/0AtjY87egE3m/url/1216370/?tab=video

 

Video Subtitling, Captioning and Subtitle Translation Editor & Services - Amara Subtitling

Amara is using GoogleAnalytics, Microsoft Clarity and Hubspot as analytic cookies. These cookies allow us to gather data about website visits, traffic sources and user journeys. We use this data to review and improve Amara for our users. You can block thes

amara.org

 

반응형

'Software Engineering' 카테고리의 다른 글

싱글턴의 진짜 문제점  (0) 2024.01.30
Robert C. Martin의 Architecture  (0) 2019.12.05
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.