개요
단일 모듈로 개발하는 것이 당연하던 시절, 클린 아키텍쳐라는 개념이 안드로이드 진영에 깊게 스며들며 많은 개발자들이 관심사에 따른 레이어 분리에 많은 노력을 들이기 시작했습니다. 궁극적으로 의존성을 완벽하게 지키면서 레이어를 분리하기 위해서는 모듈 분리가 필요했고, 점차 클린 아키텍쳐 기반의 멀티 모듈 구조를 사용하는 프로젝트들이 많아졌습니다. 그런데 구글에서는 우리가 아는 클린 아키텍쳐와는 조금 다른 아키텍쳐를 권장하고 있었습니다. 이러한 구글 권장 아키텍쳐는 클린 아키텍쳐와는 어떤 차이점이 있을까요? 두 아키텍쳐를 기반으로 안드로이드 프로젝트에서 모듈화를 진행할 때에는 어떤 특징이 있을까요? 한 번 찬찬히 알아봅시다.
Clean Architecture
우선 클린 아키텍쳐에 대해 간단히 알아봅시다. 위 사진은 아마 지겹도록 보셨을 듯한 Clean Architecture 도식화 자료입니다. 이를 좀 더 직관적으로 이해하기 위해, 안드로이드 프로젝트에서 Clean Architecture 를 채택하고 모듈화를 진행했을 때의 일반적인 구조를 살펴봅시다.
모듈 간 의존성 방향을 보면 도메인 모듈이 가장 상위 모듈인 것을 확인할 수 있습니다. 도메인 모듈은 유저가 수행할 수 있는 액션을 바탕으로 UseCase 를 정의하고 도메인에서 사용되는 고유한 Model(Entity) 을 선언해 비즈니스 로직을 정의합니다. 이러한 비즈니스 로직에 대해 최대한 외부 의존성을 제거하기 위해 도메인 모듈은 가급적 android 의존성 없이 Kotlin 언어로만 작성하기를 권장하고 있습니다. 그리고 UseCase 로부터 데이터에 접근하는 과정을 레포지터리 패턴과 추상화를 이용해 의존성을 역전시켜 데이터 모듈이 도메인 모듈을 바라보게끔 만들었습니다. 또한 데이터 모듈에서 사용하는 모델은 도메인 모듈에 정의된 모델에 기반한 mapper 를 구현해 변환하여 사용하게 됩니다. 따라서 UI 모듈과 데이터 모듈 모두 도메인 레이어에 완전히 의존하게 되는데, 이처럼 클린 아키텍쳐에 개념에서는 UseCase 에 기반한 도메인 레이어가 핵심이 되어, UI 나 데이터 쪽이 아무리 변경되더라도 고유한 도메인의 비즈니스 로직은 명확하게 캡슐화되어 있어 변경사항이 생기지 않습니다. 따라서 이렇게 견고한 비즈니스 로직을 담는 도메인 레이어는 재사용 가능성이 높아지며, 특히 여러 플랫폼을 지원하는 크로스 플랫폼 앱 환경에서 이점을 가질 수 있습니다.
그렇다면 구글에서 권장하는 아키텍쳐는 어떤 구조를 갖고 있을까요?
Google Architecture
Android Developer 에서는 권장하는 아키텍쳐를 위와 같이 도식화하여 제공하고 있습니다. 그런데 한 가지 이상한 점이 눈에 바로 띄게 됩니다.
Domain Layer 가 최상위 레이어가 아닌 것도 모자라 Optional 이라니?
Clean Architecture 와 Google Architecture 사이의 가장 큰 차이점은 바로 도메인 레이어에 있습니다. 구글 권장 아키텍쳐에서는 많은 앱들이 도메인 계층을 필수적으로 가져야 할만큼 복잡하지 않다고 보기 때문에 도메인 레이어를 선택사항으로 제시하고 있습니다. 따라서 기본적으로 UI -> 데이터
방향의 의존성을 갖게 되어 유저 액션에 따른 앱 동작의 흐름이 단방향 데이터 플로우 패턴과 맞물려 안드로이드 앱 구조에 잘 어울리는 형태가 됩니다. 또한 UI 에서 도메인 으로, 도메인 에서 데이터 로 선형적인 의존성 방향을 띄고 있기 때문에 앱 확장이 필요한 경우에 UI 레이어와 데이터 레이어 사이에 도메인 레이어를 쉽게 도입할 수 있습니다. 실제로 안드로이드 프로젝트에서 모듈화를 진행했을 때의 일반적인 구조는 다음과 같습니다.
UI 모듈에서 도메인 모듈을 거쳐 데이터 모듈까지 마찬가지로 의존성이 선형적인 방향으로 걸리게 되며, 도메인 모듈은 선택사항이기 때문에 ViewModel 에서 직접 데이터 모듈의 repository 에 접근할 수 있습니다. 이때, 의존성 방향이 도메인 -> 데이터
방향이 되면서 더 이상 의존성 역전이 필요 없어졌기 때문에 repository 의 인터페이스와 구현체 관계없이 모두 데이터 모듈에 위치하게 됩니다. 물론 인터페이스와 구현체를 분리하기 위해 data-api
와 data-impl
과 같은 모듈로 이를 분리할 수 있겠지만, 의존성 방향엔 변함이 없기 때문에 전체적인 구조는 위 그림과 크게 달라지지 않습니다. 중요한 것은 의존성 주입을 이용해 repository 의 구현체를 다른 모듈에 공개하지 않고 외부에는 인터페이스만 노출하여 SOLID 원칙을 위반하지 않는 것입니다.
그런데 한 가지 의문이 드는 것이 있습니다. Clean Architecture 에서 도메인 모듈에 존재했던 비즈니스 엔티티 모델을 의미하는 model 은 어디 갔을까요? 가장 최상위 모듈인 데이터 모듈에 선언해야 할까요? 하지만 앱의 여러 모듈에서 전반적으로 사용하는 model 은 어떻게 보면 비즈니스 로직에 가까운데 도메인 모듈에 두어야 하지 않을까요? 하지만 그렇게 되면 또다시 클린 아키텍쳐처럼 모든 model 데이터를 mapping 해서 사용해야 할텐데 불필요한 보일러 플레이트가 아닐까요?
사실 어디에 두어도 아키텍쳐의 큰 흐름에 변화는 없겠지만, 구글의 Now in Android 저장소에서 이러한 고민에 대한 한 가지 간단명료한 솔루션을 얻을 수 있었습니다.
Now in Android 에서는 core:model
라는 최상위 모듈을 따로 두어 도메인 모델들을 관리하고 있습니다. 해당 모듈은 마치 클린 아키텍쳐의 도메인 모듈의 포지션과 비슷해보이며, 큰 구조상으론 구글 아키텍쳐의 데이터 모듈에 위치하게 됩니다. 다만 도메인 모델이 필요한 다른 모듈들이 최상위 모듈인 core:model
모듈을 직접 참조함으로써 불필요한 mapper 코드의 생성을 방지하는 모습입니다.
그럼 뭐가 더 나을까?
여기까지 살펴보았을 때, 저는 이렇게 느꼈습니다. 클린 아키텍쳐는 유즈케이스에 기반한 도메인 레이어를 굉장히 중시하며 모든 설계가 도메인 주도적으로 이루어지고, 레이어 간 존재해야 할 컴포넌트도 굉장히 명확하고 경직된 느낌이었습니다. 반면 구글 권장 아키텍쳐는 클린 아키텍쳐에서 너무 경직됐던 부분을 완화하여 레이어 간 데이터 흐름을 앱 환경에 맞춤시키고, 레이어 간 컴포넌트 배치 또한 비교적 유연한 느낌이었습니다.
그럼 둘 중에 뭐가 더 나을까요?
두 아키텍쳐의 장단점 등을 더 명확히 비교하면 좋을 것 같은데, 마침 이 두 아키텍쳐에 대해 열띈 토론을 한 곳이 있습니다. 바로 다름 아닌 Now in Android 저장소의 어느 한 discussion 입니다.
어느 한 유저가 Now in Android 프로젝트가 클린 아키텍쳐 개념과 SOLID 원칙을 위반했다며 비교적 최근인 2024년 2월에 discussion 을 열었습니다. 이에 직접 Google의 Now in Android 기술 책임자가 등판하여 구글 권장 아키텍쳐에 대해 설명하였고, 그러자 많은 유저들이 해당 discussion 에서 클린 아키텍쳐와 구글 아키텍쳐에 대해 이야기를 나누기 시작했습니다.
코멘트 하나 하나가 아키텍쳐와 SOLID 원칙에 대한 여러 개념과 의견을 심도있게 담고 있기 때문에, 이에 대한 이해도를 높이시려는 분들은 한 번 시간을 내어 쭉 살펴보시기를 적극 권장드립니다. 저도 몇 번을 반복하여 읽어보았는데, 아래는 제가 핵심적인 내용을 간추린 것입니다.
Now in Android discussion 요약
Now in Android 에서 클린 아키텍쳐를 사용하지 않은 이유
- Now in Android 는 구글 공식 아키텍쳐에 대한 모범 사례 및 실제 구현이 목적이기 때문이다.
- 구글에서 공식으로 권장하는 아키텍쳐에서 도메인 레이어는 선택 사항이며, 도메인 레이어는 데이터 레이어에 종속된다.
Google에서 클린 아키텍쳐를 권장하지 않는 이유
- 많은 앱들이 도메인 레이어를 필수로할 만큼 복잡하지 않다.
- 데이터 레이어에 종속된 UI 레이어를 사용하고, 나중에 필요한 경우 도메인 레이어를 도입하면 된다.
- 이와 같은 방식이 대부분의 Android 앱에 적합한 접근 방식을 제공한다고 생각한다.
클린 아키텍쳐와 비교하여 구글 아키텍쳐의 이점
- 덜 엄격하고, UI 와 데이터 레이어로 시작하여 앱 확장이 필요한 겨우에 도메인 레이어를 도입할 수 있다.
- 데이터 레이어가 다른 레이어에 종속되지 않기 때문에 데이터가 저장되는 위치를 더 쉽게 변경할 수 있다.
구글 아키텍쳐와 비교하여 클린 아키텍쳐의 이점
- 비즈니스 로직을 더 쉽게 변경할 수 있다. 비즈니스 로직이 명확히 캡슐화 되어있고, UI 또는 데이터 레이어에 비즈니스 > 로직이 없기 때문이다.
- 도메인 레이어의 비즈니스 로직이 Android 종속성 없이 구현되기 때문에 크로스 플랫폼 앱에서 재사용이 가능하다.
Now in Android 가 SOLID 원칙을 어기지 않은 이유
- 일반적으로 high-level 모듈이 low-level 모듈 구현과 독립적이어야 한다는 말에서, 이 “모듈” 은 꼭 gradle 모듈만을 의미하는 것은 아니며 클래스가 될 수도 있다.
- 이러한 관점에서 데이터 모듈에 repository 의 인터페이스와 구현체가 동시에 존재한다고 해서 SOLID 원칙을 위반하는 것은 > 아니다.
- 여전히 의존성 역전을 통해 하나의 모듈 내에서 SOLID 원칙을 지키고 있고, 이를
data-api
와data-impl
과 같은 > 모듈로 분리하는 것은 좋은 제안이지만 결국 크게 달라지는 것은 없다.
주로 Google 측 개발자의 발언을 중심으로 요약해보았는데요. 결국 discussion 은 구글 권장 아키텍쳐의 이념이 어느정도 수용되어, Now in Android 를 참고하는 여러 개발자들이 혼란을 겪지 않도록 가이드 상에 본 프로젝트에서 사용된 아키텍쳐는 클린 아키텍쳐가 아니라는 문구를 추가하는 정도도로 마무리 됩니다.
위에서 언급하진 않았지만 한 가지 흥미로운 점은 Now in Android 기술 책임자가 직접 이 프로젝트를 구글 아키텍쳐에서 클린 아키텍쳐로 리팩토링 해보았는데, 그 작업이 1시간 밖에 걸리지 않았다는 것입니다. 두 아키텍쳐 간 구조상의 유일한 차이점이라면 도메인 모듈과 데이터 모듈 간의 의존성 방향 차이이므로, 데이터 모듈의 repository 인터페이스를 도메인 모듈로 옮겨주어 의존성을 역전시키기만 하면 되는 일이니 어쩌면 당연한 일일지도 모릅니다. 정말로 이 둘은 그렇게까지 다른 아키텍쳐일까요?
결론
Android Developer 에서 제공하는 Common modularization patterns 이라는 문서에 아래와 같은 문구가 있는 것을 보았습니다.
모든 프로젝트에 맞는 하나의 모듈화 전략은 없습니다.
간혹 본인이 선택한 아키텍쳐에 스스로 매몰되어 오히려 생산성이 떨어지는 일을 종종 보았습니다. 우리가 구현하려는 것은 아키텍쳐가 아닌 소프트웨어 그 자체이고, 어디까지나 아키텍쳐는 소프트웨어를 더 효율적으로 만들 수 있는 도구가 아닐까요? 저는 팀원들이 다 같이 논의하여 합의가 된 코드라면 그것이 바로 최선의 도구이자 아키텍쳐라고 생각합니다. 앞서 언급한 discussion 이 결국 합의점을 맞이해 지금의 Now in Android 프로젝트가 있는 것처럼 말이죠!