이 글은 https://docs.flutter.dev/app-architecture/guide 를 읽고 정리한 내용입니다.
프로젝트 구조 개요
관심사의 분리가 가장 중요한 원리로 플러터 앱은 크게 UI 레이어와 Data 레이어로 나눠질 수 있습니다.
각 레이어는 다음과 같이 더 디테일하게 분리될 수 있고, 각 구성요소는 고유한 책임, 인터페이스, 각각의 경계및 종속성을 가지고 있습니다.
- View
- View models
- Repositories
- Services
MVVM
앱을 세개의 부분으로 나누는데 Model, ViewModel, Model 로 나눌 수 있습니다.
View, ViewModel : 앱의 UI를 그리는 뷰와, 그 뷰에서 필요한 로직을 처리하는 뷰 모델
Repositories, Services : 앱 데이터의 single source인(sources of truth) repositories, 클라이언트 서버와 플랫폼 플러그인과 같은 외부 API와 상효 작용하는 서비스(앱 구조에 따라 서비스는 없을 수 있음)
MVVM 아키텍처 예시
MVVM 모델 도식화
UI 레이어
애플리케이션의 UI 계층은 사용자와 상호 작용하는 것을 담당합니다. 애플리케이션의 데이터를 사용자에게 표시하고 탭 이벤트 및 양식 입력과 같은 사용자 입력을 수신합니다.
UI가 리포지토리에서 새 데이터를 받으면 해당 새 데이터를 표시하기 위해 다시 렌더링해야 합니다. 사용자가 UI와 상호 작용하면 해당 상호 작용을 반영하도록 변경해야 합니다.
UI 계층은 MVVM 디자인 패턴을 기반으로 하는 두 가지 아키텍처 구성 요소로 구성됩니다.
- 뷰는 사용자에게 애플리케이션 데이터를 제공하는 방법을 설명합니다. 구체적으로는 기능을 구성하는 *위젯의 구성*을 말합니다. 예를 들어, 뷰는 종종(항상은 아니지만) Scaffold위젯 트리에서 위젯 아래에 있는 모든 위젯과 함께 위젯이 있는 화면입니다. 뷰는 또한 사용자 상호 작용에 대한 응답으로 뷰 모델에 이벤트를 전달하는 역할을 합니다.
- 뷰 모델은 앱 데이터를UI State 로 변환하는 로직을 포함합니다 . 저장소의 데이터는 종종 표시해야 하는 데이터와 다르게 포맷되기 때문입니다. 예를 들어, 여러 저장소의 데이터를 결합해야 할 수도 있고, 데이터 레코드 목록을 필터링해야 할 수도 있습니다.
뷰와 뷰 모델은 1:1 관계입니다.
간단히 말해서, 뷰 모델은 UI 상태를 관리하고 뷰는 해당 상태를 표시합니다. 뷰와 뷰 모델을 사용하면 UI 레이어는 구성 변경(예: 화면 회전) 중에 상태를 유지할 수 있으며, Flutter 위젯과 독립적으로 UI의 논리를 테스트할 수 있습니다.
'뷰'는 추상적인 용어이며, 하나의 뷰가 하나의 위젯과 같지는 않습니다. 위젯은 구성 가능하며, 여러 개를 결합하여 하나의 뷰를 만들 수 있습니다. 따라서 뷰 모델은 위젯과 1대 1 관계가 아니라 위젯 컬렉션 과 1대 1 관계가 있습니다.
Views
Flutter에서 뷰는 애플리케이션의 위젯 클래스입니다. 뷰는 UI를 렌더링하는 주요 방법이며, 비즈니스 로직을 포함해서는 안 됩니다. 뷰 모델에서 렌더링하는 데 필요한 모든 데이터를 전달받아야 합니다.
뷰에 포함되어야 하는 유일한 논리는 다음과 같습니다.
- 뷰 모델의 플래그 또는 null 가능 필드에 따라 위젯을 표시하거나 숨기는 간단한 if 문
- 애니메이션 로직
- 화면 크기나 방향과 같은 기기 정보에 따른 레이아웃 논리입니다.
- 간단한 라우팅 로직
데이터와 관련된 모든 논리는 뷰 모델에서 처리되어야 합니다.
View Models
뷰 모델은 뷰를 렌더링하는 데 필요한 애플리케이션 데이터를 노출합니다. 이 페이지에서 설명하는 아키텍처 디자인에서 Flutter 애플리케이션의 대부분 로직은 뷰 모델에 있습니다.
뷰 모델의 주요 책임은 다음과 같습니다.
- Repositories에서 애플리케이션 데이터를 검색하여 뷰에서 프레젠테이션에 적합한 형식으로 변환합니다. 예를 들어, 데이터를 필터링, 정렬 또는 집계할 수 있습니다.
- 뷰에서 필요한 현재 상태를 유지하여 뷰가 데이터를 잃지 않고 다시 빌드할 수 있도록 합니다. 예를 들어, 뷰에서 위젯을 조건부로 렌더링하는 부울 플래그나 화면에서 회전식 슬라이드의 어느 섹션이 활성화되어 있는지 추적하는 필드가 포함될 수 있습니다.
- 버튼 눌림이나 양식 제출과 같은 이벤트 핸들러에 첨부할 수 있는 콜백( 명령 이라고 함 )을 뷰에 노출합니다.
Data layer
데이터 레이어는 비즈니스 데이터와 로직을 처리하는 영역입니다. Service와 Repositories로 이루어져 있고, 재사용성과 테스트 가능성을 위해 잘 정의된 인풋과 아웃풋을 가져야 합니다.
MVVM에서는 Service와 Repositories가 model layer를 구성하게 됩니다.
Repositories
Repository 클래스는 모델 데이터의 single source fo truth 입니다. 이들은 서비스에서 데이터를 폴링하고, 그 원시 데이터를 도메인 모델 로 변환하는 역할을 합니다 . 도메인 모델은 애플리케이션에 필요한 데이터를 나타내며, 뷰 모델 클래스가 사용할 수 있는 방식으로 포맷됩니다. 앱에서 처리하는 각기 다른 유형의 데이터에 대해 저장소 클래스가 있어야 합니다.
저장소는 다음과 같은 서비스와 관련된 비즈니스 로직을 처리합니다.
- 캐싱
- 오류 처리
- 재시도 논리
- 데이터 새로 고침
- 새로운 데이터에 대한 폴링 서비스
- 사용자 동작에 따라 데이터 새로 고침
repository는 도메인 모델 형태로 애플리케이션 데이터를 출력합니다. For example, a social media app might have a UserProfileRepository class that exposes a Stream<UserProfile?>, which emits a new value whenever the user signs in or out.
repository에서 출력된 모델은 뷰 모델에서 사용합니다. repository와 뷰 모델은 다대다 관계를 갖습니다. 뷰 모델은 많은 repository를 사용하여 필요한 데이터를 가져올 수 있으며 repository는 많은 뷰 모델에서 사용할 수 있습니다.
repository는 서로를 알아서는 안되고, 애플리케이션에서 두 repository의 데이터가 필요한 비즈니스 로직이 있는 경우 뷰 모델이나 도메인 계층에서 데이터를 결합해야 합니다.
Services
서비스는 애플리케이션의 가장 낮은 계층에 있습니다. 서비스는 API 엔드포인트를 래핑하고 Future나 Stream 객체와 같은 비동기 응답 객체를 노출합니다. 서비스는 데이터 로딩을 격리하는 데만 사용되며 상태를 유지하지 않습니다. 앱에는 데이터 소스당 하나의 서비스 클래스가 있어야 합니다. 서비스가 래핑할 수 있는 엔드포인트의 예는 다음과 같습니다.
- iOS 및 Android API와 같은 기본 플랫폼
- REST 엔드포인트
- 로컬 파일
서비스와 리포지토리는 다대다 관계를 가지고 있습니다. 단일 리포지토리는 여러 서비스를 사용할 수 있으며, 서비스는 여러 리포지토리에서 사용될 수 있습니다.
Optional: Domain layer
앱이 성장하고 기능이 추가됨에 따라 뷰 모델에 너무 많은 복잡성이 추가되는 로직을 별도로 추상화해야 할 수도 있습니다. 이러한 클래스는 종종 인터랙터 또는 use-case 라고 합니다 .
use-case는 UI와 데이터 계층 간의 상호작용을 더 간단하고 재사용 가능하게 만드는 역할을 합니다. use-case는 repository에서 데이터를 가져와 UI 계층에 적합하게 만듭니다.
use-case는 주로 뷰 모델에 존재하고 다음 조건 중 하나 이상을 충족하는 비즈니스 로직을 캡슐화하는 데 사용됩니다.
- 여러 repository의 데이터를 병합해야 합니다.
- 매우 복잡하다
- 로직이 다른 뷰 모델에서 재사용됩니다.
이 계층은 선택 사항입니다. 모든 애플리케이션이나 애플리케이션 내의 기능이 이러한 요구 사항을 가지고 있는 것은 아니기 때문입니다. 애플리케이션이 이 추가 계층에서 이점을 얻을 수 있다고 생각되면 장단점을 고려하세요.
use-case를 통한 데이터 액세스
use-case를 추가할 때는 모든 뷰 모델마다 use-case를 추가할 것인지 아니면 필요할 때만 use-case를 추가할 것인지 생각해 볼 수 있습니다. 전자 선택시 위의 장단점이 더욱 강화됩니다. 애플리케이션 코드는 매우 모듈화되고 테스트 가능하지만 불필요한 오버헤드가 상당히 추가됩니다.
좋은 접근 방식은 필요할 때만 사용 사례를 추가하는 것입니다. 궁극적으로 다음과 같은 흐름이 됩니다.
use-case를 추가하는 이 방법은 다음 규칙으로 정의됩니다.
- use-case는 저장소에 따라 달라집니다
- use-case와 저장소는 다대다 관계를 갖습니다.
- 뷰 모델은 하나 이상의 use-case와 하나 이상의 저장소 에 따라 달라집니다.