Flutter 엔지니어를 위한 아키텍처 입문 가이드: 효율적이고 확장 가능한 앱 설계 방법
2024년 11월 18일, Flutter의 공식 문서에서 Flutter의 설계에 관한 내용이 공개되었습니다.
이에 따라, Flutter의 설계 원칙을 보다 쉽게 이해할 수 있도록 내용을 정리하고, 직역이나 인공지능 번역으로는 이해하기 어려운 부분을 최대한 풀어 설명했습니다.
처음에
이번 가이드에서는 다음과 같은 내용을 배울 수 있습니다:
- 아키텍처의 장점
- 일반적인 아키텍처 원칙
- Flutter 팀이 추천하는 아키텍처
- MVVM과 상태 관리
- 의존성 주입
- 견고한 Flutter 애플리케이션을 만들기 위한 일반적인 디자인 패턴
아키텍처의 장점
우수한 아키텍처는 엔지니어와 사용자 모두에게 많은 이점을 제공합니다.
- 유지보수성: 시간이 지나도 변경, 업데이트, 문제 수정이 용이해집니다.
- 확장성: 코드 충돌을 최소화하고, 여러 명이 동시에 같은 코드를 수정할 수 있게 합니다.
- 테스트 용이성: 입력과 출력이 명확히 정의된 단순한 클래스 덕분에 모킹이나 테스트가 쉬워집니다.
- 인지 부하 감소: 프로젝트에 익숙하지 않은 엔지니어도 짧은 시간 안에 코드를 이해할 수 있어 생산성이 향상되고 코드 리뷰 시간이 단축됩니다.
- 우수한 사용자 경험: 각 기능을 더 빠르고 버그 없이 출시할 수 있습니다.
이 가이드의 사용 방법
이 가이드는 스케일러블한 Flutter 애플리케이션을 구축하는 방법을 제시하며, 기능이 풍부한 애플리케이션을 구축하거나 여러 명의 엔지니어 팀이 같은 코드를 개발하는 경우에 유용합니다.
성장하는 팀과 코드를 가진 Flutter 애플리케이션을 만들고 있다면 이 가이드가 큰 도움이 될 것입니다.
이 가이드는 일반적인 아키텍처 조언과 함께 베스트 프랙티스의 구체적인 예시를 제공하며, 구체적인 권장 사항도 다룹니다.
일부 라이브러리는 교체 가능하며, 복잡한 대규모 팀에서는 일부 부분이 맞지 않을 수 있습니다.
그러나 아이디어는 건전하며, 이 가이드는 Flutter 애플리케이션을 구축하는 데 권장되는 방법을 제시합니다.
1. 공통 아키텍처 개념 (Common architecture concepts)
이 섹션에서는 애플리케이션 아키텍처를 결정할 때 참고할 수 있는 원칙과 Flutter에서 어떻게 적용되고 있는지 소개합니다.
추천되는 아키텍처와 베스트 프랙티스에 관련된 용어와 개념을 탐구하는 입문서로 활용하세요.
관심사의 분리 (Separation of concerns)
"관심사의 분리"는 애플리케이션 기능을 독립적인 단위로 분할하여 모듈화와 유지보수성을 촉진하는 기본 원칙입니다.
이는 UI 로직(화면 관련 동작이나 제어를 위한 처리)과 비즈니스 로직(데이터 교환이나 로그인 처리 등 UI와 관련 없는 처리를 분리하는 것을 의미합니다.
예시: 인증 로직은 검색 로직과 별개의 클래스에 분리해야 합니다.
Flutter에서의 적용 예:
UI 레이어의 Widget에서도 가능한 한 로직을 최소화하고, 재사용 가능하며 단순한 Widget을 생성합니다.
레이어드 아키텍처 (Layered architecture)
Flutter 애플리케이션은 레이어로 나누어 작성해야 합니다.
레이어드 아키텍처는 애플리케이션을 특정 역할과 책임을 가진 명확한 레이어로 정리하는 설계 패턴입니다.
일반적으로 애플리케이션은 복잡도에 따라 2~3개의 레이어로 분할됩니다.
- UI 레이어:
비즈니스 로직 레이어가 제공하는 데이터를 사용자에게 표시하고, 사용자 조작을 처리합니다. 프레젠테이션 레이어라고도 합니다. - 로직 레이어:
코어 비즈니스 로직을 구현하며, 데이터 레이어와 UI 레이어 간의 교류를 중재합니다. 도메인 레이어라고도 합니다.
이 레이어는 클라이언트 측에서 복잡한 비즈니스 로직이 필요한 경우에만 구현합니다. 사용자에게 데이터를 제공하고, 사용자가 데이터를 변경할 수 있도록 하는 것에만 관심이 있습니다 (일명 CRUD 애플리케이션에서는 이 레이어가 필요하지 않을 수도 있습니다). - 데이터 레이어:
데이터베이스나 플랫폼 플러그인과 같은 데이터 소스와의 교류를 관리하고, 비즈니스 로직 레이어에 데이터와 메소드를 제공합니다.
각 레이어는 상하 레이어와만 직접 통신할 수 있습니다. UI 레이어가 데이터 레이어를 직접 알거나 그 반대도 마찬가지입니다.
단일 진실의 정보원 (Single source of truth)
애플리케이션 내의 모든 데이터 타입은 신뢰할 수 있는 단일 정보원(SSOT)을 가져야 합니다.
SSOT는 로컬 또는 원격 상태를 표현합니다.
SSOT 클래스는 애플리케이션 내에서 데이터를 변경할 수 있는 경우, 유일하게 그 변경을 수행할 권한을 가집니다.
이를 통해 애플리케이션의 버그가 크게 줄어들고, 동일한 데이터의 복사본이 하나만 존재하여 코드가 단순해집니다.
일반적으로 SSOT는 "Repository"라고 불리는 데이터 레이어 내의 클래스에 보관됩니다. 애플리케이션 내의 각 데이터 타입에 대해 하나의 Repository 클래스를 준비하는 것이 일반적입니다.
예시:
이 원칙은 애플리케이션의 레이어나 컴포넌트 간뿐만 아니라, 개별 클래스 내에서도 적용할 수 있습니다.
예를 들어, Dart 클래스에서는 SSOT 필드에서 값을 파생하기 위해 Getter를 사용할 수 있습니다 (여러 필드를 독립적으로 업데이트할 필요 없이).
관련 값을 그룹화하기 위해 Records 리스트를 사용할 수도 있습니다 (인덱스가 동기화되지 않는 병렬 리스트 대신).
단방향 데이터 흐름 (Unidirectional data flow, UDF)
단방향 데이터 흐름(UDF)은 상태와 그 상태를 표시하는 UI를 분리하는 설계 패턴을 의미합니다.
간단히 말해, 상태는 데이터 레이어에서 로직 레이어를 거쳐 최종적으로 UI 레이어의 Widget으로 흐릅니다.
사용자 조작에서 발생하는 이벤트는 역방향으로 흐르며, 프레젠테이션 레이어에서 로직 레이어를 거쳐 데이터 레이어에 도달합니다.
UDF에서의 업데이트 루프 흐름:
- UI 레이어:
버튼 클릭 등 사용자 조작으로 이벤트가 발생합니다. Widget의 이벤트 핸들러 콜백(클릭 이벤트 등 처리 후 호출되는 처리)이 로직 레이어의 클래스 메소드를 호출합니다. - 로직 레이어:
로직 클래스가 데이터를 변경하기 위한 특정 Repository 메소드를 호출합니다. - 데이터 레이어:
Repository가 필요에 따라 데이터를 업데이트하고, 그 새로운 데이터를 로직 클래스에 제공합니다. - 로직 레이어:
로직 클래스가 새로운 상태를 저장하고, 이를 UI에 전달합니다. - UI 레이어:
UI가 ViewModel의 새로운 상태를 표시합니다.
데이터 레이어에서 시작하는 경우도 있습니다.
예를 들어, Repository가 HTTP 서버를 폴링하여 새로운 데이터를 가져오는 경우가 그렇습니다. 이 경우 데이터 흐름은 후반부 부분만 거칩니다.
가장 중요한 개념은 데이터의 변경이 항상 SSOT(데이터 레이어)에서 이루어진다는 점입니다.
이를 통해 코드는 이해하기 쉬워지고, 오류 발생 가능성이 줄어들며, 잘못되거나 예상치 못한 데이터 생성이 방지됩니다.
UI는 상태의 함수이다 (UI is a function of state)
Flutter는 선언적이기 때문에, 애플리케이션의 현재 상태를 반영하여 UI를 구성합니다.
상태가 변하면, 그 상태에 의존하는 UI의 재구성이 이루어집니다. Flutter에서는 이를 "UI는 상태의 함수이다"라고 표현합니다.
데이터는 불변하고 영구적이어야 하며, View에는 가능한 한 적은 로직만 포함되어야 합니다.
이를 통해 애플리케이션을 닫았을 때 데이터가 손실될 가능성을 최소화하고, 애플리케이션을 더 쉽게 테스트하고 버그에 강하게 만듭니다.
확장성 (Extensibility)
확장성이 높은 설계에서는 새로운 기능을 추가하거나 기존 로직을 변경할 때, 다른 부분에 영향을 미칠 위험이 적어집니다.
각 아키텍처 컴포넌트는 명확하게 정의된 입력과 출력 목록을 가져야 합니다.
예시: 로직 레이어의 ViewModel은 Repository와 같은 데이터 소스를 입력으로 받아, 커맨드나 View용으로 포맷된 데이터를 출력으로 공개해야 합니다.
이러한 깨끗한 인터페이스를 사용함으로써, 클래스의 구체적인 구현을 교체할 때 해당 인터페이스를 사용하는 코드를 변경할 필요가 없어집니다.
테스트 용이성 (Testability)
소프트웨어를 확장 가능하게 만드는 원칙은 소프트웨어의 테스트 용이성도 향상시킵니다.
예시: Repository를 모킹함으로써, ViewModel의 자체 완결형 로직을 테스트할 수 있습니다. 이 ViewModel의 테스트에서는 애플리케이션의 다른 부분을 모킹할 필요가 없습니다.
또한, Widget 자체와는 별개로 UI 로직을 테스트할 수 있습니다.
애플리케이션의 유연성도 향상됩니다. 새로운 로직이나 새로운 UI를 추가하는 것이 쉽고 위험이 낮아집니다.
예시: 새로운 ViewModel을 추가하더라도, 데이터나 비즈니스 로직 레이어의 다른 로직에 영향을 주지 않습니다.
2. 애플리케이션 아키텍처 가이드 (Guide to app architecture)
이 가이드에서는 베스트 프랙티스를 사용하여 애플리케이션을 구축하는 방법을 설명합니다.
이러한 권장 사항은 대부분의 애플리케이션에 적용될 수 있으며, 확장성, 테스트, 유지보수가 용이해집니다.
그러나 이는 가이드라인일 뿐이며, 반드시 준수해야 하는 절대적인 규칙은 아닙니다. 애플리케이션의 특정 요구사항에 맞게 조정해야 합니다.
이 섹션에서는 Flutter 애플리케이션을 설계하는 방법에 대한 개요를 설명합니다.
애플리케이션의 레이어와 각 레이어 내에 존재하는 클래스에 대해 다루며, 이후 섹션에서는 구체적인 코드 예시를 통해 이러한 권장 사항을 구현한 Flutter 애플리케이션의 구축 방법을 설명합니다.
프로젝트 구조의 개요 (Overview of project structure)
Flutter 애플리케이션을 설계할 때 가장 중요한 원칙은 "관심사의 분리(Separation of concerns)"입니다.
Flutter 애플리케이션은 UI 레이어와 데이터 레이어의 두 가지 레이어로 나누어 작성해야 합니다.
각 레이어는 서로 다른 컴포넌트로 분할됩니다. 각 컴포넌트는 명확한 책임, 정의된 인터페이스, 경계, 의존성을 가져야 합니다.
이 가이드에서는 애플리케이션을 다음과 같은 컴포넌트로 분할하는 것을 권장합니다:
- Views
- ViewModels
- Repositories
- Services
MVVM
"Model-View-ViewModel (MVVM)" 디자인 패턴을 들어본 적이 있다면, 이는 친숙한 패턴일 것입니다.
MVVM은 애플리케이션 기능을 세 부분으로 나누는 디자인 패턴입니다: Model, ViewModel, 그리고 View.
- View와 ViewModel은 애플리케이션의 UI 레이어를 구성합니다.
- Repositories와 Services는 애플리케이션의 데이터, 즉 MVVM의 Model 레이어를 나타냅니다.
애플리케이션 내의 각 기능은 하나의 View와 하나의 ViewModel, 하나 이상의 Repository, 그리고 클라이언트 서버나 플랫폼 플러그인과 같은 외부 API와의 교류를 위한 0개 이상의 Service를 포함해야 합니다.
애플리케이션의 단일 기능이 필요로 할 수 있는 객체는 다음과 같습니다:
- View
- ViewModel
- Repository
- Service
이러한 각 객체와 그것들을 연결하는 관계는 이 가이드의 마지막까지 자세히 설명됩니다.
전체 가이드를 통해, 다음과 같은 간략화된 버전의 그림이 참조로 사용됩니다.
Note
복잡한 로직을 가진 애플리케이션에서는 UI 레이어와 데이터 레이어 사이에 로직 레이어가 존재할 수 있습니다.
이 로직 레이어는 일반적으로 "도메인 레이어"라고 불립니다. 도메인 레이어에는 Interactors 또는 Use-Cases라고 하는 추가 컴포넌트가 포함됩니다. 도메인 레이어에 대해서는 이 가이드의 후반부에서 설명합니다.
애플리케이션 기능은 사용자 중심이며, UI 레이어에서 정의됩니다.
View와 ViewModel의 각 인스턴스는 애플리케이션 내의 하나의 기능을 정의합니다. 이는 종종 애플리케이션 내의 하나의 화면을 의미하지만, 반드시 그런 것은 아닙니다.
예를 들어, 로그인과 로그아웃을 생각해봅시다. 로그인은 일반적으로 로그인 방법을 사용자에게 제공하는 특정 화면에서 이루어집니다.
애플리케이션 코드에서는 로그인 화면이 LoginViewModel 클래스와 LoginView 클래스로 구성됩니다.
반면, 애플리케이션에서 로그아웃은 일반적으로 별도의 화면에서 이루어지지 않습니다.
로그아웃 기능은 일반적으로 메뉴나 사용자 계정 화면, 또는 다양한 위치에 있는 버튼으로 사용자에게 제공됩니다.
이 경우, LogoutViewModel과 다른 Widget에 쉽게 포함할 수 있는 단일 버튼만을 포함하는 LogoutView를 가질 수 있습니다.
Views
Flutter에서 Views는 애플리케이션의 Widget 클래스를 말합니다.
Views는 UI를 렌더링하는 주요 방법이며, 비즈니스 로직을 포함해서는 안 됩니다. 렌더링에 필요한 모든 데이터는 ViewModel에서 전달받아야 합니다.
View가 포함해야 하는 유일한 로직:
- ViewModel 내의 플래그나 nullable 필드를 기반으로 Widget을 표시하거나 숨기는 단순한 if 문
- 애니메이션 로직
- 디바이스 정보(화면 크기나 방향 등)에 기반한 레이아웃 로직
- 단순한 라우팅 로직
데이터와 관련된 모든 로직은 ViewModel에서 처리되어야 합니다.
ViewModels
ViewModel은 View를 렌더링하는 데 필요한 애플리케이션 데이터를 공개합니다.
이 아키텍처 디자인에서는, Flutter 애플리케이션의 로직 대부분이 ViewModel에 존재합니다.
ViewModel의 주요 책임:
- Repository에서 애플리케이션 데이터를 가져와, View에서의 표시에 적합한 형식으로 변환합니다. 예시로 데이터 필터링, 정렬, 집계 등이 있습니다.
- View에서 필요한 현재 상태를 유지합니다. 이를 통해 View는 데이터를 잃지 않고 재구성할 수 있습니다. 예시로 Widget의 표시 여부를 제어하는 boolean 플래그나, 캔버스 내 활성 섹션을 추적하는 필드 등이 있습니다.
- ViewModel은 Command(코맨드)라 불리는 콜백을 공개합니다. 이 콜백은 이벤트 핸들러(버튼 클릭이나 폼 제출 등)에 연결할 수 있습니다.
Command 패턴:
- Command는 Dart의 함수로 구현됩니다.
- View는 해당 구현을 알지 못한 채로 복잡한 로직을 실행할 수 있게 됩니다.
- Command는 ViewModel 클래스의 멤버로 작성되며, View 클래스 내의 제스처 핸들러에서 호출됩니다.
데이터 레이어
애플리케이션의 데이터 레이어는 비즈니스 데이터와 로직을 처리합니다.
데이터 레이어는 Services와 Repositories로 구성되며, 이들은 재사용성과 테스트 용이성을 높이기 위해 명확하게 정의된 입력과 출력을 가져야 합니다.
MVVM 용어를 사용한다면, Services와 Repositories는 Model 레이어를 구성합니다.
Repositories
Repository 클래스는 모델 데이터의 신뢰할 수 있는 단일 정보원(SSOT)입니다.
Repository는 서비스에서 데이터를 가져와 해당 생 데이터를 도메인 모델로 변환하는 역할을 합니다.
도메인 모델은 애플리케이션이 필요로 하는 데이터를 나타내며, ViewModel 클래스가 사용할 수 있는 형식으로 정리됩니다.
예를 들어, 소셜 미디어 애플리케이션에서는 UserProfileRepository 클래스가 Stream<UserProfile?>을 공개하여, 사용자가 로그인하거나 로그아웃할 때마다 새로운 값을 발행할 수 있습니다.
Repository가 출력한 도메인 모델은 ViewModels에서 소비됩니다.
- Repositories와 ViewModels는 다대다 관계를 가집니다.
- ViewModel은 필요한 데이터를 가져오기 위해 여러 Repository를 사용할 수 있습니다.
- 하나의 Repository는 여러 ViewModel에서 사용될 수 있습니다.
- Repositories는 서로를 인식해서는 안 됩니다. 애플리케이션 내에서 두 개의 Repository에서 데이터를 필요로 하는 비즈니스 로직이 있는 경우, 특히 Repository와 ViewModel 간의 관계가 복잡한 경우, 해당 데이터를 통합하는 처리는 ViewModel 또는 도메인 레이어에서 수행해야 합니다.
Services
Services는 애플리케이션의 최하위 레이어에 위치합니다.
이들은 API 엔드포인트를 래핑하고, 비동기 응답 객체(Future나 Stream 객체 등)를 공개합니다.
Services는 데이터 로드를 분리하기 위해 사용되며, 상태를 유지하지 않습니다.
애플리케이션에는 데이터 소스마다 하나의 Service 클래스가 있어야 합니다.
Services가 래핑할 수 있는 엔드포인트는 다음과 같습니다:
- iOS나 Android API 같은 기본 플랫폼
- REST endpoints
- 로컬 파일
일반적으로, 애플리케이션의 Dart 코드 외부에 데이터가 존재할 경우, Services는 특히 유용합니다.
위의 예는 모두 이 조건에 해당합니다.
Services와 Repositories는 다대다 관계를 가집니다.
- 하나의 Repository는 여러 Service를 사용할 수 있습니다.
- 하나의 Service는 여러 Repository에서 사용될 수 있습니다.
도메인 레이어 (옵션)
애플리케이션이 성장하고 기능이 추가됨에 따라, ViewModel에 과도한 복잡성을 추가하는 로직을 추상화해야 할 때가 있습니다.
이러한 클래스는 종종 Interactors나 Use-Cases라고 불립니다.
Use-Cases는 UI 레이어와 데이터 레이어 간의 상호작용을 단순하고 재사용 가능하게 만드는 역할을 합니다.
Repository에서 데이터를 가져와 이를 UI 레이어에 적합한 형식으로 변환합니다.
Use-Cases를 사용하는 조건:
- 여러 Repository에서 데이터를 통합해야 할 경우
- 로직이 매우 복잡한 경우
- 해당 로직이 여러 ViewModel에서 재사용되는 경우
도메인 레이어가 옵션인 이유:
모든 애플리케이션이나 애플리케이션 내의 모든 기능이 이 레이어를 필요로 하는 것은 아닙니다.
이 추가 레이어가 애플리케이션에 이점을 제공한다고 생각될 때, 다음의 장단점을 고려해야 합니다.
장점 | 단점 |
---|---|
✅ ViewModel 내의 코드 중복을 피할 수 있음 | ❌ 아키텍처의 복잡성이 증가하고 클래스 수가 늘어나 인지 부하가 높아짐 |
✅ 복잡한 비즈니스 로직을 UI 로직에서 분리하여 테스트 용이성 향상 | ❌ 테스트에 추가적인 Mock이 필요함 |
✅ ViewModel의 코드 가독성 향상 | ❌ 코드에 추가적인 보일러플레이트가 증가함 |
Use-Cases를 추가할지 여부는 상황에 따라 다릅니다.
만약 ViewModel이 대부분의 경우 Use-Case를 통해 데이터를 접근하게 된다면, 코드 리팩토링을 통해 Use-Case를 전면적으로 활용하는 아키텍처로 변경할 수 있습니다.
이 가이드에서는 몇 가지 기능에 Use-Case를 사용하지만, ViewModel이 직접 Repository와 상호작용하는 경우도 포함되어 있습니다.
3. 아키텍처 권장 사항과 리소스 (Architecture recommendations and resources)
이 가이드에서는 아키텍처의 베스트 프랙티스, 그 중요성, 그리고 Flutter 애플리케이션에 이를 채택할지 여부에 대한 권장 사항을 설명합니다. 이러한 권장 사항은 "권장"일 뿐, 절대적인 규칙은 아닙니다. 애플리케이션의 독특한 요구사항에 맞게 조정해야 합니다.
이 페이지에 나오는 베스트 프랙티스는 권장 우선순위가 있으며, Flutter 팀이 얼마나 강하게 권장하는지를 반영합니다.
강력히 권장 (Strongly recommend)
새로운 애플리케이션을 구축하기 시작할 때는 이 권장 사항을 항상 구현해야 합니다. 기존 애플리케이션에서도 현재의 접근 방식과 근본적으로 충돌하지 않는 한, 이 실천 사항을 구현하기 위한 리팩토링을 강력히 고려해야 합니다.
권장 (Recommend)
이 실천 사항은 애플리케이션을 개선하는 데 큰 도움이 될 가능성이 높습니다.
조건부 (Conditional)
이 실천 사항은 특정 상황에서 애플리케이션을 개선하는 데 도움이 될 수 있습니다.
책임의 분리 (Separation of Concerns)
애플리케이션을 UI 레이어와 데이터 레이어로 나누고, 각 레이어 내에서 로직을 책임별로 클래스에 분리해야 합니다.
권장 사항 | 설명 |
---|---|
명확히 정의된 데이터 레이어와 UI 레이어 사용 (강력히 권장) | 책임의 분리는 가장 중요한 아키텍처 원칙입니다. 데이터 레이어는 애플리케이션 데이터를 공개하고, 비즈니스 로직을 포함합니다. UI 레이어는 데이터를 표시하고, 사용자 이벤트를 받습니다. 각 레이어는 로직과 Widget을 분리하여 관리해야 합니다. |
데이터 레이어에 Repository 패턴 사용 (강력히 권장) | Repository 패턴은 데이터 접근 로직을 애플리케이션의 다른 부분과 분리하는 설계 방법입니다. Repository 클래스와 Service 클래스를 만들어 데이터 레이어에서 추상화를 실현합니다. |
UI 레이어에서 ViewModels과 Views 사용 (강력히 권장) | View와 ViewModel을 분리함으로써 Widget을 "간단한" 상태로 유지하고, 오류를 방지합니다 (MVVM 패턴). |
ChangeNotifier와 Listenables를 사용하여 Widget 업데이트 (조건부) | ChangeNotifier는 Widget이 ViewModel의 변화를 관찰하기 편리한 방법입니다. 상태 관리 선택지는 다양하며, 개인의 취향에 따라 달라질 수 있습니다. |
Widget에 로직 포함하지 않기 (강력히 권장) | 비즈니스 로직은 ViewModel의 메소드로 캡슐화해야 합니다. Widget에 포함해야 하는 로직은 단순한 조건 분기, 애니메이션, 레이아웃, 간단한 라우팅 로직뿐입니다. |
도메인 레이어 사용 (조건부) | 애플리케이션이 매우 복잡한 로직을 포함하거나, ViewModel에서 로직이 중복되는 경우 도메인 레이어를 추가합니다. 대규모 애플리케이션에서는 유용하지만, 대부분의 애플리케이션에서는 불필요한 오버헤드를 초래할 수 있습니다. |
데이터 처리 (Handling Data)
데이터를 신중하게 다룸으로써 코드가 이해하기 쉬워지고, 오류가 줄어들며, 잘못된 데이터 생성이 방지됩니다.
권장 사항 | 설명 |
---|---|
단방향 데이터 흐름 사용 (강력히 권장) | 데이터의 업데이트는 데이터 레이어에서 UI 레이어로만 흐라야 합니다. UI 레이어에서의 조작은 데이터 레이어에서 처리됩니다. |
사용자 조작의 이벤트 처리에 Command 사용 (권장) | Command는 렌더링 오류를 방지하고, UI 레이어에서 데이터 레이어로의 이벤트 전송을 표준화합니다. |
불변 데이터 모델 사용 (강력히 권장) | 불변의 데이터 모델은 데이터가 모델 내에서만 변경되도록 보장하는 데 중요합니다. |
freezed나 built_value를 사용하여 불변 데이터 모델 생성 (권장) | freezed나 built_value를 사용하면 JSON 직렬화/역직렬화, 딥 이퀄 체크, 복사 기능 등의 편리한 메소드를 생성할 수 있습니다. 단, 모델 수가 많을 경우 빌드 시간이 증가할 수 있습니다. |
API 모델과 도메인 모델 분리 (조건부) | 모델을 분리함으로써 ViewModel이나 Use-Case의 복잡성을 방지할 수 있지만, 중복성이 증가합니다. 대규모 애플리케이션에서는 유용합니다. |
애플리케이션 구조 (App Structure)
좋은 코드 구조는 애플리케이션의 건전성과 팀의 작업 효율성을 향상시킵니다.
권장 사항 | 설명 |
---|---|
의존성 주입 사용 (강력히 권장) | 의존성 주입을 통해 전역적으로 접근 가능한 객체를 없애고 오류를 줄입니다. provider 패키지를 추천합니다. |
go_router를 내비게이션에 사용 (권장) | go_router는 90%의 Flutter 애플리케이션에서 추천되는 내비게이션 방법입니다. 특정 유스케이스에서는 Navigator API나 다른 패키지를 고려할 수 있습니다. |
클래스, 파일, 디렉토리에 표준적인 명명 규칙 사용 (권장) | 클래스 이름에는 아키텍처 컴포넌트를 나타내는 이름을 사용하세요 (예: HomeViewModel, UserRepository). 혼란을 피하기 위해 Flutter SDK 객체와 혼동되는 이름은 피하세요. |
추상 Repository 클래스 사용 (강력히 권장) | Repository는 애플리케이션 데이터의 신뢰할 수 있는 정보원으로 작용합니다. 추상 Repository 클래스를 만들어 개발용이나 스테이징용 등 다양한 구현을 유연하게 전환할 수 있게 합니다. |
테스트 (Testing)
테스트는 애플리케이션의 유연성을 높이고, 새로운 로직이나 UI를 추가할 때의 리스크를 줄여줍니다.
권장 사항 | 설명 |
---|---|
아키텍처 컴포넌트를 개별 및 통합으로 테스트 (강력히 권장) | 각 Service, Repository, ViewModel 클래스의 유닛 테스트를 작성하고, 각각의 메소드 로직을 개별적으로 테스트하세요. Widget의 Widget 테스트도 실시하여 특히 라우팅이나 의존성 주입을 확인하세요. |
테스트용 펨(fake)을 만들고 이를 활용할 수 있는 코드 작성 (강력히 권장) | 테스트용 펨을 만들고, 입력과 출력에 중점을 둔 코드를 작성함으로써 모듈화된 경량의 함수와 클래스가 자연스럽게 생겨납니다. |
이 가이드는 Flutter 애플리케이션을 효율적이고 확장 가능하게 설계하는 데 필요한 주요 원칙과 실천 방법을 제공합니다.
각 레이어의 역할과 책임을 명확히 분리함으로써 코드의 유지보수성과 확장성을 높이고, 테스트 용이성을 향상시킬 수 있습니다.
'Flutter' 카테고리의 다른 글
Flutter 렌더링 방식 완벽 해부: Widget부터 픽셀까지, 핵심 원리 파헤치기 (1) | 2024.11.29 |
---|---|
Dart 소스 코드 실행 구조 완벽 분석: Flutter에서 Dart VM까지 (1) | 2024.11.29 |