SW 공부노트

[안드로이드] 앱 아키텍처 가이드 본문

안드로이드/아키텍처 & 라이브러리

[안드로이드] 앱 아키텍처 가이드

요빈 2023. 3. 31. 17:06

https://developer.android.com/jetpack/guide?hl=ko 

 

앱 아키텍처 가이드  |  Android 개발자  |  Android Developers

앱 아키텍처 가이드 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 이 가이드에는 고품질의 강력한 앱을 빌드하기 위한 권장사항 및 권장 아키텍처가 포함

developer.android.com

이 글은 안드로이드 developers의 앱 아키텍처 가이드를 공부하며 작성한 글입니다.


App Architecture Guide

 

모바일 앱 사용자 환경(Mobile app user experience)

일반적인 안드로이드 앱에는 Activity, Fragment, service, content provider, broadcast receiver 등 여러 *앱 컴포넌트들로 구성된다. 개발자는 앱 Manifest에 이러한 구성요소를 선언하며, AOS에서 이 파일을 사용해 기기의 전반적인 사용자 환경에 앱을 통합하는 방법을 결정한다.

 

일반적인 Android 앱은 여러 구성요소를 포함할 수 있고, 짧은 시간 내 여러 앱과 상호작용하는 경우가 많다는 걸 고려하면, 앱은 사용자 중심의 다양한 워크플로 및 작업에 맞게 조정될 수 있어야 한다.

  • 앱에서 앱으로 바꾸는 동작(카메라에서 파일 선택, 다른 앱 사용 중 전화)이 일반적이므로 흐름을 잘 처리해야
  • 휴대기기의 리소스는 제한적이기 때문에 운영체제에서 공간 확보를 위해 언제든 일부 앱 프로세스를 종료할 수 있다.

 

이러한 환경을 고려해볼 때 앱 컴포넌트개별적이고 비순차적으로 실행될 수 있으며, 운영체제나 사용자가 언제든 앱 컴포넌트를 제거할 수 있다. 이러한 이벤트는 개발자가 직접 제어할 수 없기 때문에 앱 컴포넌트에 애플리케이션 데이터나 상태를 저장해서는 안되며, 앱 컴포넌트가 서로 종속되면 안된다.

 

* 앱 컴포넌트는 Android 앱의 필수적인 기본 구성 요소이다.

각 유형은 뚜렷한 목적과 각자의 수명주기가 있어 생성 및 소멸 방식을 정의한다.

  • Activity, Fragment: 사용자와 상호작용하는 화면
  • service: 백그라운드에서 실행되는 컴포넌트로, 오랫동안 실행되는 작업을 수행하거나 원격 프로세스 작업을 수행
  • content provider: 파일 시스템, SQLite DB 등 모든 앱 데이터를 관리하는 컴포넌트
  • broadcast receiver: 외부에서 일어나는 이벤트를 앱에 전달하는 컴포넌트로, 브로드캐스트 알림에 응답할 수 있게 한다.

일반 아키텍처 원칙(Common Architectural principles)

안드로이드 앱을 개발하면 앱을 확장하고, 견고성을 높이며 앱을 더 쉽게 테스트할 수 있도록 아키텍처를 정의하는 것이 중요하다. 다음은 아키텍처 설계 시 준수해야할 원칙이다.

 

 

1. 관심사 분리(Separation of concerns)

 

Activity, Fragment에 모든 코드를 작성하면 안된다. 이러한 UI 기반 클래스에서 UI 및 상호작용을 처리하는 로직만 포함해야 한다. 이러한 클래스를 가볍게 유지하여 컴포넌트 수명주기와 관련된 많은 문제를 피하고 테스트 가능성을 개선할 수 있다.

OS는 시스템 상의 문제로 언제든지 클래스를 제거할 수 있기 때문에 이러한 클래스에 대한 의존성을 최소화하는 것이 좋다.

 

즉, 특정 기능에 대한 클래스는 기능과 관련된 로직만 포함해야 한다는 것이다.

이에 따라 다른 클래스와의 의존성을 줄이고 모듈화할 수 있으며, 결과적으로 테스팅이 용이해진다.

 

 

2. 데이터 모델에서 UI 도출하기(Drive UI from a model)

 

데이터 모델은 앱의 데이터를 나타내며, 앱의 UI 요소 및 기타 컴포넌트와 독립되어 있어 UI 및 앱 컴포넌트 수명주기 관련 문제에 영향을 받지 않는다. 안드로이드 가이드에서는 지속적인 모델을 권장한다.

  • OS에서 리소스 확보를 위해 앱을 제거해도 사용자 데이터가 삭제되지 않는다.
  • 네트워크 연결이 취약하거나 연결되어 있지 않아도 앱이 계속 작동한다.

 

즉, 데이터 관리 책임이 잘 정의된 Model(데이터) 클래스를 기반으로 앱을 만들면 테스트가 쉽고, 데이터 일관성을 유지할 수 있다.


권장하는 앱 아키텍처(Recommended app architecture)

 

일반 아키텍처를 따르면 각 애플리케이션에는 레이어가 두 개 이상 있어야 한다.

  • 화면에 애플리케이션 데이터를 표시하는 UI 레이어
  • 앱의 비즈니스 로직을 포함하고, 애플리케이션 데이터를 노출하는 데이터 레이어

 

여기에 추가적으로 UI와 데이터 레이어 간의 상호작용을 간소화하고, 재사용하기 위한 도메인 레이어를 추가할 수 있다.

 

* 최신 앱 아키텍처에서는 다음 기법을 권장한다.

  • 앱의 모든 레이어에서 단방향 데이터 흐름(UDF)
  • State holder가 있는 UI 레이어로의 UI 복잡성 관리
  • Coroutine 및 Flow
  • Dependency Injection(DI, 종속성 주입)

 

일반적인 앱 아키텍처 다이어그램

UI 레이어

 

UI 레이어(프레젠테션 레이어)는 화면에 데이터를 표시하는 역할을 한다. 사용자 상호작용(ex. 버튼 누르기) 또는 외부 입력(ex. 네트워크 응답)으로 인해 데이터가 변할 때마다 변경사항을 반영하도록 업데이트되어야 한다.

 

UI 레이어는 다음 두 가지로 구성된다.

  • UI element: 화면에 데이터 렌더링 -> xml 파일 또는 Compose 사용
  • State holder: 데이터를 보유하고 이를 UI에 노출하며 로직을 처리 -> ViewModel 클래스

 

앱 아키텍처에서의 UI 레이어 역할


데이터 레이어

 

데이터 레이어에는 비즈니스 로직이 포함되어 있다.

비즈니스 로직은 앱에 가치를 부여하는 요소로, 앱 데이터의 생성, 저장, 변경 방식을 결정하는 규칙으로 구성된다.

 

데이터 레이어는 여러 개의 데이터 소스를 각각 포함할 수 있는 리포지터리로 구성된다.

다양한 유형의 데이터마다 리포지터리 클래스를 만들어야 한다.

 

앱 아키텍처에서의 데이터 레이어 역할

 

리포지터리 클래스의 역할은 다음과 같다.

  • 앱의 나머지 부분에 데이터 전달
  • 데이터 변경 사항을 한 곳에 집중
    • 데이터가 업데이트될 때 어디서 데이터를 가져올지, 어떤 API를 호출할 지 알고있음
  • 여러 데이터 소스 간의 충돌 해결
  • 앱의 나머지 부분에서 데이터 소스 추상화
  • 비즈니스 로직 포함

 

쉽게 말해 리포지터리는 다른 레이어를 위한 공공 API라고 할 수 있으며 어플리케이션 데이터에 접근하기 위한 유일한 길이다.

 

데이터 소스 클래스는 파일, 네트워크 소스, 로컬 DB 같은 하나의 데이터 소스를 사용해야 한다.


도메인 레이어

 

도메인 레이어는 비즈니스 로직의 캡슐화를 담당한다.

복잡성을 처리하거나 재사용성을 선호하는 등 필요한 경우에 도메인 레이어를 사용한다. 

 

이 레이어의 클래스는 주로 Use case라고 한다. 각 Use case는 하나의 기능을 담당해야 한다.  

Use case는 ViewModel의 중복된 로직을 제거하고 간단하게 하는 데 사용되며 주로 리포지터리의 데이터를 결합하고 변환한다.

 

예를 들어 여러 ViewModel에서 시간대를 사용하여 화면에 적절한 메시지를 표시하는 경우, ViewModel의 데이터와 관련된 데이터를 리포지터리로부터 가져와 처리를 하는 GetTimeZoneUseCase 클래스가 있을 수 있다. 즉, UseCase 클래스 하나를 사용함으로써 여러 리포지터리를 사용하는 등의 복잡한 로직을 캡슐화해 ViewModel을 보다 깔끔하게 유지할 수 있는 것이다.

 

* 다음은 아키텍처 구조를 직접 확인할 수 있는 샘플 앱이다.

글만으로는 감이 오지 않아서 참고할 예정!

https://github.com/android/architecture-samples

 

GitHub - android/architecture-samples: A collection of samples to discuss and showcase different architectural tools and pattern

A collection of samples to discuss and showcase different architectural tools and patterns for Android apps. - GitHub - android/architecture-samples: A collection of samples to discuss and showcase...

github.com

https://github.com/android/nowinandroid/blob/main/docs/ArchitectureLearningJourney.md

 

GitHub - android/nowinandroid: A fully functional Android app built entirely with Kotlin and Jetpack Compose

A fully functional Android app built entirely with Kotlin and Jetpack Compose - GitHub - android/nowinandroid: A fully functional Android app built entirely with Kotlin and Jetpack Compose

github.com


컴포넌트 간 종속 항목 관리

 

위 이미지들 내 화살표는 클래스 간의 종속성을 나타낸다. 예를 들어 도메인 레이어는 데이터 레이어 클래스에 종속된다.

즉, 도메인 레이어 내 클래스에서 데이터 레이어 클래스의 객체를 사용한다는 의미이다.

 

앱 클래스는 올바른 작동을 위해 다른 클래스에 종속된다. 특정 클래스의 종속 항목을 수집하기 위해서 다음 디자인 패턴을 사용한다.

  • 종속성(의존성) 주입(DI): 종속성을 주입하면 클래스가 자신의 종속 항목을 구성할 필요 없이 종속 항목을 정의할 수 있다. 런타임 시 다른 클래스가 이 종속 항목을 제공해야 한다.
  • 서비스 로케이터(Service locater): 서비스 로케이터는 클래스가 자신의 종속 항목을 구성하는 대신 종속 항목을 가져올 수 있는 레지스트리를 제공한다.

 

이 패턴은 코드 중복 및 복잡성을 줄이고, 종속성을 관리하기 위한 명확한 패턴을 제공하므로 코드를 확장할 수 있다.

안드로이드 공식 문서는 DI 패턴을 따르며 안드로이드 앱에서 Hilt 라이브러리를 사용하는 것을 추천한다.

Hilt는 dependency tree를 따라 이동하며 객체를 자동으로 구성하고, dependency의 컴파일 시간을 보장하며 Android 프레임워크 클래스의 dependency container를 만든다.


앱 아키텍처의 원칙, 권장하는 아키텍처 구조에 대해 공부했다.

리포지터리, 유즈케이스가 가장 헷갈린다...

 

리포지터리는 데이터 레이어에 있으며, 데이터 관련 작업을 한다. 데이터가 필요할 때는 리포지터리를 사용하면 된다. 유즈 케이스는 기능별로 나뉜 클래스로 해당 기능과 관련된 리포지터리를 호출해 기능을 구현한다. 따라서 뷰모델에서 여러 리포지터리를 통해 구현하던 복잡한 코드를 유즈케이스 하나로 해결할 수 있다는 것이다.

 

대략적인 내용은 이해가 가지만 코드에 적용하기까지는 아직 많은 공부가 필요한 것 같다. 안드로이드 공식문서에서 제공하는 아키텍처 샘플 깃허브 링크를 위에 첨부에 두었다. 직접 코드로 아키텍처를 확인할 수 있으니 공부에 많은 도움이 될 것 같다.

 

아키텍처의 모든 부분에서 공통적으로 말하는 것은 유지보수하기 쉽고, 테스트가 용이하게 하는 것이 목표라는 거다.

이런 목표를 이루기 위한 아키텍처 설계에 사용되는 대표적인 패턴이 MVVM이다. 

아키텍처와 AAC 라이브러리를 공부해 MVVM 구현해봐야지...!