뚝딱뚝딱 모바일

[Flutter] 아키텍처에 대한 고민 (1) - 처음 해보는 Flutter 본문

실무 이야기

[Flutter] 아키텍처에 대한 고민 (1) - 처음 해보는 Flutter

규석 2023. 12. 7. 16:52

안녕하세요!

이번에 적으려고 하는 내용은 제가 Flutter 개발을 시작하면서, 어떤 아키텍처들을 구성하고, 고민했는지에 대한 내용을 적어보려고 합니다.

한 3편 정도의 글이 나오지 않을까 생각하고 있습니다. 여유되시는 분들은 천천히 읽어주시고, 의견 나눠주시면 감사하겠습니다.


Flutter? 해보겠습니다.

전 처음부터 Flutter를 공부하고, 이를 목표로 삼았던 사람이 아니었습니다. 학생 때는 Android 개발을, 취업 후에는 iOS 개발을 2년 정도 하다 이직을 하게 되었는데, 이직한 회사에서 전 여러 플랫폼을 커버하여야 했고, 이를 위해 Flutter를 시작하게 되었습니다.

다행히 관심이 좀 있었고, SwiftUI로 선언형 UI에 대해 미리 접했었기 때문에, Flutter와의 첫 만남은 어렵지 않았습니다. 친구의 친구를 만나는 느낌으로 첫 발을 내디뎠습니다. 이 어색하지만 내적 친분 있는 Flutter에 대해 공부를 어느 정도 하고, 전 첫 프로젝트를 시작하게 되었습니다.

첫 프로젝트, 이게 정말 맞는걸까?

제가 처음 만들게 된 프로그램은 회사 내부에서 사용할 ERP 데스크톱 앱이었습니다. 개발자가 많았다면 윈도우, 맥OS 각각 따로따로 만드는 것이 좋았겠지만, 그렇지 않았기에, Flutter로 진행해 보자는 의견이 나와하게 되었습니다.

코드를 짜기 앞서, 제일 먼저 찾아본 것은 상태관리 라이브러리였습니다. 여러 후보군들이 있었지만, 러닝커브가 낮고 빠르게 활용이 가능할 거 같은 Riverpod으로 선택하였습니다.

당시 슬랙에 정리해둔 내용

하지만 저 혼자만 Flutter와 친하다고 생각했었던 것 같습니다. 상태관리 라이브러리만 생각했지, 아키텍처와 디자인 패턴에 대해선 무지했기에 UI와 로직이 분리가 되어있지 않은, 전형적인 Massive View Controller 형태가 되었습니다. 비동기 코드를 맞추기 위해 덕지덕지 붙어있는 closure들과, 어디가 어떤 기능을 하는지 한눈에 알아볼 수 없는 UI 코드들... 이 코드로 짜인 ERP가 회사에서 잘 사용되었다는 것들이 신기할 따름이었습니다.

고민을 해보자 고민을

ERP 제작을 어느 정도 끝내고 나니 여유가 생겨서 아키텍처와 디자인 패턴에 대해 고민하게 되었습니다. 가장 먼저 든 생각은 iOS 개발을 할 때 적용했었던, ReactorKit이였습니다. ReactorKit에 대해 간략하게 얘기를 하자면, View에서 Action을 방출, 그 Action을 토대로 Logic을 처리할 Mutate를 거친 후, 이 Mutate에서 State를 변경, 변경된 State를 View에서 적용하는 단방향 패턴의 아키텍처입니다. 사용해 봤을 때 개발 경험이 좋았고, 코드가 깔끔했기에 Flutter에서도 RxDart를 활용해 MVVM패턴을 구성하면 가능하지 않을까 하며 흐름도를 만들었습니다.

혼자 쓱쓱 날리며 적다보니...

Dart는 Class안의 Class라든가, Struct (구조체)가 없기에, Input과 Output은 mixin을 이용하여 만들어, 이 안에 알맞은 Stream들을 넣어주었습니다. 또한 BaseView와 BaseViewModel 두 개의 부모 클래스를 만들어 구조를 획일화하였습니다. State는 똑같이 Riverpod을 이용해 관리해 주었습니다.

BaseView

abstract class BaseView<T extends ConsumerStatefulWidget, J extends BaseViewModel> extends ConsumerState<T> {
  late final J viewModel;
  CompositeSubscription subscription = CompositeSubscription();

  @override
  void initState() {
    super.initState();
    bindOutput();
  }

  void bindOutput();

  @override
  void dispose() {
    subscription.dispose();
    viewModel.dispose();
    super.dispose();
  }
}

BaseViewModel

abstract class BaseViewModel {
  CompositeSubscription subscription = CompositeSubscription();

  BaseViewModel() {
    bindInput();
  }

  void bindInput();

  void dispose() {
    subscription.dispose();
  }
}

BaseView에는 bindOutput 함수를, ViewModel에는 bindInput 함수를 몸체가 없는 추상 함수로 만들어서 Input과 Output을 적을 함수를 만들어주었고, CompositeSubscription 변수를 만들고 Stream을 dispose 해주었습니다.

이렇게 하면, Flutter에서 유사 ReactorKit을 만들 수 있지 않을까?!라는 기대함과 함께 리팩토링을 준비하였습니다.

꽤 괜찮잖아...?!

이런 기대감을 가지면 반대의 결과가 나오는 것이 대부분인데, 다행히도 꽤 괜찮은 개발 경험을 받았습니다. 제가 이를 사용함으로써 얻길 원했던 단방향적인 Flow와 View와 Logic의 분리를 얻게 되었습니다. 당연히, 이 패턴이 정답은 아니고 만능도 아닙니다. 제 상황에서는 정말 잘 맞아떨어졌기에, 저는 이를 이용하여 ERP 프로그램을 리팩토링하였고, 이후 개발하게 될 앱 프로젝트에서도 적용하게 되었습니다. 어떤 문제가 생길지 모르고...

 

다음 글

 

[Flutter] 아키텍처에 대한 고민 (2) - 익숙해지니 보이는 것들

[Flutter] 아키텍처에 대한 고민 (1) - 처음 해보는 Flutter 안녕하세요! 이번에 적으려고 하는 내용은 제가 Flutter 개발을 시작하면서, 어떤 아키텍처들을 구성하고, 고민했는지에 대한 내용을 적어보

nkstar-ios.tistory.com