1. 들어가며

CaloLink iOS 앱의 초기 설계를 위한 아키텍처 선정 과정을 기록하는 것을 목표로 합니다.
여러 CS 개념을 코드로 구현하면서 공부하다보니 아키텍처의 설계가 프로젝트의 유지보수성, 확장성, 테스트 용이성에 핵심적인 요소가 됨을 알았습니다.
CaloLink는 사용자가 식품을 검색하고, 영양성분 및 가격 정보를 바탕으로 필터링 및 정렬하며, 최종적으로 구매 링크로 연결되는 흐름을 가집니다.
이는 단순한 정보 표시를 넘어 데이터를 필터링 및 정렬하고 외부 쇼핑몰과 연동하는 비즈니스 로직이 중요하게 작용하는 서비스임을 의미합니다.
따라서 iOS 개발에서 주로 사용되는 아키텍처 패턴들을 우리 프로젝트의 특성에 맞춰 비교해보고 가장 적합한 구조를 선택하려고 합니다.
2. 아키텍처 후보 분석
1. MVC (Model - View - Controller)
Apple의 전통적인 아키텍처 패턴으로 가장 기본적인 구조입니다.
- Model: 앱의 데이터와 비즈니스 로직을 담당.
- View: 사용자에게 보여지는 UI를 담당.
- Controller: Model과 View 사이의 중재자 역할. (UIKit의 UIViewController)
CaloLink에 적용해 본다면?
SearchViewController가 사용자의 입력을 받아 네트워크 통신(Model)을 요청하고 받아온 데이터를 가공하여 UITableView(View)에 표시하는 역할을 모두 수행하게 됩니다.
- 장점
- 구조가 단순하여 개발 속도가 빠르고 소규모 프로젝트나 학습용으로 적합하다.
- Apple이 기본적으로 제공하는 프레임워크 구조와 가장 유사하여 진입 장벽이 낮다.
- 단점
- 거대한 뷰 컨트롤러 (Massive View Controller)
- 프로젝트 규모가 커질수록 UIViewController가 네트워크 로직, 데이터 가공 로직, UI 업데이트 로직 등 너무 많은 책임을 떠안게 된다.
- CaloLink의 필터링, 정렬 기능이 모두 컨트롤러에 들어간다면 코드가 매우 복잡해지고 유지보수가 어려워진다.
- 테스트의 어려움
- View와 Controller가 강하게 결합되어 있어 비즈니스 로직만을 따로 떼어 테스트하기가 매우 어렵다.
2. MVVM (Model - View - ViewModel)
MVC의 "거대한 뷰 컨트롤러" 문제를 해결하기 위해 등장한 패턴입니다.
- Model: MVC의 Model과 동일
- View: 사용자에게 보여지는 UI와 사용자 입력을 담당. (UIViewController, UIView)
- ViewModel: View를 표현하기 위해 필요한 데이터와 상태를 가지며 비즈니스 로직을 수행.
- View는 ViewModel의 데이터를 바인딩(Binding)하여 화면을 갱신한다.
CaloLink에 적용해 본다면?
SearchViewController(View)는 오직 UI를 그리고 사용자 입력을 SearchViewModel에 전달하는 역할만 합니다.
SearchViewModel은 네트워크 통신을 요청하고 필터링/정렬 로직을 수행하며, 가공된 최종 결과(예: [Product])를 가지고 있습니다.
View는 이 결과를 관찰하다가 변경되면 화면을 업데이트합니다.
- 장점
- 관심사의 분리
- View와 비즈니스 로직이 분리되어 UIViewController의 부담이 크게 줄어든다.
- 테스트 용이성
- ViewModel은 UI에 의존하지 않으므로 비즈니스 로직을 독립적으로 테스트할 수 있다.
- 단점
- ViewModel의 비대화
- 앱이 복잡해지면 여전히 ViewModel이 네트워크 통신, 데이터베이스 접근, 비즈니스 로직 등 다양한 책임을 가지게 되어 또 다른 "거대한 ViewModel"이 탄생할 수 있다.
3. MVVM + Clean Architecture
MVVM을 기반으로 비즈니스 로직을 한 번 더 분리하여 의존성을 관리하는 패턴입니다.
- Presentation Layer (MVVM): 사용자에게 보여지는 영역. (View, ViewModel)
- Domain Layer (Use Case): 앱의 핵심 비즈니스 규칙을 담당. 다른 계층에 의존하지 않는 순수한 로직.
- Data Layer (Repository): 데이터의 출처(네트워크, 데이터베이스 등)를 숨기고, Domain 계층이 요청한 데이터를 제공하는 역할.
CaloLink에 적용해 본다면?
SearchViewController(View)는 사용자 입력을 SearchViewModel에 전달합니다.
SearchViewModel은 SearchProductsUseCase(Domain)를 실행시킵니다.
SearchProductsUseCase는 ProductRepository(Data)에게 "상품 목록을 가져와 줘"라고 요청합니다.
ProductRepository는 실제 API 통신을 통해 데이터를 가져와 UseCase에 전달합니다.
데이터는 UseCase -> ViewModel -> View 순서로 전달되어 화면에 표시됩니다.
- 장점
- 높은 테스트 용이성
- 각 계층이 명확히 분리되어 있어 특정 기능(Use Case)을 독립적으로 완벽하게 테스트할 수 있다.
- 유연성과 확장성
- 나중에 데이터 출처가 API에서 로컬 DB로 바뀌거나 새로운 API가 추가되어도 Repository만 수정하면 된다.
- ViewModel이나 View는 전혀 영향을 받지 않는다.
- 명확한 역할 분담
- 복잡한 비즈니스 로직(필터링, 정렬 등)을 UseCase가 전담하므로 ViewModel은 화면 표시를 위한 데이터 가공에만 집중할 수 있다.
- 단점
- 초기 설정이 복잡하고 다른 패턴에 비해 만들어야 할 파일(프로토콜, 객체 등)이 많다.
3. 최종 결정: MVVM + Clean Architecture
CaloLink는 "영양성분 필터링"과 "가격 비교"라는 핵심적인 비즈니스 로직을 가지고 있고, 이 로직은 앞으로 더 고도화될 가능성이 높다고 생각합니다.
MVC는 컨트롤러의 비대화 문제, MVVM은 ViewModel의 비대화 가능성으로 인해 장기적인 관점에서 프로젝트에 적합하지 않다고 판단했습니다.
따라서 초기 설정의 복잡함을 감수하더라도 유지보수, 테스트, 확장성 측면에서 가장 큰 이점을 제공하는 MVVM + Clean Architecture를 최종 아키텍처로 선정하기로 결정했습니다.
이 구조를 통해 우리는 각자의 역할을 명확히 하는 코드를 작성하고 변화에 유연하게 대처할 수 있는 안정적인 앱을 만들어나갈려고 합니다.