ViewModel 리소스 다이어트 시키기

Ximya(심야)·2023년 8월 15일
3

Plotz 개발일지

목록 보기
12/12
post-thumbnail

해당 포스팅은 유튜브 영화&드라마 리뷰 영상 큐레이션 플랫폼 Plotz를 개발하면서 도입된 기술 및 방법론에 대한 내용을 다루고 있습니다.
다운로드 링크 : 앱스토어 / 플레이스토어

Plotz 앱은 기본적으로 OOP(객체지향)를 기반한 MVVM(Model-View-ViewModel) 아키텍쳐를 준수하고 있습니다. MVVM은 많은 장점이 있지만, 몇 가지 단점도 동반합니다. 그중에서도 프로젝트의 규모가 커지고 한 화면에서 다루어야 할 비즈니스 로직들이나 데이터들이 많을수록 ViewModel 무거워지고 복잡해지는 것이 가장 큰 단점이라고 할 수 있습니다.

객체치향 프로그래밍이란 처음에 이루고자 하는 목표에서부터, 덩어리들을 차근차근 분리
하고 깍아내는 과정이라고 합니다. 이러한 관점으로 복잡해진 ViewModel을 인간이 쉽게 이해할 수 있을 정도로 쪼개는 작업이 필요합니다. 케이크를 먹을 때도 먹기 좋게 슬라이스하는 것처럼 말이죠.

ViewModel를 쪼개는(구조화) 방법은 여러 가지가 있습니다.

  • 여러 개의 ViewModel로 분리하여 관리
  • Use Case 및 Repository 도입 (비즈니스 데이터 액세스 분리)
  • extension, part 키워드를 활용하여 ViewModel 리소스를 분리
  • 기타 등등

상황과 목적에 따라 위에 방법들을 적절히 사용하는 게 중요하겠지만, 본 포스팅에서는 extension, part 키워드를 이용하여 ViewModel 리소스를 분리하여 관리 하는 방법과 그 이점에 대해 다루어보려고 합니다.

Plotz 앱의 '콘텐츠 상세 스크린'을 예시로 합니다.


1. 섹션정의

먼저 섹션을 구분해야됩니다.

콘텐츠의 여러 정보를 보여주는 '콘텐츠 상세 스크린'을 크게 3가지의 섹션으로 정의했습니다.

  • Header
  • Tab1
  • Tab2

NOTE
각 섹션은 보통 하나의 주제나 레이아웃으로 구분됩니다.


2. Getter로 데이터 접근 구조화하기

섹션을 정의했으면 이제 섹션별로 ViewModel의 데이터 접근을 구조화해 볼 수 있습니다.

class ContentDetailViewModel extends BaseViewModel {
    //	콘텐츠 정보(제목, 장르, 개봉년도, 출연진, 등등)
	final Content contentInfo; 
    
    // 유튜브 채널 정보 (채녈명, 구독자 수, 등등)
    final Video videoInfo; 
    
    //유튜브 영상 정보 (조회수, 좋아요 수, 업로드일)
    final Channel channleInfo;
   
   ...
}

콘텐츠 상세 페이지에는 콘텐츠, 채널, 영상 이렇게 3가지 유형의 데이터가 존재하고 ViewModel 클래스에서 각 객체를 관리하고 있습니다.

final vm = ContentDetailViewModel()

// 헤더 섹션
Column(
	children: [
    	Text(vm.contentInfo.title.korean), // 제목
        Text(vm.contentInfo.genres.name), // 장르
        Text(vm.videoInfo.title) // 비디오 제목
    ]
)	

ViewModel에서 관리하고 있는 데이터의 모델이 위와 같이 중첩된 형태이기 때문에 UI 위젯에서 데이터를 접근할 때 dot notation, 즉 .(온점)을 이용하여 필요한 객체에 접근하고 있습니다. 이 방식도 문제가 있는건 아니지만, ViewModel 클래스에서 getter 메소드를 이용하여 데이터 접근을 구조화하면 조금 간편하고 쉽게 UI 위젯에서 데이터를 받을 수 있게 됩니다.

getter란?
getter는 클래스 내부의 속성을 외부에서 읽을 수 있게 해주는 메소드입니다. Getter는 일반적으로 클래스의 내부 속성을 직접 접근하는 대신, 속성 값을 반환하는 데 사용됩니다.

class ContentDetailViewModel extends BaseViewModel {
	/* Variables */
	final Content _contentInfo;
    final Video _videoInfo; 	
    final Channel _channleInfo;
   
   /* Getters */
   String title => _contentInfo.title.korean;
   String genres => _contentInfo.genres.name;
   String videoTitle => _contentInfo.videoInfo.title;
   
   ...
}

위 코드와 같이 ViewModel 클래스 안에서 UI 위젯에서 필요한 데이터들을 getter메소드를 통해 접근할 수 있게 명확히 구분 지어 주면 여러 가지 이점이 있습니다.

1. 추상화와 단순성

final vm = ContentDetailViewModel()

// getter 적용버전
Column(
	children: [
    	Text(vm.title), 
        Text(vm.genres), 
        Text(vm.videoTitle)
    ]
)	

// 이전 버전
Column(
	children: [
    	Text(vm.contentInfo.title.korean), // 제목
        Text(vm.contentInfo.genres.name), // 장르
        Text(vm.videoInfo.title) // 비디오 제목
    ]
)	

첫 번째로, getter를 이용해서 데이터 구조를 ViewModel에 캡슐화함으로써 UI 코드가 더 추상화되고 단순해집니다. UI 코드에서는 데이터의 구체적인 구조를 알 필요가 없으며, 간결하게 접근할 수 있게 됩니다.

2.확장성

/// ex) 데이터 모델이 변경되었을 때
/// 기존 값 -> _contentInfo.title.korean
String title => _contentInfo.title;

Column(
	children: [
    	Text(vm.title), 
    ]
)	

ViewModel에서 관리하는 객체의 내부 구현을 변경하거나 확장할 때, getter를 통해 접근하는 UI 코드는 그대로 유지될 가능성이 높습니다. 새로운 데이터 속성을 추가하거나 기존 속성을 변경해도 getter의 시그니처는 유지되므로, ViewModel과 View의 의존성이 분리되어 UI 코드 변경이 최소화되는 효과를 얻습니다.

3. 연산 추가

// 날짜 포맷 변경 로직, 2008-01-20 --> 2008
String releaseYear => Formatter.dateToYear(_contentInfo.releaseDate)

getter는 단순히 속성값을 반환하는 것 이상의 역할을 할 수 있습니다. 계산된 값, 변환된 데이터, 다른 속성들의 조합 등을 반환하는 로직을 getter 내부에 넣을 수 있습니다. 이로써 코드의 가독성을 높이고 재사용성을 증가시킬 수 있습니다.


3. extension 키워드를 이용하여 ViewModel 분할하기

마지막 단계입니다. 이제 ViewModel 리소스들을 분리해 주면 됩니다. 이 단계에서 part와 그리고 extension 키워드가 사용됩니다.

먼저 위에서 정의한 섹션별로 part 파일을 각각 만들어 줍니다.

NOTE
part파일의 경로나 파일명은 유동적으로 변경하셔도 무방합니다.

content_detail_view_model.dart

part 'resources/header.p.dart'; // 헤더 영역
part 'resources/tab1.p.dart'; // tab1 영역
part 'resources/tab2.p.dart'; // tab2 영역

class ContentDetailViewModel extends BaseViewModel {
	...
}

그다음 ViewModel 소스파일에서 part 파일을 호출해 주고,

resources/header.dart

part of '../content_detail_view_model.dart';

extension HeaderResources on ContentDetailViewModel {
	....
}

생성한 part 파일에는 part of 디렉티브를 사용하여 ViewModel 소스파일에서 part 파일의 리소스들에 접근할 수 있도록 연결해 줍니다. 그리고 가장 중요한 부분인데, 해당 part 파일에 ViewModel을 확장하는 extension을 정의 해주면 됩니다.

이렇게 part파일 안에 ViewModel을 확장하는 extension을 만들어 주는 이유는 part파일에서 ViewModel 자원에 접근하기 위함입니다. part of 디렉티브를 사용하여 ViewModel에서 part파일 자원에 접근할 수 있게하고 extension을 만들어줘 part파일이 ViewModel 자원에 접근할 수 있는 형태인 거죠.

resources/header.dart

part of '../content_detail_view_model.dart';

extension HeaderResources on ContentDetailViewModel {
   /* Getters */
   String title => _contentInfo.title.korean;
   String genres => _contentInfo.genres.name;
   String videoTitle => _contentInfo.videoInfo.title;
   
   /* Intent */
   void heaerMethod1() {...}
   void heaerMethod2() {...}
   void heaerMethod3() {...}
}

마지막으로 각 part파일에 각 섹션에 해당하는 이벤트 메소드getter 구문을 기존 ViewModel 클래스로부터 옮겨주어 리소스를 분리합니다.

NOTE
extension 내부에서 변수를 선언하는 것은 허용되지 않은 점에 유의해주세요. extension은 기존 클래스에 새로운 기능을 추가하는 메커니즘으로, 클래스의 인스턴스 변수나 속성을 직접 추가할 수 없습니다. Extension은 클래스의 인스턴스 메서드나 getter, setter, 일부 특정 메타데이터만을 추가할 수 있습니다.

모든 섹션에서 공통으로 사용되는 리소스는 기존 ViewModel 클래스에서 관리하고, 각 섹션에서 독립적으로 사용되는 리소스들을 위와 같이 part 파일로 분리하여 관리하는 게 바람직합니다.


이점

이렇게 ViewModel 리소스를 분리하여 관리하면 어떤 이점이 있을까요?

1. 가독성 향상

ViewModel 크고 복잡해질수록 가독성이 중요해집니다. ViewModel 리소스를 분리하여 관리하면 정의한 섹션의 코드가 하나의 파일에 모여 있어서 코드의 목적을 파악하기 쉬워집니다.

2. 유지 보수 용이성

ViewModel이나 다른 로직이 part 파일로 분리되면, 해당 부분을 수정하거나 확장할 때 다른 부분에 영향을 덜 줍니다. 변경이 필요한 부분만 수정하여 기능을 개선하거나 버그를 수정할 수 있습니다.

3. 팀 협업 용이성

여러 명의 개발자가 협업하는 경우, 코드의 구조를 일관성 있게 유지하고 변경 사항을 더 쉽게 추적하는 데 유리할 수 있습니다. 각각의 part 파일을 담당하는 팀원들이 독립적으로 작업할 수 있게 되는 거죠.
예를 들어 한 화면에서 각 영역별로 한명씩 구현 작업을 분담한다고 했을 때 part 파일을 분리하면 각자의 작업영역을 명확하게 할 수 있기 때문에 불필요하게 git conflict가 나는 상황을 방지할 수 있습니다.

마무리

이번 포스팅에서는 MVVM 패턴에서 ViewModel 리소스를 구조화하여 관리하는 방법과 그 이점에 대해 알아보았습니다. ViewModel 리소스를 분리하면 여러 분명 이점이 있지만 상황과 목적에 맞게 적절히 사용해야 합니다. 필요 이상으로 많은 part 파일을 생성하여 ViewModel을 분리하다 보면, 오히려 코드의 문맥을 이해하기 어려워질 수 있기 때문이죠. 상황과 목적을 고려하여 ViewModel을 깔끔하게 관리해 보시죠!

profile
https://medium.com/@ximya

5개의 댓글

comment-user-thumbnail
2023년 8월 15일

이런 유용한 정보를 나눠주셔서 감사합니다.

1개의 답글
comment-user-thumbnail
2024년 2월 16일

안녕하세요, 심야님의 velog뿐만 아니라 github의 코드를 탐독하면서 부족함을 많이 깨닫고 있는 flutter 취준생입니다! 항상 좋은 글과 코드 공유해주신 점 감사드립니다. 혹시 MVVM 패턴 학습 및 적용에 특히 도움이 되었던 책이나 강의, 문헌 자료가 있으신가요? 기능 구현에 집중하다보니 유지보수에도 관심이 생겼는데, 심야님은 어떤 자료로 학습하셨는지 궁금합니다!

1개의 답글