앱 아키텍처는 앱의 구조를 정의하는 중요한 청사진이다. 이 아키텍처는 코드베이스를 효율적으로 관리하고, 확장 가능하게 만들며, 유지보수하기 쉬운 상태로 유지하기 위해 설계된다.
유지보수성 향상: 코드가 모듈화되어 변경 사항을 손쉽게 관리할 수 있다. 새로운 기능을 추가하거나 기존 기능을 수정할 때 영향이 최소화된다.
테스트 용이성: 관심사가 분리되어 있어 각 모듈을 독립적으로 테스트할 수 있다. 특히 UI와 데이터 처리가 분리되면, 데이터 로직을 테스트하기 쉬워진다.
확장성: 앱이 성장함에 따라 새로운 기능을 추가하는 것이 쉬워진다. 레이어 간 의존성이 명확해져 새로운 요구 사항에 유연하게 대응할 수 있다.
안정성과 성능: 앱이 잘 구조화되어 있어 비정상적인 종료나 리소스 부족 상황에서도 안정성을 유지할 수 있다. 데이터 저장과 로직을 분리함으로써 앱이 강제 종료되더라도 사용자 데이터를 복구할 수 있다.
협업 향상: 여러 개발자가 동시에 작업할 때, 아키텍처를 따른 코드는 서로의 작업이 겹치거나 충돌할 위험이 적다. 역할과 책임이 명확하게 나뉘어 있어 협업이 원활해진다.
일반적인 Android 앱은 다양한 구성요소(액티비티, 프래그먼트, 서비스 등)로 구성된다.
이러한 구성요소는 Android OS에 의해 관리되며, 개발자는 앱 매니페스트에서 이들을 선언한다. OS는 이를 통해 기기 전체 사용자 경험에 앱을 통합하는 방법을 결정한다.
앱은 사용자 중심의 다양한 워크플로와 작업에 적응할 수 있어야 한다.
사용자는 종종 여러 앱과 빠르게 상호작용하며, 운영체제는 리소스 부족 시 일부 앱 프로세스를 종료할 수 있다.
따라서 구성요소는 독립적이어야 하고, 앱 구성요소에 상태나 데이터를 저장하지 말아야 한다.
앱을 확장 가능하고 견고하게 만들며 테스트를 용이하게 하기 위해, Android 앱은 명확한 아키텍처를 가져야 한다. 주요 원칙은 다음과 같다.
관심사 분리
UI 기반 클래스(Activity, Fragment)는 UI 및 운영체제와의 상호작용만을 처리해야 한다. 이들을 가볍게 유지하면 수명 주기 관련 문제를 피하고, 테스트 가능성을 높일 수 있다.
데이터 모델에서 UI 도출
앱의 UI는 데이터 모델을 기반으로 도출해야 한다. 데이터 모델은 UI와 독립적이어야 하며, 지속적인 모델을 사용하는 것이 좋다. 이렇게 하면 앱이 강제 종료되더라도 사용자 데이터가 유지되고, 네트워크 연결이 없을 때도 앱이 계속 작동할 수 있다.
단일 소스 저장소(SSOT)
앱 내 데이터는 단일 소스 저장소(SSOT)에서 관리해야 한다. SSOT는 데이터를 소유하고 수정할 수 있는 유일한 위치다. 이를 통해 데이터 일관성을 유지하고, 오류를 쉽게 추적할 수 있다.
단방향 데이터 흐름(UDF)
상태는 한 방향으로만 흐르며, 데이터 수정 이벤트는 반대 방향으로 전달된다. 이 패턴을 사용하면 데이터 일관성이 강화되고 디버깅이 쉬워진다.
권장되는 앱 아키텍처는 최소한 두 가지 레이어로 구성된다:
1. UI 레이어: 애플리케이션 데이터를 화면에 표시하는 역할을 한다.
2. 데이터 레이어: 비즈니스 로직을 포함하며 애플리케이션 데이터를 관리한다.
필요에 따라 UI와 데이터 레이어 사이에 도메인 레이어를 추가할 수 있다.
도메인 레이어는 복잡한 비즈니스 로직을 처리하거나 여러 ViewModel에서 재사용되는 로직을 캡슐화한다.
UI 레이어는 애플리케이션 데이터를 화면에 표시하고, 사용자 상호작용이나 외부 입력에 따라 UI를 업데이트한다. UI는 뷰나 Jetpack Compose로 구축되며, 상태 홀더(ViewModel)가 데이터를 관리한다.
데이터 레이어는 비즈니스 로직과 데이터 소스를 관리하는 저장소로 구성된다. 저장소는 앱의 나머지 부분에 데이터를 제공하고, 데이터 변경을 한 곳에 집중시켜 충돌을 방지한다.
도메인 레이어는 선택적으로 추가되며, 여러 ViewModel에서 재사용되는 복잡한 비즈니스 로직을 캡슐화한다. 이 레이어는 필요에 따라 추가하며, 반드시 모든 앱에 필요한 것은 아니다.
Single Source of Truth(SSOT)는 애플리케이션에서 데이터의 유일한 출처를 의미한다.
즉, 데이터는 한 곳에서만 관리되고, 그 데이터에 대한 변경 사항이 생기면 앱 전체에 일관되게 반영된다.
이를 통해 여러 곳에서 동일한 데이터를 중복으로 관리할 필요 없이, 오류를 줄이고 데이터 일관성을 유지할 수 있다.
SSOT는 일반적으로 데이터 레이어에서 사용되며, 이 데이터는 UI와 비즈니스 로직에서 참조된다.
예를 들어, ViewModel이나 리포지토리가 데이터의 SSOT로서 역할을 하여, 데이터를 관리하고 UI에 제공하는 구조로 동작한다.
최신 Android 앱 아키텍처는 다음을 권장한다:
앱 클래스는 다른 클래스에 종속되며, 이를 관리하기 위한 패턴으로 종속성 주입(DI)이나 서비스 로케이터를 사용할 수 있다. DI 패턴을 따르는 것이 일반적으로 권장되며, Android에서는 Hilt 라이브러리를 사용한다.
UI 요소와 상태 보유자를 포함하는 레이어로, 사용자와 상호작용하는 모든 부분을 처리한다.
Jetpack Compose
에서 사용되는 Button
, Text
, Column
과 같은 요소들이다. 사용자는 이 요소들과 상호작용하며, UI는 데이터를 기반으로 화면을 업데이트한다.ViewModel
이 상태를 보유하며, UI에 필요한 데이터를 제공하고 그 데이터를 업데이트하는 로직을 포함한다. ViewModel은 UI 요소와 데이터를 연결해 주는 중간 다리 역할을 한다.이 레이어에서는 사용자가 앱을 사용하며 보게 되는 화면과 상호작용하는 요소들을 다룬다.
중요한 점은, 이 레이어는 데이터나 비즈니스 로직을 직접 다루지 않고, 그 책임은 다른 레이어에서 관리된다는 것이다.
비즈니스 로직을 처리하는 레이어로, 앱의 핵심 로직을 담당한다. 이 레이어는 선택적이며, 복잡한 비즈니스 로직이 필요하지 않다면 생략할 수 있다.
이 레이어는 UI와 데이터 레이어 사이에서 비즈니스 로직을 중재하며, 재사용 가능한 로직을 제공하는 것이 특징이다. 이를 통해 UI 레이어는 단순히 데이터를 가져와서 화면에 표시하는 역할에 집중할 수 있다.
리포지토리와 데이터 소스를 포함하며, 앱이 처리하는 모든 데이터를 관리하는 레이어다.
Repositories (리포지토리): 데이터의 중간 관리자 역할을 하며, 여러 데이터 소스로부터 데이터를 가져오고 필요한 처리를 한다. 예를 들어, 리포지토리는 네트워크 API, 로컬 데이터베이스, 또는 캐시에서 데이터를 가져올 수 있다. 또한, 데이터를 가공하고 도메인 레이어나 UI 레이어에 적합한 형태로 전달하는 역할도 수행한다.
Data Sources (데이터 소스): 실제 데이터를 가져오는 곳이다. 대표적으로는 로컬 데이터베이스, 네트워크 API, 파일 시스템, 캐시 등이 있다. 리포지토리는 이 데이터 소스와 상호작용하여 필요한 데이터를 가져오고, 이를 도메인 레이어나 UI 레이어에 제공한다.
데이터 레이어는 앱에서 데이터를 처리하는 모든 로직을 담당한다. 이 레이어는 데이터의 출처나 저장소에 대한 세부 사항을 숨기고, 상위 레이어에 추상화된 데이터 액세스 방법을 제공하여 관심사의 분리를 강화한다.