피트니스 산업과 IT, 그리고 스타트업

IT

플러터, 앱 구조 가이드 학습하기

핏더스트리 2025. 4. 25. 18:18
728x90
반응형

플러터, 앱 구조 가이드 학습하기

Flutter를 공부하다 보면 어느 순간부터 UI 위젯을 넘어서 "아키텍처"에 대해 고민하게 돼요. 작은 앱에서는 모든 코드가 하나의 파일에 있어도 별문제 없지만, 기능이 늘어나고 팀 프로젝트가 되면 유지보수와 확장성에서 문제가 생기기 시작하거든요. 그래서 저처럼 Flutter를 처음 시작하고 나서 조금씩 실력을 쌓아가는 학습자들에게 공식 문서에서 제공하는 앱 아키텍처 가이드는 필수 참고서예요.

 

이번 글에서는 Flutter 공식 문서의 Guide to app architecture 내용을 바탕으로, Flutter 앱을 어떻게 구조화할 수 있는지, 어떤 원칙을 따르는 것이 좋을지에 대해 정리해 보려고 해요. 다소 복잡하게 느껴질 수 있는 구조도 실제 사례를 상상하면서 설명할게요. 지금 Flutter 앱 구조에 대해 막연한 고민을 하고 있는 분들이라면, 이 글이 방향을 잡는 데 도움이 될 수 있을 거예요.

 

구조의 핵심은 역할 분리(Separation of Concerns)

Flutter 공식 문서에서 가장 먼저 강조하는 건 역할 분리, 즉 Separation of Concerns예요. 앱을 만들다 보면 데이터, UI, 로직이 뒤섞이기 쉬운데, 이걸 명확히 구분해야 나중에 확장하기도 쉽고 테스트하기도 편해져요. Flutter 앱은 기본적으로 두 개의 큰 층으로 나눌 수 있어요: UI LayerData Layer예요.

 

UI Layer는 사용자와 직접 상호작용하는 부분으로, 우리가 흔히 만드는 화면, 버튼, 리스트 같은 것들이 여기에 해당돼요. 이 레이어는 다시 ViewViewModel로 나뉘어요.

  • View는 위젯 조합으로 화면을 그려주는 역할을 해요. 단, 여기에는 복잡한 로직이 들어가면 안 돼요. 단순한 조건문이나 레이아웃 계산 정도만 허용돼요.
  • ViewModel은 View가 보여줘야 할 상태를 정리해주고, 사용자 인터랙션에 따라 적절한 처리를 하죠. 예를 들어 버튼 클릭 시 어떤 함수를 실행할지 결정하거나, 여러 데이터 소스를 조합해서 UI에 보여줄 데이터를 만들어주는 역할을 해요.

이 둘은 보통 1:1로 짝을 이루며, 하나의 기능 단위를 담당해요. 예를 들어 로그인 기능이 있다면 LoginView와 LoginViewModel이 함께 구성돼요. 이런 구조는 흔히 MVVM(Model-View-ViewModel)이라고 불리는 아키텍처 패턴에서 온 거예요.

 

UI Layer는 "보여주기"와 "상호작용 반응"에 집중하고, 데이터는 ViewModel이 책임지도록 구조를 짜면 훨씬 명확하고 관리하기 쉬운 코드가 돼요.

플러터, 앱 구조 가이드 학습하기
https://docs.flutter.dev/app-architecture/guide

Data Layer, 앱의 ‘진짜 내용’을 책임지는 구조

앞서 살펴본 UI Layer가 화면과 상호작용을 담당한다면, Data Layer는 앱이 실제로 다루는 정보와 로직을 책임져요. 이 레이어는 사용자의 눈에 직접 보이지 않지만, 앱의 핵심 동작을 뒷받침하는 매우 중요한 부분이에요. Flutter 공식 문서에서는 이 레이어를 RepositoryService라는 두 구성요소로 설명하고 있어요.

 

먼저 Repository(레포지토리)는 말 그대로 앱 데이터의 중심, 즉 ‘진실의 원천’이에요. API를 통해 받아온 데이터, 캐시된 정보, 또는 로컬 저장소에서 읽어온 데이터를 가공해서 앱이 사용할 수 있는 형태로 제공해줘요. 예를 들어 UserProfileRepository는 로그인된 사용자의 프로필 정보를 가져오고, 이 데이터를 ViewModel이 받아 화면에 표시할 수 있도록 도와주는 거죠.

Repository는 다음과 같은 일을 담당해요.

  • 다양한 Service에서 데이터를 받아와 통합하기
  • 캐싱오류 처리
  • 새로고침, 재시도 로직
  • 외부 요청이 실패했을 때 대체 데이터 제공

이렇게 Repository가 다양한 책임을 떠맡고 있기 때문에, 코드의 재사용성과 테스트 가능성이 크게 향상돼요. 똑같은 데이터를 여러 화면에서 쓰더라도 Repository만 잘 만들어두면 중복 코드를 줄일 수 있고, 테스트할 때도 외부 API에 의존하지 않고 쉽게 테스트할 수 있어요.

 

그럼 Repository가 데이터를 어디서 가져오느냐? 바로 Service(서비스)를 통해서예요.
Service는 Flutter 앱에서 외부 세계와 연결되는 가장 바깥쪽 레이어예요.

  • REST API 요청
  • 로컬 파일 접근
  • 플랫폼 기능 (예: GPS, 카메라, 알림 등)

이런 모든 외부 데이터를 가져오는 역할을 하는 것이 바로 Service예요. 중요한 점은, Service는 상태를 가지지 않고 단순히 데이터를 요청하고 결과를 반환하는 역할만 한다는 거예요. 예를 들어 UserApiService는 사용자 정보 API를 호출하고, 그 결과를 UserRepository에 전달하는 식이에요.

 

정리하면, Service는 데이터를 "가져오기만" 하고, 그 데이터를 어떤 형태로 쓸지는 Repository가 결정해요. 이렇게 책임을 나누면 각 역할이 명확해지고, 코드를 관리하고 테스트하기 쉬워져요.

 

또 하나 중요한 포인트는 Repository와 Service는 서로 유기적으로 연결되면서도, 각자의 책임이 명확히 분리되어 있어야 한다는 점이에요. Repository는 여러 Service를 조합할 수 있고, 하나의 Service가 여러 Repository에서 재사용될 수도 있어요. 이 유연한 관계 덕분에 앱 구조가 커져도 복잡하지 않고 확장하기 쉬운 구조를 만들 수 있어요.

 

 

도메인 레이어: 복잡한 로직을 다루는 중간자

앱이 커지고 기능이 다양해지면, ViewModel에 너무 많은 로직이 몰리는 문제가 생길 수 있어요. 이럴 때 사용하는 게 도메인 레이어(Domain Layer)예요. 이 레이어는 UI와 데이터 사이에서 복잡한 비즈니스 로직을 중간에서 처리해주는 역할을 해요.

 

도메인 레이어에서는 주로 Use-case(또는 Interactor)라는 클래스를 정의해요. Use-case는 말 그대로 ‘특정 기능을 수행하는 하나의 명확한 작업 단위’를 의미해요. 예를 들어 "사용자 프로필 불러오기", "로그인 처리하기", "장바구니에 상품 추가하기" 같은 것들이 대표적인 Use-case죠.

 

Use-case가 필요한 상황은 다음과 같아요.

  • 여러 Repository에서 데이터를 가져와 조합해야 할 때
  • 로직이 너무 복잡해서 ViewModel 안에 두기 어려울 때
  • 동일한 로직이 여러 ViewModel에서 반복 사용될 때

도메인 레이어를 추가하면 코드의 재사용성, 테스트 가능성, 가독성이 높아지는 장점이 있어요. 반면, 클래스 수가 늘어나고 구조가 복잡해지는 단점도 있죠. 그래서 공식 문서에서도 말하듯 꼭 필요한 경우에만 도입하는 걸 추천하고 있어요. 처음부터 무조건 넣기보다는, ViewModel이 과도하게 복잡해질 때 자연스럽게 도입하는 방식이 현실적이에요.

 

예를 들어 ViewModel에서 두 개 이상의 Repository 데이터를 조합해 복잡한 조건으로 필터링하고 가공해야 한다면, 그 작업을 Use-case로 옮겨보는 거예요. ViewModel은 단순히 이 Use-case를 호출만 하면 되고, 그만큼 구조가 깔끔해져요.

 

플러터, 앱 구조 가이드 학습하기
https://docs.flutter.dev/app-architecture/guide

마치며...

Flutter 앱의 구조를 처음 접하면 다소 생소하고 복잡하게 느껴질 수 있어요. View, ViewModel, Repository, Service, Use-case… 이름도 많고, 서로 어떻게 연결되는지 감이 잘 안 올 수도 있죠. 하지만 구조의 본질은 단순해요. 각 컴포넌트가 자신의 역할에만 집중하도록 코드를 나누는 것, 바로 그게 Flutter 아키텍처 가이드가 말하고자 하는 핵심이에요.

 

처음에는 View와 ViewModel만 나누는 것부터 시작해보세요. 점점 프로젝트가 커지면 Repository를 도입하고, 필요하다면 Use-case로 복잡한 로직을 분리하면서 구조를 확장해 나가면 돼요. 중요한 건 이 아키텍처가 "정답"이 아니라 가이드라인이라는 점이에요. 팀의 상황이나 개인의 스타일에 맞게 유연하게 적용해도 괜찮아요.

 

Flutter 앱을 더 안정적이고, 테스트 가능하고, 유지보수하기 쉬운 구조로 만들고 싶다면, 이번 공식 문서 기반의 구조 가이드는 꼭 한 번 찬찬히 읽어보길 추천해요. 저 역시 이 가이드를 통해 Flutter 앱을 한층 더 깊이 있게 이해하고, 더 깔끔하게 만들 수 있게 되었거든요.

공부하는 여정 속에서 구조에 대한 고민은 피할 수 없는 단계예요. 그 고민에 이 글이 작은 이정표가 되길 바랄게요!

728x90
반응형