[디프만 16기] 기술스택 선정 및 프로젝트 설계 과정

Jayven·2025년 4월 6일
post-thumbnail

기술스택 선정 과정


프로젝트에서 기술 스택을 선정할 때 왜 이 기술을 선택했는지, 그리고 어떤 논의 과정을 거쳤는지를 이야기하고자 합니다.
개발 기간이 길지 않았기 때문에, 자연스럽게 개발 속도를 고려할 수밖에 없었습니다.
동시에 사이드 프로젝트인 만큼 새로운 기술을 도전적으로 적용해보는 것도 중요하다고 생각했습니다.
이 두 가지가 상충되는 지점이 있었고, 그 사이에서 밸런스를 잡으려고 노력을 했습니다.

결론적으로 SwiftUI + TCA로 개발하게 되었는데 하나씩 살펴보도록 하겠습니다.


UIKit보다 SwiftUI가 더 적합했던 이유

우선 동아리에서 진행하는 프로젝트라 기간이 정해져 있었고 그 기간이 길지는 않았습니다.
그래서 UI 구현 속도가 프로젝트 완성에 중요한 요소중에 하나였고
SwiftUI가 UIKit보다 UI생산성에 긍정적인 영향을 줄 것이라고 예상하여 선택하게 되었습니다.

왜냐하면 SwiftUI는 선언형 UI 프레임워크로, UIKit에 비해 적은양의 코드로 직관적인 UI 구성이 가능하기 때문입니다.
그래서 상대적으로 간결한 코드로 UI를 개발할 수 있다고 생각했습니다.

또 하나는 팀원 전원이 SwiftUI 경험이 있다는 점이었습니다.
만약 SwiftUI 경험이 부족한 팀원이 있었다면 UIKit을 선택했을지도 모르겠지만
러닝 커브 없이 바로 개발할 수 있는 상황이었고, 프로젝트 특성상 SwiftUI가 더 적합하다고 판단했습니다.


MVVM, MVI가 아닌 TCA를 선택한 이유

이번 프로젝트에서는 아키텍처 선택 과정에서 MVVM과 MVI를 고려했지만,
최종적으로 TCA(The Composable Architecture)를 도입하기로 했습니다.
몇몇 팀원에게는 새로운 도전이었지만, 사이드 프로젝트인 만큼 새로운 기술을 도전하고 학습하는 과정도 중요하다고 생각했습니다.


MVVM을 채택하지 않은 이유

MVVM에서 ViewModel은 View에 상태를 바인딩해 UI에 반응형으로 반영하는 역할을 하기 때문에,
UIKit을 쓸 때는 Rx나 Combine을 활용해 ViewModel의 상태를 구독하고, 변화에 따라 UI를 업데이트하는 방식이 익숙했습니다.

SwiftUI에서 MVVM을 사용한다면 가장 어색했던 점은
SwiftUI 자체가 View + ViewModel의 성격을 모두 가진 프레임워크처럼 동작한다는 점이었습니다.

View 안에서 @State, @Binding, @StateObject, @ObservedObject 같은 PropertyWrapper를 통해
상태를 선언하고 관리할 수 있기 때문에, 기존 ViewModel이 맡았던 역할 중 상당 부분이 SwiftUI의 View에 흡수된 느낌이 었습니다.


SwiftUI에서 ViewModel은 아예 필요없나?

작고 단순한 View라면 SwiftUI에서 @State를 활용해 View 내부에서 상태를 직접 관리해도 무방하다고 생각합니다.
SwiftUI는 선언적 UI 프레임워크로, View와 상태가 매우 밀접하게 연결되어 있어서
View를 단독으로 사용하거나 View+Model로 사용해 ViewModel 없이도 구조가 간단해지죠.

하지만 앱이 커지고, 여러 View 간에 데이터를 공유하거나 비즈니스 로직이 점점 복잡해지기 시작하면,
View가 감당해야 할 상태와 로직이 많아지고 결국 View가 비대해지는 구조적 문제에 직면하게 됩니다.
이 시점에서 우리는 자연스럽게 "상태를 분리하고 싶다"는 필요를 느끼게 됩니다.
그래서 상태를 관리하는 ViewModel 혹은 이와 유사한 상태를 관리하는 패턴이 필요하게 됩니다.


Unidirectional Data Flow

애플이 SwiftUI 세션에서 보여주는 Data Flow 예시가 단방향 데이터 플로우를 권장하고 소개하고 있습니다.
아래 그림을 보면 Action -> State -> View 구조가 상태 관리 기반의 단방향 구조라는 것을 볼 수 있습니다.

그래서 선언형 UI에 환경에 어울리는 상태관리 기반의 단방향 데이터 플로우인 MVI와 TCA를 고민하게 되었습니다.


MVI와 TCA

MVI와 TCA 둘 다 상태 기반의 단방향 데이터 흐름을 가지고 있습니다.

MVI 같은 경우에는 TCA에 비해서 러닝커브가 높지 않지고
특히 규모가 작고 복잡하지 않은 화면에서는 구현이 빠르고 명확합니다.
하지만 깊은 뷰 계층에서의 관리 복잡성이 있습니다.

예를 들면 MVI에서 하위 MVI를 가지고 그 하위가 또 하위 MVI를 가지는 복잡한 구조가 형성된다면
서로의 이벤트 전달 과정을 정의하기가 복잡(상위에서 하위, 하위에서 상위 이벤트 전달)하고 결합되기 쉬운 구조가 됩니다.
개인적으로 이부분을 위해서 RIBs의 Dependency 주입 방식을 참고하여 사용한 경험이 있는데요
결과적으로 복잡도와 유지보수 비용을 완전히 피하기는 어려웠습니다.

반면 TCA는 기능 단위로 Reducer를 나눠 관리할 수 있고,
이 Reducer들을 조합하여 서로 간에 액션을 전달하는 방식이 구조적으로 잘 정리되어 있기 때문에
기능별로 Reducer를 나누고 조합하여 확장성과 유지보수 측면에서 장점을 가져가며
위에서 말한 MVI의 단점을 해소할 수 있다고 생각했습니다.
다만 러닝커브가 있는 편이고, 라이브러리를 사용해야지만 적용할 수 있는 단점도 존재합니다.

이번 프로젝트는 사실 복잡한 화면이나 깊은 구조가 필요한 건 아니었습니다.
그래서 이 정도 규모에 실제 프로젝트였다면, MVI를 선택할 가능성이 더 높았을 것 같습니다.

그럼에도 TCA를 선택한 이유는, Reducer를 분리하고 조합하는 구조를 직접 경험해보고 싶었기 때문에 TCA를 선택했습니다.


아키텍처 설계


Tuist 기반 Clean Architecture 모듈화 전략

저희 팀은 프로젝트를 단순히 런칭하는 데 그치지 않고,
장기적으로 운영하며 유지보수가 용이한 구조를 만드는 것을 목표로 했습니다.
또한, 여러 명의 개발자가 협업하는 환경에서 충돌을 줄이고,
기능의 책임을 분리하여 유연한 확장이 가능하도록 설계하는 것이 중요했습니다.

정해진 기간 내에 안정적으로 런칭하기 위해서는 팀원들이 익숙하고,
동시에 위 요구사항들을 만족할 수 있는 아키텍처를 선택하는 것이 필요했습니다.
결과적으로 팀 전체가 더 익숙하고 확장성·유지보수 측면에서 적합한 Clean Architecture를 채택했습니다.

추가로, 프로젝트를 기능 단위로 나누어 관리하고, 각 모듈 간의 의존성을 명확히 하기 위해
Tuist를 활용한 모듈화 구조도 함께 도입했습니다.
이 조합은 개발 및 협업 효율성은 물론, 향후 기능 확장과 리팩토링에서도 유리한 구조를 제공할 수 있다고 판단했습니다.


모듈화 그래프

저희 팀이 Tuist를 활용하여 설계한 Clean Architecture 기반 모듈화 그래프입니다.
각 Feature를 수평으로 생성하여 분리함으로써 각 Feature를 독립적으로 유지하며
전체적인 구조는 CleanArchitecture의 흐름에 따라 모듈화 하였습니다.


모듈들의 역할

Service

  • 각 Feature의 루트 역할을 하는 상위 모듈입니다.
  • 화면 전환 로직과 의존성 주입(DI)을 담당합니다.

Feature

  • 화면 단위의 모듈입니다. (View, Feature)
  • ex) 로그인, 폴더, 리포트, 설정 등
  • 각 Feature는 독립적으로 동작하며, 재사용 가능한 구조를 지향합니다.
  • 각 Feature MicroFeatureArchitecture로 구성 되며 Feature간의 통신은 Interface를 통한 의존성 주입(DIP)에 따라 이루어집니다.

Core

  • Feature들이 공통적으로 사용하는 유틸리티, Extension, 공용 View 등 공통 기능 모음입니다.
  • 비즈니스 로직과는 무관한 단순 재사용 코드들이 위치합니다.

DesignKit, VideoKit

  • DesignKit: 앱 전반에 사용되는 디자인 시스템, 컴포넌트(UI Components), 폰트, 컬러 등을 포함합니다.
  • VideoKit: 영상 재생과 관련된 모듈입니다.

Data

  • Domain 레이어의 요구사항을 만족시키는 Repository 및 DataSource 구현체 모듈입니다.

Networker

  • 네트워크 요청을 처리하는 모듈입니다.
  • 자체적으로 만든 네트워크 라이브러리를 사용합니다.

Domain

  • 프로젝트의 핵심 비즈니스 로직이 위치한 모듈입니다.
  • UseCase, Entity, RepositoryProtocol 등의 비즈니스 중심 계층을 담당합니다.

Shared

  • 앱 전반에서 사용되는 공통 자원들이 포함된 모듈입니다.

static, dynamic Framework의 선택 기준

Static과 Dynamic Framework의 간단한 차이점을 짚고 넘어가겠습니다.

Static

  • 앱의 실행 파일에 바이너리 파일이 포함되어 컴파일 타임에 로드되는 방식
  • 모듈화 시 static 모듈을 여러곳에서 의존하게 된다면 코드가 여러 곳에 복사되어서 중복 발생
  • 실행 파일에 포함 되어 속도는 빠르지만 앱의 바이너리 사이즈 증가
  • 독립적으로 사용되는 기능에 적합

Dynamic

  • 앱의 실행 파일에 포함되지 않고, 런타임 시 링크되어 코드가 메모리에 로드 되는 방식
  • dynamic을 의존하면 코드가 메모리에 한 번만 로드되어 중복 문제가 없음
  • 메모리 사용 효율이 좋고 앱 바이너리 사이즈가 작지만, 초기 로드 시 약간의 오버헤드 발생 가능 (하지만 메모리 공유로 인한 이점도 존재)
  • 여러 모듈에서 공유되는 코드나 리소스를 포함하는 경우에 적합

몇 개 이상 모듈을 의존하는 경우 dynamic, static 중 하나를 선택해야되나에 대한 고민이 있었습니다.

결론은 정확한 개수(2개 이상이면 무조건 Dynamic Framework) 보다
조금 더 유연한 방법으로 판단하기 위해서 아래와 같은 3가지 규칙을 만들고 Framework를 선택하게 되었습니다.

조건DynamicStatic
여러 모듈에서 참조하는 전역적인 성질을 띄는 모듈인가?
여러 모듈에서 참조하지 않고 독립적인 모듈인가?
리소스를 포함하는 모듈인가?

Feature간의 의존성 관리 방식

Feature간의 의존성 관리 방식은 Feature들을 수평으로

같은 Layer의 위치 시킴으로써 각 Feature 간의 독립성을 유지 시킴으로써

각자의 작업에 영향을 최소화 하였습니다.


MicroFeature Architecture

MicroFeature Architecture를 적용한 배경

저희 팀은 총 4명의 iOS 개발자로 구성되어 있어, 규모가 작지는 않았습니다.
그만큼 여러 명이 동시에 각자의 기능을 개발하면서도 충돌 없이 독립적인 개발 환경을 유지하는 것이 중요했습니다.

그래서 각 기능(Feature)을 완전히 독립적인 단위(MicroFeature)로 분리하고,
Demo, Test, Interface 등을 함께 갖춘 구조로 설계하였습니다.

이 구조는 각 Feature 별로 독립적으로 앱을 빌드하고 테스트할 수 있으며,
각 Feature간에 결합도를 줄이며 인터페이스 기반의 의존성 주입을 통해서 유연하게 통합되는 장점이 있기 때문에
MicroFeature를 적용하게 되었습니다.

각 모듈들의 역할을 간단하게 알아보겠습니다.


Demo

  • 실제 앱과 별개로, 해당 Feature를 독립적으로 실행하고 테스트할 수 있는 샘플 앱 모듈입니다.

Feature

  • 해당 기능의 핵심 구현이 포함된 모듈입니다.
  • View, Reducer(TCA 사용 시), UI 구성 요소 등을 담고 있습니다.

Interface

  • Feature가 외부에 제공하는 공식적인 진입점이자 추상화된 인터페이스입니다.
  • 다른 Feature에서 이 인터페이스만 의존함으로써, DIP(Dependency Inversion Principle)를 실현합니다.

Test

  • 해당 Feature에 대한 테스트 모듈입니다.

Testing

  • Feature에 대한 테스트 전용 Mock 데이터를 포함하는 모듈입니다.

마무리


짧은 시간 안에 프로젝트를 안정적으로 런칭함과 동시에, 새로운 기술에도 도전해보기 위해
많은 논의와 고민을 거쳤고, 그 여정을 부족하나마 글로 정리해보았습니다.

무엇보다도 능력 있는 개발자 분들과 함께한 시간은 정말 값진 경험이었고,
개발 외에도 팀워크와 소통 면에서 많은 것을 배울 수 있던 의미 있는 여정이었습니다.

iOS 팀은 물론, 서버와 디자인팀 모두 정말 고생 많으셨고
함께 작업할 수 있어 정말 행복했습니다. ❤️❤️❤️

디프만 16기 5팀 iOS 깃허브


참고

https://green1229.tistory.com/267

profile
iOS 개발자

2개의 댓글

comment-user-thumbnail
2025년 4월 16일

글을 읽는데 이해가 잘 되서 너무 재밌게 잘 읽었습니다. 간단명료하게 설명해주셔서 너무 좋아요

1개의 답글