안녕하세요. 올리브영에서 앱 개발을 담당하고 있는 쌈(ssam) 입니다. 약간은 늦었을 수도 있지만 올리브영의 성장과 함께 앱이 어떤 변화를 겪고 있는지 공유드리려고 하며 앱 아키텍처 도입 시리즈의 첫 번째 이야기로 올리브영의 앱(Android, iOS)이 클린 아키텍처를 도입하게 된 이유와 과정을 공유드리고자 합니다.
필요성을 느끼며
올리브영의 앱(Android, iOS) 프로젝트를 열면 처음엔 누구나 비슷한 생각을 했습니다. (“어!~ 이거 뭐지”). 그리고 두 번째로 드는 생각은 어디부터 봐야 하지 입니다. 개인차가 있을 수 있지만 소프트웨어의 개발 단계인 요구분석 → 설계 → 구현 → 테스트 → 유지 보수에서 개인적으로 가장 중요하게 생각하는 건 유지 보수 입니다. “내가 만든 오래된 코드는 다른 사람 코드와 같다”라는 걸 깨닫게 되었을 때부터 어떻게 하면 유지 보수 하기 쉽게 개발할 수 있을까라는 생각으로 개발을 해왔기 때문에 오랜만에 마주친 갖가지 안티 패턴들과 더불어 많은 God Class/Object 들은 참 많은 생각들을 하게 했습니다.
팀원들 누구나 구조부터 개선하고 싶은 마음은 컸지만 당장은 눈앞에 있는 이슈들부터 해결을 해야 했기에 섣불리 시작할 수 없었고 부채만 쌓여가는 걸 알면서도 계속해서 기능만 추가하고 있었습니다. 하지만 때마침? 앱 성능 개선이라는 과제가 떨어졌고 다들 맘속으로만 진행했던 구조 개선을 위해 한자리에 모여 다음과 같이 정리해 봤습니다.
우선 우리가 가지고 있는 가장 큰 문제는 무엇인가부터 살펴보았습니다.
- Android, iOS 모두 강하게 결합(스위스 장인이 만든 것처럼…)되어 있는 God Class (ex. God Activity, God ViewController), God Object 들를 가지고 있습니다.
- 또한 관심사 분리가 되어 있지 않다 보니 분석이 힘들고 코드 전반에 대한 장악력이 떨어졌고, 결국 간단한 기능을 수정할 때도 사이드 임펙트를 먼저 걱정하게 되는 상황에 이르렀습니다.
그래서 우리는 무엇을 원하고 있는지 생각했습니다.
- 고객 중심적인 사고를 바탕으로 관심사를 분리하여, 원활한 협업과 더불어 유지 보수를 향상시키고 싶었습니다.
- 계층 구조를 적용하여 코드의 품질을 높이고, 테스트 코드도 도입하고 싶었습니다.
- Android, iOS 두 플랫폼에 동일한 비니지스 구조를 적용함으로써 함께 고민하고자 했습니다. (Android, iOS 개발자들이 엄청 친합니다.)
- 그래서 결론은 클린 아키텍처를 도입하기로 결정하였습니다.
클린 아키텍처
우선 Android, iOS 모두 현재 모바일에서 가장 대중적인 클린 아키텍처를 프로젝트 아키텍처로 도입하기로 결정 했습니다. 그럼 올리브영 앱이 도입한 클린 아키텍처에 대해 알아보겠습니다.
클린 아키텍처는 개발자라면 한 번쯤 들어봤을 책 “클린 코드”를 저술한 로버트 C 마틴이 제안한 시스템 아키텍처로, 계층을 분리하여 관심사를 분리하는 것이라 할 수 있습니다. 아래 그림은 해당 아키텍처를 다이어그램으로 표현하고 있습니다. (이하 과녁판 그림)
엔티티(Entities)
- 어떤 업무의 일반적인 규칙을 가지고 있는 그 업무 규칙에 대한 정의라고 볼 수 있으며, 외부가 변경되더라도 영향을 받지 않는 영역입니다.
- 메서드를 가진 객체일 수도 있지만 데이터 구조와 함수의 집합일 수도 있습니다.
유즈 케이스(Use Cases)
- 엔티티가 업무의 규칙이라면 유즈케이스는 어플리케이션에 특화된 업무 규칙을 포함합니다. 즉 어떤 업무에 대한 어플리케이션의 요구사항들이라고 볼 수 있습니다.
- 유즈 케이스는 어플리케이션의 업무 규칙을 캡슐화하여 엔티티로부터의 데이터 흐름을 조합합니다.
- 해당 계층의 변경이 엔티티에 영향을 줘서는 안되며 DB, Web Framework 및 UI에 대한 변경으로부터 영향을 받지 않습니다.
인터페이스 어뎁터(Interface Adapters)
- 컨트롤러, 프리젠터, 게이트웨이등 어뎁터들로 구성됩니다. 컨트롤러는 외부 장치들과 소통하는 I/O 장치라고 생각할 수 있으며, 프리젠터는 외부에 있는 UI에 어떤 데이터를 보여주고, 외부 인터페이스로부터 입력을 어떻게 받을지를 결정합니다. 그리고 게이트웨이를 통해서 DB나 외부 네트워크에 액세스할 수 있습니다.
- 내부(엔티티 및 유즈케이스)에서 나가는 데이터를 외부에서 처리하기 위한 형식으로 변한하고, 외부에서 들어오는 데이터를 내부에서 처리하기 위한 형식으로 변환합니다.
프레임워크와 드라이버(Frameworks & Drivers)
- 일반적으로 DB나 웹 프레임워크 같은 시스템의 핵심 업무와는 상관없는 프레임워크 도구들로 구성됩니다.
고 수준의 계층은 그보다 하위 계층의 변화로부터 보호되어야 하며, 업무 규칙이란 게 한번 정해지면 변화가 많지는 않지만, UI의 경우는 변화가 잦을 수 있기 때문에 해당 계층의 변화가 상위 계층에 영향이 가면 안 된다고 할 수 있습니다. 클린 아키텍처를 가능하게 하는 가장 중요한 규칙이 의존 규칙인데, 이 규칙에 의해 소스코드는 안쪽을 향해서만 의존할 수 있으며 이런 아키텍처가 동작하기 위해서는 의존성 규칙을 지켜야 합니다. 위 다이어그램의 화살표 방향이 이를 표현하고 있는데, 즉 의존성의 방향은 저수준(바깥쪽)에서 고수준(안쪽)으로 향해야만 한다는 것입니다.
모바일 클린 아키텍처
사실 모바일 클린 아키텍처라는 건 존재하지 않습니다. 로버트 C 마틴의 책 어디를 봐도 모바일이라는 말은 존재하지 않습니다. 많은 모바일 개발자들이 클린 아키텍처를 바탕으로 모바일 환경에 맞는 설계 방법을 찾게 되었고 현재는 아래와 같이 가장 대중적인 구조를 모바일에서 가져간다 생각됩니다. 올리브영 앱 또한 이를 바탕으로 리패키징을 진행하였습니다.
프리젠테이션 (Presentation Layer)
- 플랫폼에 의존성이 높은 레이어로 화면과 입력에 대한 처리 등 UI와 관련된 모든 부분을 담당합니다. UI의 경우 업무 로직에 비해 상대적으로 변경할 일이 많기 때문에 UI에 관련된 코드를 한곳에서 관리함으로써 업무 로직을 보호합니다.
- View
- UI 화면 표시와 입력에 대한 처리만 담당합니다. 중요한 목적은 플랫폼에 의존적인 모든 처리를 담당합니다.
- Presenter (ViewModel)
- View에 표시되는 데이터를 만드는 역할을 담당합니다. 사용자로부터 어떤 입력이 왔을 때 어떤 UseCase를 실행할지에 대한 판단을 합니다. 즉 하나 이상의 UseCase를 실행시켜 결과를 받아와 UI를 업데이트합니다.
도메인 (Domain Layer)
- 해당 레이어는 플랫폼 및 Presentation, Data 레이어에 대한 의존성을 가지지 않으며 개발 언어에 대해서만 의존성을 가지고 있습니다. 업무 로직들을 한 계층에서 관리하는 데 목적을 두고 있습니다.
- UseCase
- 도메인 관점의 업무 로직을 담고 있습니다. 각 개별 기능 또는 업무의 논리 단위를 담당하고 있으며, 이름만 보고 이게 무슨 기능을 가졌을지 짐작하고 구분할 수 있어야 합니다.
- Model
- 순수하게 앱의 업무 로직에서 필요한 데이터 입니다. (과녁판 그림의 엔티티와는 전혀 다릅니다.)
- Repository Interface
- Data 계층에서 구현될 Repository에 대한 Interface 정의입니다.
데이터 (Data Layer)
- 데이터 입출력 코드를 하나의 계층에서 관리하고, 데이터 소스들과 데이터를 소비하는 다른 계층과의 경계를 둡니다.
- Repository
- Domain 레이어에서 정의한 Interface를 구현합니다. 데이터 레이어에서 가장 중요하다고 볼 수 있으며, 저장소가 어떻게 되는지를 외부에 감쳐주는 역할을 합니다. 즉 다른 계층과 데이터 소스에 대한 경계를 둠으로 인해 내부에서 어떤 데이터 처리가 일어나는지를 숨겨준다고 할 수 있습니다.
- DataSource
- 실제 데이터의 입출력에 대한 추상화라고 할 수 있습니다. 즉 데이터 처리의 구현 방식을 감춰줍니다. 외부로 노출되는 함수들이 있지만 실제로 내부에서 어떤 API를 쓰는지 로컬 저장소를 사용한다면 DB를 사용하는지 파일로 사용하는지 이런 걸 외부에 숨겨줍니다.
- Entity
- 데이터 소스에서 사용되는 데이터를 정의한 모델입니다. 일반적으로 Rest API의 Request/Response를 위한 json, Local DB에 저장된 테이블을 표현하는 Data Class 형태로 정의됩니다.
마치며
현재 올리브영 앱은 Android, iOS 앱 모두 아직 완벽하지는 않지만 위와 같은 3-layer 구조로 클린 아키텍처가 적용되어 있으며, 프리젠테이션 레이어에서는 각 플랫폼에 맞는 설계와 패턴을 적용하고자 했으며, 도메인 레이어, 데이터 레이어의 경우 설계를 동일하게 가져가 각각의 플랫폼 개발자들이 동일한 고민을 할 수 있도록 하였으며 추가로 개발되는 모든 기능은 클린 아키텍처를 기반으로 개발되고 있습니다.
적용하는 과정에서 가장 설계가 어려웠던 계층은 도메인 레이어인 것 같습니다. 사실 데이터 레이어의 경우 데이터 소스가 백앤드를 기준으로 설계하면 되고, 프리젠데이션 레이어는 UI를 기준으로 설계하면 되지만 도메인 레이어의 경우 정답이 없다고 생각합니다. 각 계층들을 독립적이고 격리되게 설계하고자 하는 목표가 있지만 한번 수정이 되면 영향도가 가장 큰 계층이다 보니 끊임없이 고민하고 지속적으로 리펙토링이 필요한 계층이라고 생각됩니다.
물론 요새 가장 중요한 비지니스 로직의 경우 대부분 백앤드에 있기 때문에 꼭 필요한가라는 의문을 가질 수 도 있습니다. 하지만 도메인 계층이 없다고 생각해 봤을 때 UI에 변화가 있을 때 마다 혹은 백앤드 데이터에 변화가 있을 때마다 프로젝트 전반에 영향을 미칠 수 있다고 생각합니다. 꼭 도메인이라는 이름에 계층이 필요하다고는 할 수 없지만 이 역할을 하는 어떤 무언가는 필요하다고 생각을 합니다.
마지막으로 말씀드리면 모바일에서 클린 아키텍처를 꼭 도입해야 한다라는 규칙은 없습니다. 계층이 나눠지고 규칙을 따르다 보니 간단한 기능을 구현할 때도 많은 클래스의 생산은 피할 수 없기 때문에 무엇보다 함께 개발을 하는 팀원들의 목표가 동일해야 한다고 생각합니다. 도입을 통해 갖게 되는 장점이 현재 프로젝트에 얼마나 많은 도움을 줄 수 있는지 팀 내에서 충분한 협의를 통해 상황에 맞는 설계가 가장 중요하다 생각됩니다.
이번 시간에는 올리브영 앱에 첫 번째로 시작했던 우다당탕 아키텍처 도입기를 공유했습니다. 다음번에는 좀 더 발전된 모습을 통해 한 단계 더 진화한 내용을 가지고 찾아뵙도록 하겠습니다. 그럼 제가 가장 좋아하는 “좋은 코드의 구조가 좋은 제품으로 이어진다”라는 말을 소개 드리며 마치도록 하겠습니다…