프로젝트에서 기술 스택을 선정할 때 왜 이 기술을 선택했는지, 그리고 어떤 논의 과정을 거쳤는지를 이야기하고자 합니다.
개발 기간이 길지 않았기 때문에, 자연스럽게 개발 속도를 고려할 수밖에 없었습니다.
동시에 사이드 프로젝트인 만큼 새로운 기술을 도전적으로 적용해보는 것도 중요하다고 생각했습니다.
이 두 가지가 상충되는 지점이 있었고, 그 사이에서 밸런스를 잡으려고 노력을 했습니다.
결론적으로 SwiftUI + TCA로 개발하게 되었는데 하나씩 살펴보도록 하겠습니다.
우선 동아리에서 진행하는 프로젝트라 기간이 정해져 있었고 그 기간이 길지는 않았습니다.
그래서 UI 구현 속도가 프로젝트 완성에 중요한 요소중에 하나였고
SwiftUI가 UIKit보다 UI생산성에 긍정적인 영향을 줄 것이라고 예상하여 선택하게 되었습니다.
왜냐하면 SwiftUI는 선언형 UI 프레임워크로, UIKit에 비해 적은양의 코드로 직관적인 UI 구성이 가능하기 때문입니다.
그래서 상대적으로 간결한 코드로 UI를 개발할 수 있다고 생각했습니다.
또 하나는 팀원 전원이 SwiftUI 경험이 있다는 점이었습니다.
만약 SwiftUI 경험이 부족한 팀원이 있었다면 UIKit을 선택했을지도 모르겠지만
러닝 커브 없이 바로 개발할 수 있는 상황이었고, 프로젝트 특성상 SwiftUI가 더 적합하다고 판단했습니다.
이번 프로젝트에서는 아키텍처 선택 과정에서 MVVM과 MVI를 고려했지만,
최종적으로 TCA(The Composable Architecture)를 도입하기로 했습니다.
몇몇 팀원에게는 새로운 도전이었지만, 사이드 프로젝트인 만큼 새로운 기술을 도전하고 학습하는 과정도 중요하다고 생각했습니다.
MVVM에서 ViewModel은 View에 상태를 바인딩해 UI에 반응형으로 반영하는 역할을 하기 때문에,
UIKit을 쓸 때는 Rx나 Combine을 활용해 ViewModel의 상태를 구독하고, 변화에 따라 UI를 업데이트하는 방식이 익숙했습니다.
SwiftUI에서 MVVM을 사용한다면 가장 어색했던 점은
SwiftUI 자체가 View + ViewModel의 성격을 모두 가진 프레임워크처럼 동작한다는 점이었습니다.
View 안에서 @State, @Binding, @StateObject, @ObservedObject 같은 PropertyWrapper를 통해
상태를 선언하고 관리할 수 있기 때문에, 기존 ViewModel이 맡았던 역할 중 상당 부분이 SwiftUI의 View에 흡수된 느낌이 었습니다.
작고 단순한 View라면 SwiftUI에서 @State를 활용해 View 내부에서 상태를 직접 관리해도 무방하다고 생각합니다.
SwiftUI는 선언적 UI 프레임워크로, View와 상태가 매우 밀접하게 연결되어 있어서
View를 단독으로 사용하거나 View+Model로 사용해 ViewModel 없이도 구조가 간단해지죠.
하지만 앱이 커지고, 여러 View 간에 데이터를 공유하거나 비즈니스 로직이 점점 복잡해지기 시작하면,
View가 감당해야 할 상태와 로직이 많아지고 결국 View가 비대해지는 구조적 문제에 직면하게 됩니다.
이 시점에서 우리는 자연스럽게 "상태를 분리하고 싶다"는 필요를 느끼게 됩니다.
그래서 상태를 관리하는 ViewModel 혹은 이와 유사한 상태를 관리하는 패턴이 필요하게 됩니다.
애플이 SwiftUI 세션에서 보여주는 Data Flow 예시가 단방향 데이터 플로우를 권장하고 소개하고 있습니다.
아래 그림을 보면 Action -> State -> View 구조가 상태 관리 기반의 단방향 구조라는 것을 볼 수 있습니다.
그래서 선언형 UI에 환경에 어울리는 상태관리 기반의 단방향 데이터 플로우인 MVI와 TCA를 고민하게 되었습니다.
MVI와 TCA 둘 다 상태 기반의 단방향 데이터 흐름을 가지고 있습니다.
MVI 같은 경우에는 TCA에 비해서 러닝커브가 높지 않지고
특히 규모가 작고 복잡하지 않은 화면에서는 구현이 빠르고 명확합니다.
하지만 깊은 뷰 계층에서의 관리 복잡성이 있습니다.
예를 들면 MVI에서 하위 MVI를 가지고 그 하위가 또 하위 MVI를 가지는 복잡한 구조가 형성된다면
서로의 이벤트 전달 과정을 정의하기가 복잡(상위에서 하위, 하위에서 상위 이벤트 전달)하고 결합되기 쉬운 구조가 됩니다.
개인적으로 이부분을 위해서 RIBs의 Dependency 주입 방식을 참고하여 사용한 경험이 있는데요
결과적으로 복잡도와 유지보수 비용을 완전히 피하기는 어려웠습니다.
반면 TCA는 기능 단위로 Reducer를 나눠 관리할 수 있고,
이 Reducer들을 조합하여 서로 간에 액션을 전달하는 방식이 구조적으로 잘 정리되어 있기 때문에
기능별로 Reducer를 나누고 조합하여 확장성과 유지보수 측면에서 장점을 가져가며
위에서 말한 MVI의 단점을 해소할 수 있다고 생각했습니다.
다만 러닝커브가 있는 편이고, 라이브러리를 사용해야지만 적용할 수 있는 단점도 존재합니다.
이번 프로젝트는 사실 복잡한 화면이나 깊은 구조가 필요한 건 아니었습니다.
그래서 이 정도 규모에 실제 프로젝트였다면, MVI를 선택할 가능성이 더 높았을 것 같습니다.
그럼에도 TCA를 선택한 이유는, Reducer를 분리하고 조합하는 구조를 직접 경험해보고 싶었기 때문에 TCA를 선택했습니다.
저희 팀은 프로젝트를 단순히 런칭하는 데 그치지 않고,
장기적으로 운영하며 유지보수가 용이한 구조를 만드는 것을 목표로 했습니다.
또한, 여러 명의 개발자가 협업하는 환경에서 충돌을 줄이고,
각 기능의 책임을 분리하여 유연한 확장이 가능하도록 설계하는 것이 중요했습니다.
정해진 기간 내에 안정적으로 런칭하기 위해서는 팀원들이 익숙하고,
동시에 위 요구사항들을 만족할 수 있는 아키텍처를 선택하는 것이 필요했습니다.
결과적으로 팀 전체가 더 익숙하고 확장성·유지보수 측면에서 적합한 Clean Architecture를 채택했습니다.
추가로, 프로젝트를 기능 단위로 나누어 관리하고, 각 모듈 간의 의존성을 명확히 하기 위해
Tuist를 활용한 모듈화 구조도 함께 도입했습니다.
이 조합은 개발 및 협업 효율성은 물론, 향후 기능 확장과 리팩토링에서도 유리한 구조를 제공할 수 있다고 판단했습니다.
저희 팀이 Tuist를 활용하여 설계한 Clean Architecture 기반 모듈화 그래프입니다.
각 Feature를 수평으로 생성하여 분리함으로써 각 Feature를 독립적으로 유지하며
전체적인 구조는 CleanArchitecture의 흐름에 따라 모듈화 하였습니다.
Service
Feature
Core
DesignKit, VideoKit
Data
Networker
Domain
Shared
Static과 Dynamic Framework의 간단한 차이점을 짚고 넘어가겠습니다.
Static
Dynamic
몇 개 이상 모듈을 의존하는 경우 dynamic, static 중 하나를 선택해야되나에 대한 고민이 있었습니다.
결론은 정확한 개수(2개 이상이면 무조건 Dynamic Framework) 보다
조금 더 유연한 방법으로 판단하기 위해서 아래와 같은 3가지 규칙을 만들고 Framework를 선택하게 되었습니다.
| 조건 | Dynamic | Static |
|---|---|---|
| 여러 모듈에서 참조하는 전역적인 성질을 띄는 모듈인가? | ✅ | ❌ |
| 여러 모듈에서 참조하지 않고 독립적인 모듈인가? | ❌ | ✅ |
| 리소스를 포함하는 모듈인가? | ✅ | ❌ |
Feature간의 의존성 관리 방식은 Feature들을 수평으로
같은 Layer의 위치 시킴으로써 각 Feature 간의 독립성을 유지 시킴으로써
각자의 작업에 영향을 최소화 하였습니다.
저희 팀은 총 4명의 iOS 개발자로 구성되어 있어, 규모가 작지는 않았습니다.
그만큼 여러 명이 동시에 각자의 기능을 개발하면서도 충돌 없이 독립적인 개발 환경을 유지하는 것이 중요했습니다.
그래서 각 기능(Feature)을 완전히 독립적인 단위(MicroFeature)로 분리하고,
Demo, Test, Interface 등을 함께 갖춘 구조로 설계하였습니다.
이 구조는 각 Feature 별로 독립적으로 앱을 빌드하고 테스트할 수 있으며,
각 Feature간에 결합도를 줄이며 인터페이스 기반의 의존성 주입을 통해서 유연하게 통합되는 장점이 있기 때문에
MicroFeature를 적용하게 되었습니다.
각 모듈들의 역할을 간단하게 알아보겠습니다.
Demo
Feature
Interface
Test
Testing
짧은 시간 안에 프로젝트를 안정적으로 런칭함과 동시에, 새로운 기술에도 도전해보기 위해
많은 논의와 고민을 거쳤고, 그 여정을 부족하나마 글로 정리해보았습니다.
무엇보다도 능력 있는 개발자 분들과 함께한 시간은 정말 값진 경험이었고,
개발 외에도 팀워크와 소통 면에서 많은 것을 배울 수 있던 의미 있는 여정이었습니다.
iOS 팀은 물론, 서버와 디자인팀 모두 정말 고생 많으셨고
함께 작업할 수 있어 정말 행복했습니다. ❤️❤️❤️
글을 읽는데 이해가 잘 되서 너무 재밌게 잘 읽었습니다. 간단명료하게 설명해주셔서 너무 좋아요