iOS Modular Architecture 를 향한 여정 Part 1 — XcodeGen 도입과 모듈화의 시작

Wooseong Kim
29CM TEAM
Published in
10 min readJul 21, 2022

안녕하세요, 29CM iOS 개발자 김우성입니다.

저번 기술 블로그를 작성한 지 꽤 오랜 시간이 지났네요. 어떤 내용을 다뤄볼까 고민하다 이번 글에서는 여러 iOS 팀에서 고민하시는 Modular Architecture 를 29CM iOS 팀에서는 지금까지 어떻게 만들어 오고 있었는지, 그리고 앞으로는 어떻게 만들어 갈 것인지에 대해 이야기를 해볼까 합니다. (3부작 정도로 생각하고 있습니다 ㅎㅎ)

  • Part 1 : XcodeGen 도입과 모듈화의 시작 (본편)
  • Part 2 : 프로젝트 모듈화, 레거시와 공존하기
  • Part 3 : 아키텍쳐 레이어의 변화와 Tuist 의 도입, 그리고 그 너머

위와 같은 순서로 글을 작성할 예정이며, 현재 저희 팀은 Tuist 도입의 마무리 단계에 있습니다. 이는 아래에서도 잠깐 언급할 예정입니다.

.

작년 29CM 에 합류했을 당시 저희 iOS 프로젝트는 Xcode Project 나 Workspace 를 관리하는 도구(iOS 쪽에선 사실상 XcodeGen/Tuist 를 의미하겠죠) 를 사용하고 있지 않는 상태였습니다.

Squad-based 프로덕트 팀에서 여러 iOS 개발자가 효율적으로 협업하기 위해선 프로젝트 관리 도구가 다른 무엇보다 중요한 인프라라고 생각했기에, 합류 후 팀에서 가장 먼저 진행했던 것이 XcodeGen 도입입니다. 도입과 관련해선 좋은 글이 이미 워낙 많기에, 이번 글에서는 도입 자체를 다루진 않고 그 이후부터를 얘기하고자 합니다.

XcodeGen 이 도입된 시점에서 저희는 하나의 Project 와 하나의 Target(=앱) 만 가진 상태였습니다. 인프라나 플랫폼 작업이 보통 그렇듯 이것만 메인으로 진행하긴 현실적으로 어렵기에, 장기적인 시각으로 점진적으로 진행할 수 있도록 Milestone 을 가장 먼저 세웠습니다.

아래는 이 여정을 시작하기 전에 세웠던 것으로, 최초에는 3개의 Phase 만 정했습니다. 처음부터 시간을 들여 상세하게 Phase 를 나눌 수도 있었지만 입사 초기엔 프로젝트에 대한 이해도도 부족했을 뿐더러 우선 시작하는 것 자체가 더 중요하다 생각했었기에 첫 삽을 뜨고 난 이후에 Phase 를 더 상세하게 나누어도 된다는 생각이 있었습니다.

Phase 1 을 진행하며 프로젝트의 현황이나 여러 레거시를 맞닿뜨리면서 좀 더 현실적인 방향으로 Phase 를 점점 더 세세하게 나누게 되었는데요, 아래의 스샷이 가장 최신화된 Phase 입니다. (현재 Phase 5 / Phase 6 가 같이 진행되고 있습니다)

Tuist 를 도입하기로 하면서 앱 아키텍쳐 레이어를 현재는 다시 개편한 상태인데요, 그 전까진 아래와 같은 형태로 모듈을 분리해 오고 있었습니다.

어떤 과정을 거쳐 위와 같은 형태로 만들어나가게 되었는지 크게 세 가지의 꼭지로 말씀드리고자 합니다.

1. 모듈화의 시작, Foundation 모듈과 아키텍쳐 틀 잡기

가장 쉽고 빠르게 할 수 있는 작업입니다. 각 모듈에서 거의 대부분 사용하게 되는 모듈이기도 해서 사실 가장 먼저 진행해야 하기도 합니다. 많은 코드들이 서로 의존하고 있었던 최초 상태에서도 Foundation 분리는 크게 어렵지 않았습니다.

  • Foundation 프레임워크의 클래스들의 Extension 들
  • 유틸성 로직
  • 앞으로 만들 모듈들에서 공통으로 사용하게 될 오픈소스들(Swinject, Pure 등)

정도가 있었습니다. 처음부터 모든 Foundation 성격의 파일을 앱 타겟에서 분리할 필요는 없습니다.

어느정도 분리가 되었다면 다음으로는 Modular Architecture 의 틀을 먼저 잡는 것이 좋습니다. 그래야 상호의존도가 강한 코드들을 조금씩 분리해 적절한 모듈에 넣으면서 조금씩 아키텍쳐를 발전시키기 용이하다는 생각입니다. 저희 팀은 아래와 같은 모듈들을 먼저 만들기 시작했습니다.

Foundation 위 레이어를 보시면 Core / ThirdParty 레이어가 있는데요(실제 존재하는 모듈은 아니고 추상 레이어입니다), 사실 최초 작업 시에는 Core 레이어가 있지는 않았습니다.

첫 시작은 아래 정도의 모듈로만 시작했습니다.

  • App29CM
  • App29CM_Domain
  • App29CM_SomeModule
  • App29CM_SomeModule2
  • ThirdParty_Firebase
  • App29CM_Foundation (Firebase 가 Foundation 모듈을 의존하게 되는데 네이밍이 이상하죠)

빈 Entity, Networking, ReactiveX, Domain 등을 만들어 두고, 다른 개발을 하면서 조금씩 옮길 수 있을 때마다 적절한 모듈로 이동을 시켜 주었습니다. 그 과정에서 분리가 쉽지 않은 코드들이 많이 있었고 Part 2에서 소개할 방법들과 함께 상호의존을 먼저 제거하고 파일을 옮기는 식으로 주로 진행을 했었습니다.

다만 많이 진행하고 보니 모듈 네이밍 컨벤션이 좋지 않다고 느껴서, Tuist 도입을 하면서는 아키텍쳐 레이어를 새로 개편한 상태입니다. (Part 3 에서 소개 예정)

이번 단계에서 Firebase 모듈을 적었는데요, 경험적으로 다른 써드파티 SDK들 보다 Firebase 를 분리하는게 가장 어렵고 번거롭기 때문에 Modular Architecture 로 가는게 큰 병목 중 하나로 인지하고 모듈화 작업 초기부터 시간을 들였습니다.

기존엔 CocoaPods 로 Firebase SDK 를 가져오고 있었는데요, CocoaPods 로는 Firebase 부분을 모듈화 할 수 없습니다. (관련해서 오랜 기간에 걸쳐 여러 시도를 해봤었는데 불가능하다 판단했습니다. 혹시 가능한 방법을 아신다면 알려주시면 감사하겠습니다) 이는 XcodeGen 뿐만 아니라 Tuist 기반의 Workspace 에서도 마찬가지였습니다.(SPM 으로도 불가)

두 가지 방향이 가능한데요,

  • 필요한 Firebase SDK xcframework 를 레포에 포함시켜 로컬에서 Firebase 모듈에 링킹
  • Carthage 를 사용해 Firebase 모듈에 링킹

이 중 팀이 선호하시는 방향을 선택하시면 될 것 같습니다.

SDK를 모듈에 링킹한 이후에는 모듈 내에서 다음과 같이 `@_exported import` 를 사용해 주고, 모듈 외부에서는 Firebase 모듈만 임포트를 해도 Firebase SDK 를 사용할 수 있도록 해 주시면 됩니다.

다만 위 방식은 기존에 있던 여러 레거시 로직들로 인해 다른 모듈들에서 Firebase SDK 를 직접 알아야 할 때만 필요한 부분이고, 나머지 모듈에서 모든 Firebase SDK 의존이 제거된 이후에는 위 부분을 다시 제거하고 Firebase Interface / Implementation 모듈로 나누어 Interface 모듈로만 다른 모듈에 기능을 제공하도록 변경하시면 됩니다. (저희 팀에선 Tuist 도입 이후 본격적으로 진행하게 될 방향입니다)

2. 아키텍쳐 틀에 맞게 모듈 분리해 나가기

여기까지 진행이 되었으면 이제 모듈을 조금씩 더 만들고 파일들을 조금씩 더 옮겨가는 단계입니다. 보통은 Domain, Service 레이어의 로직이 가장 많은 상호 의존 이슈가 있을 것이라 예상할 수 있기에, 본격적인 모듈 분리에 앞서 Service 레이어 아래로의 로직들을 먼저 분리하는 단계라 보시면 됩니다.

Feature Flag 같이 비교적 독립적인 코드들은 쉽게 분리할 수 있습니다. 그 외에 중요한 모듈은 Entity, Networking, ReactiveX 입니다. 각각의 모듈에서 모듈이 가져야 할 오픈소스들을 링킹하도록 하며(+ 동시에 앱 타겟에서는 제거), 향후 Service 모듈 작업을 위해 선행되어야 하는 필수 작업이기도 합니다.

그리고 이 시기에는, Foundation 모듈이 제대로 분리가 되었다면 ThirdParty 계열 모듈을 조금씩 분리할 수 있게 됩니다. 물론 써드파티와 관련된 로직들도 구현에 따라 분리가 쉽지 않은 경우가 있는데, 이 부분은 Part 2 에서 좀 더 다루려고 합니다.

Domain 모듈은 만들어 두었지만 이 시기에는 사실 크게 옮길 수 있는 파일들이 없습니다. 팀에 조금 더 시간적인 여유가 있는 상황이라면 Domain 보다 더 하위 모듈로 내려야 하는 파일이 여러 의존 때문에 못 내리는 상황에서 Domain 모듈로 우선 내려두는 결정을 할 수도 있습니다.

이러한 과정들을 통해 하나의 프로젝트와 타겟만 가지던 Workspace 가 (여전히 하나의 프로젝트이지만) 10개 이상의 모듈로 분리할 수 있게 됩니다. 여기서부터는 본격적으로 모듈을 분리할 수 있게 됩니다 :)

마치며

위에 언급했던 7 단계의 모듈화 작업에서, 현재 저희 팀은 Phase 5 / 6 을 진행하고 있습니다. Tuist 도입은 거의 마무리가 되었고 조만간 Tuist 로 만든 새 Workspace 에서 개발을 하게 될 것 같네요 :)

바뀐 Architecture Layer 만 이번 글에서 먼저 짧게 소개드리면 아래와 같습니다. 위에서 소개드린 수준까지 진행을 하고 보니 Tuist 를 제대로 적용하기 전에 조금 더 나은 형태로 레이어의 추가 개편이 필요하다는 생각이 드는 시기가 찾아왔습니다.

그래서 저희보다 더 큰 규모의 국내외 회사들의 사례를 찾아보게 되었는데요, 주로 아래와 같은 형태로 레이어 이름을 사용하는 것을 많이 보았습니다. 그래서 저희 조사에서 가장 많이 나왔던 형태로 각 레이어의 이름을 결정하게 되었습니다.

  • App
  • App Feature (Splash, Main, Product, Contents …)
  • App Core (Entity, Networking, Service, UI, Resource …)
  • ThirdParty (여러 SDK들)
  • Shared (App Core 와 ThirdParty 모듈이 의존하게 될 모듈들)

위와 같은 형태로 만들어 갈 예정이고, Tuist 로 넘어간 이후에는 이 상태에서 추가로 Interface / Implementation 모듈로도 나누어 적절하게 dynamic/static 을 사용해 줄 예정입니다.

.

Modular Architecture 는 어떻게 보면 양도 너무 방대하고 방식도 다양해서, 하나의 글에서 상세한 내용을 다루기는 어려웠습니다. 그래서인지 이번 시리즈 작성을 오랫동안 미뤄왔던 것 같은데요, 이번 Part 1 글을 통해 iOS 팀 혹은 모바일 팀에서 모듈화를 시작하실 때 할 때 조금이나마 도움이 되실 수 있길 바라겠습니다.

그러면 조만간 ‘Modular Architecture 를 향한 여정 Part 2' 글에서 또 뵙겠습니다!

함께 성장할 동료를 찾습니다

29CM 는 3년 연속 거래액 2배의 성장을 이루었습니다.

앞으로도 더 큰 성장을 만들기 위해 여러 스쿼드에서 OKR을 기반으로 여러 피쳐들을 만들어 가고 있으며, 이번 글과 같이 인프라 혹은 플랫폼 업무를 하기도 합니다.

함께 성장하고 유저 가치를 만들어낼 동료 개발자분들을 찾고 있습니다.
많은 지원 부탁드려요!

🚀 29CM 채용 페이지 : https://www.29cmcareers.co.kr/

--

--

Wooseong Kim
29CM TEAM

Software Craftman @ 29CM. Love books, coffee, simplicity. An enthusiastic follower of Steve Jobs.