안드로이드 앱에는 액티비티, 프래그먼트, 서비스 , Contents Provider, BroadCast Receiver를 비롯한 여러 앱 컴포넌트 포함 -> 매니페스트에서 앱 컴포넌트의 대부분을 선언.
Android OS는 매니페스트 파일을 사용하여 앱을 기기의 사용자 경험에 통합하는 방법 결정.
모바일 기기에는 리소스가 제한되어 있으므로 언제든지 운영 체제에서 새 프로세스를 위한 공간을 확보하기 위해 일부 앱 프로세스를 종료할 수 있음.
👉 이러한 환경에서는 앱 컴포넌트가 개별적이고 순서 없이 실행될 수 있으며 운영체제나 사용자가 언제든지 해당 컴포넌트 요소를 파괴할 수 있음. 이러한 이벤트는 사용자가 제어할 수 없으므로 앱 컴포넌트의 애플리케이션 데이터나 상태를 메모리에 저장하거나 유지해서는 안되며 앱 컴포넌트가 서로 종속되어서는 안됨.
안드로이드 앱 크기가 커짐에 따라 앱의 확장을 허용하고 견고성을 높이며 더 쉽게 테스트할 수 있는 아키텍처를 정의하는 것이 중요.
몇 가지 특정 원칙을 따르도록 앱 아키텍처를 설계해야 함.
지속적 모델이 이상적인 이유
이 패턴의 여러가지 이점
오프라인 우선 애플리케이션에서 애플리케이션 데이터의 정보 소스는 일반적으로 데이터베이스임. 다른 경우에는 정보의 출처가 ViewModel이거나 UI일 수도 있음.
단일 소스 저장소 원칙은 Google 가이드에서 종종 단방향 데이터 흐름(UDF) 패턴과 함께 사용됨. UDF에서 state는 한 방향으로만 흐름. 데이터를 수정하는 이벤트는 반대 방향으로 흐름.
Android에서 state 또는 데이터는 일반적으로 계층 구조의 상위 범위 유형에서 하위 범위 유형으로 흐름.
이벤트는 보통 하위 범위 유형에서 트리거되어 상응하는 데이터 유형의 SSOT에 도달.
(예: 애플리케이션 데이터는 보통 데이터 소스에서 UI로 흐름. 버튼 누르기와 같은 사용자 이벤트는 UI에서 SSOT로 흐르며 SSOT에서는 애플리케이션 데이터가 불변 타입으로 수정 및 노출됨.)
이와 같은 패턴은 데이터 일관성을 강화하고 오류가 발생할 확률을 줄여주며 디버그하기 쉽고 SSOT패턴의 모든 이점을 제공함.
일반적인 아키텍처 원칙을 고려하면 애플리케이션에는 최소한 두 개의 레이어가 있어야 함.
도메인 레이어라는 추가 레이어를 추가하여 UI와 데이터 레이어 간의 상호작용을 단순화하고 재사용할 수도 있음.
⭐️ 참고: 이 가이드의 다이어그램에 있는 화살표는 클래스 간 종속성을 나타냄. 예를 들어 도메인 계층은 데이터 계층 클래스에 따라 달라짐.
최신 앱 아키텍처는 다음 기술을 사용하도록 권장함.
UI 레이어 (또는 프리젠테이션 레이어)의 역할은 데이터를 화면에 표시하는 것.
사용자 상호 작용(예: 버튼 누르기)이나 외부 입력(예: 네트워크 응답)으로 인해 데이터가 변경될 때마다 UI는 변경 사항을 반영하도록 업데이트되어야 함.
📝 UI 레이어 구성
데이터 계층에는 비즈니스 로직이 포함되어 있음.
비즈니스 로직은 앱이 데이터를 생성, 저장, 변경하는 방법을 결정하는 규칙으로 구성됨.
데이터 계층은 각각 0~여러개의 데이터 소스를 포함할 수 있는 repository로 구성됨.
앱에서 처리하는 다양한 데이터 유형마다 저장소(repository) 클래스를 만들어야 함.
(예: 영화에 관련된 데이터에 대한 MoviesRepository 클래스 생성 or 결제 관련 데이터에 대한 PaymentsRepository 클래스 생성)
📝 리포지토리 클래스 역할
각 데이터 소스 클래스는 파일, 네트워크 소스 또는 로컬 데이터베이스 등 하나의 데이터 소스에 대해서만 작업을 담당해야함.
데이터 소스 클래스는 데이터 작업을 위해 앱과 시스템 사이를 연결하는 다리.
도메인 레이어는 UI와 데이터 레이어 사이에 있는 optional 레이어.
도메인 레이어는 복잡한 비즈니스 로직 또는 여러 ViewModel에서 재사용되는 간단한 비즈니스 로직을 캡슐화하는 역할 담당.
모든 앱에 이러한 요구사항이 있는 것은 아니므로 이 계층은 선택사항임.
(복잡성을 처리하거나 재사용성을 선호하는 등 필요한 경우에만 사용해야 함.)
이 계층의 클래스는 일반적으로 사용 사례 또는 상호 작용자라고 함. (use cases or interactors)
예를 들어 ViewModel이 시간대를 사용하여 화면에 적절한 메시지를 표시하는 경우 앱에 GetTimeZoneUseCase 클래스가 있을 수 있음.
앱의 클래스는 제대로 동작하기 위해 다른 클래스에 의존함. 아래의 디자인 패턴 중 하나를 사용하여 특정 클래스의 종속 항목을 수집할 수 있음.
의존성 주입(DI): 의존성 주입을 통해 클래스는 자신의 종속 항목을 구성할 필요 없이 종속 항목을 정의할 수 있음. 런타임 시 다른 클래스가 이 종속 항목을 제공해야 함.
Service locator: 서비스 로케이터 패턴은 클래스가 자신의 종속 항목을 구성하는 대신 종속 항목을 가져올 수 있는 레지스트리를 제공함.
이러한 패턴을 사용하면 코드를 복제하거나 복잡성을 추가하지 않아도 종속 항목을 관리하기 위한 명확한 패턴을 제공하므로 코드를 확장할 수 있음. 또한 테스트 구현과 프로덕션 구현을 빠르게 전환할 수 있음.
의존성 주입 패턴을 따르고 Android 앱에서 Hilt 라이브러리를 사용하는 것이 좋음.
Hilt는 종속성 트리를 탐색하여 자동으로 객체를 생성하고 종속성에 대한 컴파일 시간 보장을 제공하며 Android 프레임워크 클래스용 종속성 컨테이너를 만듦.
권장사항은 아니지만 다음을 따르면 대부분의 경우 장기적으로 코드 기반이 더욱 강력하고 테스트 가능하며 유지관리가 가능해짐.
앱 컴포넌트에 데이터를 저장하지 말것.
액티비티, 서비스, 브로드캐스트 리시버 등 앱의 진입점을 데이터 소스로 지정하지 말 것.
대신 그 진입점과 관련된 데이터 일부만 가져오도록 다른 컴포넌트에 맞춰 조정해야 함.
각 앱 컴포넌트는 사용자와 기기의 상호작용 및 시스템의 전반적인 현재 상태에 따라 단기간만 지속됨 (수명이 짧음).
Android 클래스에 대한 종속성을 줄일 것.
앱 컴포넌트는 Context 또는 Toast 같은 Android 프레임워크 SDK API를 사용하는 유일한 클래스여야함. 앱 컴포넌트와 별도로 앱의 다른 클래스를 추상화하면 테스트 가능성은 높아지고 앱 내의 결합이 줄어듦.
앱의 다양한 모듈 간 책임이 잘 정의된 경계를 만들 것.
예를 들어 네트워크에서 데이터를 로드하는 코드를 코드베이스의 여러 클래스나 패키지에 분산시키지 말 것. 마찬가지로 동일한 클래스에서 데이터 캐싱 및 데이터 바인딩과 같이 관련되지 않은 여러 책임을 정의하지 말 것.
각 모듈에서 가능한 적게 노출할 것
예를 들어 모듈의 내부 구현 세부 정보를 노출하는 바로가기를 만들지 말 것.
코드베이스가 발전함에 따라 기술 결함이 여러 번 발생할 가능성이 높음.
다른 앱과 차별되도록 앱의 고유한 핵심에 초점을 맞출 것
동일한 상용구 코드를 반복하여 작성하지 말 것. 대신 앱을 독특하게 만드는 데 시간과 에너지를 집중하고 반복적인 상용구는 Jetpack 라이브러리와 기타 권장 라이브러리가 처리하도록 할 것.
앱의 각 부분을 독립적으로 테스트하는 방법을 고려할 것
예를 들어 네트워크에서 데이터를 가져오기 위해 API를 잘 정의하면 해당 데이터를 로컬 데이터베이스에 보존하는 모듈을 더 쉽게 테스트할 수 있음.
그러지 않고 두 모듈의 로직을 한 위치에 혼합하거나, 네트워크 코드를 전체 코드베이스에 분산하면 테스트가 불가능하지는 않을지라도 훨씬 더 어려워짐.
유형은 동시성 정책을 담당.
유형이 장기 실행 차단 작업을 수행하는 경우 해당 계산을 올바른 스레드로 이동하는 역할을 담당해야함. 이 유형은 자신이 실행하는 컴퓨팅 유형이 무엇이고 어느 스레드에서 실행되어야 하는지를 알고 있음. 유형은 기본 안전성을 갖춰야함. 즉, 기본 스레드에서 차단 없이 안전하게 호출될 수 있어야함.
가능한 한 관련성이 높은 최신 데이터를 보존할 것
이렇게 하면 기기가 오프라인 모드일 때도 사용자가 앱의 기능을 이용할 수 있음. 모든 사용자가 끊김 없고 속도가 빠른 연결을 사용하지는 않는다는 점에 유의할 것. 끊김 없고 속도가 빠르더라도 혼잡한 곳에서는 수신 상태가 좋지 않을 수 있음.