Learn to share,Share to learn
유니페스의 구조는 어떻게 세웠을까?(3) 본문
개발자라면 언제나 잘 만든 구조에 대해 고민하게된다. 그럼 이때 말하는 잘만든 구조는 무엇을 말할까?
다양한 대답이 있을수 있지만, 보편적으로 나오는것 3가지를 뽑자면 다음과 같을것이다:
- 유지보수성
- 확장성
- 재사용성
사이드 프로젝트는 처음부터 하나하나 쌓아올릴수 있기에 효율적으로 구조를 짜고 작성할수 있었다.
우리 유니페스트는 드로이드 나이츠와 나우 인 안드로이드를 참고해 다음과 같은 구조를 세웠다:
구글 권장 아키텍쳐, MVI 패턴, 빌드 로직, 멀티 모듈
보통 기술적으로 협업을 한다 했을시 클린아키텍쳐와 MVVM을 사용하는것이 일반적이다. 그렇다면 왜 유니페스는 이러한 방식으로 작성했을까? 우리 프로젝트의 주요 구조별 포인트들을 하나하나 알아가보자.
1. 아키텍쳐
아키텍처는 구글 권장 아키텍처를 사용했다. 나우 인 안드로이드를 주로 참고해 프로젝트를 진행했는데, 간단히 정리해보자면 클린 아키텍처와는 다음과 같은 차이가 있다.
클린 아키텍처
의존성 방향)
data ← domain → presentation
repository interface - domain
repository impl - data
구글 권장 아키텍처
의존성 방향)
data → domain(선택) → presentation
repository interface, impl 모두 data
구글 권장 아키텍쳐는 클린 아키텍쳐에 비교해, 크게 2가지의 차이가 있다. 첫번째로, 도메인이 옵셔널하다. UI와 데이터 레이어 간의 상호작용을 간소화하고 재사용하기위해 레이어를 추가할 수 있지만, 그렇지않고 단순히 레포지토리에서 유즈케이스로 포워딩만 하는등의 경우는 사용하지 않는것이 좋다. 코드에는 항상 이유가 있어야하며 명확한 이유가 없는경우 (예: a레포지토리와 b레포지토리를 조합하여 새로운 함수를 만드는등) 그 구조는 오히려 흠이 된다.
두번째로, 의존성 방향이 다르다. 클린 아키텍처는 도메인에 데이터와 프레젠테이션에 의존성이 있다면, 구글 권장 아키텍처는 프레젠테이션이 데이터에 의존성을 가진다.
사실 클린아키텍쳐랑 구글 권장아키텍쳐랑 크게 다를건 없다. 도메인이 옵션일뿐인데, 이부분은 클린아키텍처라지만 도메인을 생략하면 같다고 볼수 있으니. 또 컴포즈와 함께 ui는 uistate를 사용하고 있으면, 데이터 흐름측에서도 이미 사실상 사용하는부분에서 큰 차이가 없다. Now in Android를 보면 또 많이 다르지만 처음 접한다해서 걱정하지 말고 한번 시도해보자.
2. 디자인 패턴
https://velog.io/@vov3616/MVVM-MVC-MVP-MVI-%EB%AD%90%EA%B0%80-%EB%8B%A4%EB%A5%BC%EA%B9%8C#3-mvvm
소프트웨어 아키텍처 패턴 -MVVM, MVC, MVP, MVI 뭐가 다를까?
면접은 개발자에게 필수인것 같습니다. 알고 있다고 생각했는데 질문하면 막상 모른다고 생각하는 얘기가 많으니까요. 오늘 할얘기는 MVVM, MVC, MVP에 대해서 설명해 드리겠습니다. 1. MVC 패턴
velog.io
유니페스에서는 MVVM 대신 MVI 패턴을 채택하였다. MVVM에서 MVI가 나오게 된 계기에는, 복잡한 상태 관리를 단순화, 그리고 컴포즈의 등장으로 구현이 용이해진 점이 크다. 만약 처음 접하는 패턴이라면 위의 블로그글을 참고해보자. MVI 패턴은 단방향 데이터 흐름을 강조하며 다음과 같이 작동한다
MVI 패턴의 데이터 흐름
- 사용자 이벤트: 사용자가 버튼을 클릭하거나, 텍스트를 입력하는 등의 이벤트가 발생함
- Intent 생성: View는 사용자 이벤트를 Intent로 변환한다. Intent는 사용자 의도를 명시적으로 표현한다.
- Intent 처리: Intent는 비즈니스 로직을 통해 처리되는 과정에서 Model의 상태(state)가 변경된다
- 상태 변경: Model의 상태가 변경되면, 새로운 상태가 View로 전달된다
- UI 업데이트: View는 새로운 상태를 기반으로 UI를 업데이트하여 사용자에게 변경 사항을 반영한다.
사실 기존의 경우 익숙한 MVVM으로 개발을 진행하던중, 점점 상태관리가 비대해지는것을 느끼고 리팩토링을 하게 되었다. 예시를 통해 알아보자:
각 화면을 구현하면서, 우리팀은 점점 더 많은 State 와 콜백 이벤트가 컴포저블 함수에 쌓여 지치기 시작했다.
게다가 특정 바텀시트가 Home, Map, Menu 세 화면에 들어가면서, 사용하는 state 및 콜백 이벤트도 Screen 에 중복하여 계속 쌓여, 각 컴포저블 함수의 가독성을 저하시키곤 하였다.
이를 해결하기 위해, MVI 패턴을 도입하게 되었다.
각 화면내에서 발생할 수 있는 사용자의 모든 intent /action 을 각각의 sealed interface 로 관리함으로써, 모든 컴포저블에 전달해줘야했던 콜백들을 onAction 이라는 콜백 하나로 대체해주었다.
이를 통해 우리 팀은
- 단방향 데이터 흐름
상태와 이벤트의 단방향 흐름을 통해 데이터 흐름을 명확하고 예측 가능하게 만들었다. 이는 디버깅과 유지보수를 용이하게 해주었다(가독성도 올려주었다) - 명확한 상태 관리
모든 상태 변화를 단일 상태 객체로 관리하여, 상태 변화를 추적하고 복원하는 것이 용이하게 되었다. 이는 복잡한 UI 상태 관리에 특히 효과적이었다.
추가로 참고해볼만한 글의 링크도 하나 첨부한다.
https://www.reddit.com/r/androiddev/comments/ybd4x3/the_main_difference_between_mvvm_and_mvi_is/
3. 빌드로직
안드로이드 앱 개발에서는 여러 개의 모듈(기능 단위)로 프로젝트가 구성된다. 이때 각 모듈마다 필요한 설정이 있는데, 이 설정이 반복되면 관리하기 어렵다는 단점이 있는데, 이를 해결하기 위해 빌드 로직(build-logic)과 컨벤션 플러그인(convention plugins)을 사용하였다.
이때 빌드로직이란 프로젝트 전반에 공통으로 적용할 설정들을 모아둔, 한 곳에서 모든 공통 설정을 관리하여 중복을 줄이고, 유지 보수를 쉽게 하는 폴더이다. 컨벤션 플러그인은, 자주 사용되는 설정들을 플러그인 형태로 만들어 두는 것이며, 이를 통해 각 모듈이 필요한 설정을 쉽게 가져다 쓸 수 있어 유용하다. 기존 방식에 비해, 일단 build.gradle방식이 획기적으로 깔끔하게 작성되었고, 공통설정을 한곳에서 관리하기 때문에 중복 코드를 피하고, 유지보수가 매우 용이했다. 이부분은 Now in Android에 자세히 볼수 있는데, 해당부분을 참고하여 작성하였다.
4. 멀티모듈
멀티모듈구조는 다음과 같은 이유로 도입하게 되었다:
- 물리적으로 서로간에 참조를 못하게 레이어들을 분리하기위함(관심사 분리)
- 증분빌드의 이점을 얻기 위해 (변경사항이 존재하는 모듈만 다시 빌드되도록, 나머지 변경되지 않은 모듈들은 다시 빌드x) → 빌드 시간의 이점을 얻을 수 있음
그럼 유니페스트의 모듈구조는 어떻게 구성되는지 함께 알아보자:
- Now in Android 기반
- build-logic (gradle 관련), version catalog
- :core
- :ui : 공통 component, modifier, (graphic) TopAppBar(ScreenA, B, C), SearchTextField
- :design-system : Color, Theme, Font, Component(Button, LoadingWheel, dialog…)
- :network (네트워크 통신 관련 모듈)
- :datastore, database (로컬DB 관련 모듈 )
- :common (공통으로 쓰이는 class, function, ex) BaseViewModel, extension functions) 공통으로 쓰이는데 UI 관련된게 아닌것들(잡동사니들)
- :domain(옵셔널)
→ UseCase 는 단순히 Repository 함수를 포워딩만 하는 경우엔 사용하지 않기!
→ UseCase( a repository 와 b repository 를 조합하여 새로운 함수를 창조하는 경우 유의)
라고 정해, 도메인 모듈은 생략하였다. - :data
- :model
- :feature
- :home
- :festival
- :booth
- :intro
- :liked-booth
- :map
- :setting
- :waiting
- :menu
까지의 화면단위로 모듈을 나눴고. 그 이외의 모듈은 다음과 같다. - :navigator(네비게이션을 정의해주는 모듈이다. 우리앱의 경우 Single Activity가 아닌 스플래시, 인트로, 메인 이 3가지의 액티비티로 구성되어있다. 사실 앱이 Single Activity로 구성되는것이 권장된다는 말도 어디선가 나와서 어디론가 들어가긴하는데, 딱히 그런것은 아니다. 액티비티를 여러개 쓰는게 잘못된 방식은 아니고, 오히려 navigation start destination을 동적으로 바꾸는게 더 복잡해 멀티 액티비티를 사용한다. 이러한 부분의 네비게이션을 정의하기 위해 필요한 모듈이다)
- :splash(스플래시 모듈이다. 앱이 첫 시작시 여기서 시작되고, 이후 사용자가 온보딩 관련작업이 끝났는지를 체크해 Intro화면으로 갈지 main화면으로 갈지 정한다)
- :main(메인모듈을 통해 바텀탭과 네비게이션을 구현하여 각 화면으로 이동한다)
리팩토링을 진행하며 부족한 부분에 대해 생각하고, 더 응집성있지만 잘 분리된 코드에 대해 고민하며 코드를 작성했다. 무엇보다 중요한것은 구조는 잘 짜되 그것이 개발에 도움되는 방향일것이란 점이다. 개발자는 설계를 통해 이상향을 쫓지만, 그것이 신기루가 아닌지 잘 생각하며 현재 우리팀에 맞는 구조를 세우는것이 가장 중요하다. 언제나 생각하지만, 이 모든건 우리 개발자를 위한것이지, 코드를 위한것이 아니기 때문이다.
'유니페스' 카테고리의 다른 글
유니페스는 어떻게 협업했을까?(2) (0) | 2024.06.27 |
---|---|
대학 축제 지도를 펼쳐라! 유니페스 소개(1) (0) | 2024.06.03 |