타이탄의 도구들

[Android] AAC 정리 1편 - 앱 아키텍쳐 개요 본문

Dev Tools/Android

[Android] AAC 정리 1편 - 앱 아키텍쳐 개요

Titan04 2022. 6. 6. 12:48
728x90

모바일 앱 사용자 환경

일반적인 Android 앱에는 액티비티, 프래그먼트, 서비스, 콘텐트 프로바이더를 비롯하여

여러 앱 컴포넌트가 포함됩니다.

개발자는 매니페스트에서 이러한 앱 컴포넌트 대부분을 선언하며,

Android OS에서 이 파일을 사용하여

기기의 전반적인 사용자 환경에 앱을 통합하는 방법을 결정합니다.

일반적인 Android 앱은 여러 구성요소를 포함할 수 있고,

사용자는 짧은 시간 내에 여러 앱과 상호작용할 때도 많다는 점을 고려하면,

앱은 사용자 중심의 다양한 워크플로 및 작업에 맞게 조정될 수 있어야 합니다.

또한 휴대기기는 리소스가 제한되어 있으므로,

운영체제에서 새로운 앱을 위한 공간을 확보하도록

언제든지 일부 앱 프로세스를 종료해야 할 수 있습니다.

이러한 환경 조건을 고려해 볼 때

앱 컴포넌트는 개별적이고 비순차적으로 실행될 수 있으며,

운영체제나 사용자가 언제든지 앱 구성요소를 제거하는것이 가능합니다.

이러한 이벤트는 직접 제어할 수 없기 때문에

앱 컴포넌트에 애플리케이션 데이터나 상태를 저장해서는 안 되며

앱 컴포넌트가 서로 종속되면 안 됩니다.

일반 아키텍처 원칙

애플리케이션 데이터와 상태를 저장하는 데 앱 컴포넌트를 사용할 수 없다면

앱을 대신 어떻게 설계해야 할까요?

Android 앱은 크기가 커지기 때문에

앱을 확장하고 앱의 견고성을 높이며 앱을 더 쉽게 테스트할 수 있도록 아키텍처를 정의하는 것이 중요합니다.

앱 아키텍처앱의 부분과 그 각 부분에 필요한 기능 간의 경계를 정의합니다.

위에 언급된 요구사항을 충족하려면

몇 가지 특정 원칙을 준수하도록 앱 아키텍처를 설계해야 합니다.

1) 관심사 분리(Seperation Of Concern)

따라야 할 가장 중요한 원칙은 관심사 분리입니다.

액티비티 또는 프래그먼트에 모든 코드를 작성하는 실수는 흔히 일어납니다.

이러한 UI 기반의 클래스는 UI 및 운영체제 상호작용을 처리하는 로직만 포함해야 합니다.

이러한 클래스를 최대한 가볍게 유지하여 구성요소 수명 주기와 관련된 많은 문제를 피하고

그러한 클래스의 테스트 가능성을 개선할 수 있습니다.

액티비티 및 프래그먼트는 Android OS와 앱 사이를 이어주는 클래스일 뿐입니다.

OS는 사용자 상호작용을 기반으로 또는 메모리 부족과 같은 시스템 조건으로 인해

언제든지 액티비티 및 프래그먼트 클래스를 제거할 수 있습니다.

만족스러운 사용자 환경과 더욱 수월한 앱 관리 환경을 제공하려면

액티비티 및 프래그먼트 클래스에 대한 의존성을 최소화하는 것이 좋습니다.

2) 데이터 모델에서 UI 도출하기

또 하나의 중요한 원칙은 데이터 모델에서 UI를 도출해야 한다는 것입니다.

가급적 지속적인 모델(Persistent models)을 권장합니다. 데이터 모델은 앱의 데이터를 나타냅니다.

이들은 앱의 UI 요소 및 기타 구성요소와 독립되어 있습니다.

즉, 이들은 UI 및 앱 구성요소 수명 주기와는 관련이 없습니다.

하지만 OS에서 메모리에서 앱의 프로세스를 삭제하기로 결정하면 여전히 삭제됩니다.

지속적인 모델(Persistent models)이 이상적인 이유는 다음과 같습니다.

- Android OS에서 리소스를 확보하기 위해 앱을 제거해도 사용자 데이터가 삭제되지 않습니다.

- 네트워크 연결이 취약하거나 연결되어 있지 않아도 앱이 계속 작동합니다.

앱 아키텍처를 데이터 모델 클래스에 기반하는 경우 앱의 테스트 가능성과 견고성이 더 높아집니다.

권장 앱 아키텍처

이 섹션에서는 다음의 권장사항에 따라 앱을 구조화하는 방법을 보여줍니다.

참고: 이 페이지의 추천 사항과 권장사항은 광범위한 앱에 적용되며,

이를 기반으로 앱을 확장하고, 품질 및 견고성을 개선하고, 더욱 손쉽게 테스트할 수 있습니다.

도움말은 가이드라인으로 간주하고 필요에 따라 요건에 맞게 조정해야 합니다.

이전 섹션에서 언급된 일반적인 아키텍처 원칙을 고려하여

각 애플리케이션에는 레이어가 두 개 이상 있어야 합니다.

-화면에 애플리케이션 데이터를 표시하는 UI 레이어

-앱의 비즈니스 로직을 포함하고 애플리케이션 데이터를 노출하는 데이터 레이어

-UI와 데이터 레이어 간의 상호작용을 간소화하고 재사용하기 위한

도메인 레이어라는 레이어를 추가할 수 있습니다.

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

참고: 이 가이드에 있는 다이어그램의 화살표는 클래스 간의 종속 항목을 나타냅니다.

예를 들어 도메인 레이어는 데이터 레이어 클래스에 의존합니다.

UI 레이어

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

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

1) 화면에 데이터를 렌더링하는 UI 요소.

이러한 요소는 View (Activity, Fragment 등) 또는 Jetpack Compose 함수를 사용하여

빌드할 수 있습니다.

2) 데이터를 보유하고 이를 UI에 노출하며 로직을 처리하는 State holders (예: ViewModel 클래스)

그림 2. 앱 아키텍처에서 UI 레이어의 역할

데이터 레이어

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

비즈니스 로직

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

데이터 레이어는 0개부터 여러 개의 데이터 소스를 각각 포함할 수 있는 저장소(Repositories)로

구성됩니다. 앱에서 처리하는 다양한 유형의 데이터마다 저장소 클래스를 만들어야 합니다.

예를 들어 영화 관련 데이터에는 MoviesRepository 클래스 또는 결제 관련 데이터에는 PaymentsRepository 클래스를 만들 수 있습니다.

그림 3. 앱 아키텍처에서 데이터 레이어의 역할

저장소(Repositories) 클래스에서 담당하는 작업은 다음과 같습니다.

- 앱의 나머지 부분에 데이터 노출

- 데이터 변경사항을 한 곳에 집중

- 여러 데이터 소스 간의 충돌 해결

- 앱의 나머지 부분에서 데이터 소스 추상화

- 비즈니스 로직 포함

데이터 소스 클래스

파일, 네트워크 소스, 로컬 데이터베이스 같은 하나의 데이터 소스만 사용해야 합니다.

데이터 소스 클래스는 데이터 작업을 위해 애플리케이션과 시스템 간의 가교 역할을 합니다.

도메인 레이어

도메인 레이어는 UI 레이어와 데이터 레이어 사이에 있는 선택적 레이어입니다.

도메인 레이어는

복잡한 비즈니스 로직, 또는 여러 ViewModel에서 재사용되는

간단한 비즈니스 로직의 캡슐화를 담당합니다.

모든 앱에 이러한 요구사항이 있는 것은 아니므로 이 레이어는 선택사항입니다.

따라서 복잡성을 처리하거나 재사용성을 선호하는 등

필요한 경우에만 도메인 레이어를 사용해야 합니다.

그림 4. 앱 아키텍처에서 도메인 레이어의 역할

이 레이어의 클래스는 일반적으로 use cases 또는 interactors 라고 합니다.

use case에서는 하나의 기능을 담당해야 합니다.

예를 들어 여러 ViewModel에서 시간대를 사용하여 화면에 적절한 메시지를 표시하는 경우

앱에는 GetTimeZoneUseCase 클래스가 있을 수 있습니다.

앱 컴포넌트간 의존성 관리

앱의 클래스는 올바른 작동을 위해 다른 클래스에 종속됩니다.

특정 클래스의 의존성을 가질 때 다음 디자인 패턴 중 하나를 사용할 수 있습니다.

의존성 주입(DI) : 의존성 주입을 사용하면 클래스가 자신의 의존성을 구성할 필요 없이

의존성을 정의할 수 있습니다. 런타임 시 다른 클래스가 이 의존성을 제공해야 합니다.

서비스 로케이터 : 서비스 로케이터 패턴은 클래스가 자신의 의존성을 구성하는 대신

의존성을 가져올 수 있는 레지스트리를 제공합니다.

이 패턴은 코드를 중복하거나 복잡성을 추가하지 않아도

의존성을 관리하기 위한 명확한 패턴을 제공하므로 코드를 확장할 수 있습니다.

또한 이러한 패턴을 사용하면 테스트와 프로덕션 구현 간에 신속하게 전환할 수 있습니다.

종속 항목 삽입 패턴을 따르고 Android 앱에서 Hilt 라이브러리를 사용하는 것이 좋습니다.

Hilt는 의존성 트리를 따라 이동하여 객체를 자동으로 구성하고

의존성의 컴파일 시간을 보장하며 Android 프레임워크 클래스의 의존성 컨테이너를 만듭니다.

일반 권장사항

프로그래밍은 창조적인 분야이며 Android 앱 제작도 예외가 아닙니다.

문제를 해결하는 방법은 여러 가지가 있습니다.

즉, 여러 액티비티나 프래그먼트 간에 데이터를 교환하거나,

원격 데이터를 가져와서 오프라인 모드에서 사용하도록 데이터를 로컬에 보존하거나,

중요한 앱에서 발생하는 다른 일반적인 시나리오를 그 수에 관계없이 처리하는 것입니다.

다음 권장사항은 필수는 아니지만,

대부분의 경우 그러한 권장사항을 따르면

장기적으로 더 강력하고, 테스트 및 유지관리가 쉬운 코드베이스를 만들 수 있습니다.

1) 앱 컴포넌트에 데이터를 저장하지 마세요

액티비티, 서비스, 브로드캐스트 리시버와 같은 앱의 진입점을 데이터 소스로 지정하지 마세요.

대신 그 진입점과 관련된 데이터 일부만 가져오도록 다른 컴포넌트에 맞춰 조정해야 합니다.

각 앱 컴포넌트는 사용자와 기기의 상호작용 및 시스템의 전반적인 현재 상태에 따라

매우 단기간만 지속됩니다.

2) Android 클래스 끼리의 의존성을 줄입니다.

앱 컴포넌트는 Context 또는 Toast 같은 Android 프레임워크 SDK API를 사용하는

유일한 클래스여야 합니다.

앱 컴포넌트와 별도로 앱의 다른 클래스를 추상화하면

테스트 가능성은 높이고 앱 내의 의존 관계는 줄일 수 있습니다.

3) 앱의 다양한 모듈 간 기능이 잘 설정된 바운더리(경계)를 만듭니다.

예를 들어 네트워크에서 데이터를 로드하는 코드를

코드베이스의 여러 클래스나 패키지 전체에 분산하면 안 됩니다.

마찬가지로 데이터 캐시와 데이터 결합 등

여러 개의 관련 없는 기능을 동일한 클래스에 정의하면 안 됩니다.

즉, 한 클래스당 한 기능을 담당하는 것이 좋습니다.

4) 각 모듈에서 가능하면 적게 노출합니다.

예를 들어 모듈의 내부 구현 세부정보를 노출하는 단축키를 만들어서는 안 됩니다.

단기적으로는 약간의 시간을 벌 수 있지만,

코드베이스가 발전함에 따라 기술적 문제가 여러 번 발생할 수 있습니다.

5) 다른 앱과 차별되도록 앱의 고유한 컨텐츠에 초점을 맞춥니다.

동일한 상용구 코드를 반복하여 작성하느라 시간을 낭비하지 마세요.

대신 앱을 독특하게 만드는 데 시간과 에너지를 집중하고

반복적인 상용구는 Jetpack 라이브러리와 기타 권장 라이브러리가 처리하도록 하세요.

6) 앱의 각 부분을 독립적으로 테스트하는 방법을 고려합니다.

예를 들어 네트워크에서 데이터를 가져오기 위해 API를 잘 정의하면

해당 데이터를 로컬 데이터베이스에 보존하는 모듈을 더 쉽게 테스트할 수 있습니다.

그러지 않고 두 모듈의 로직을 한 위치에 혼합하거나,

네트워크 코드를 전체 코드베이스에 분산하면

효과적인 테스트가 불가능하지는 않을지라도 훨씬 더 어려워집니다.

7) 가능한 한 관련성이 높은 최신 데이터를 보존합니다.

이렇게 하면 기기가 오프라인 모드일 때도 사용자가 앱의 기능을 이용할 수 있습니다.

모든 사용자가 끊김 없고 속도가 빠른 연결을 사용하지는 않는다는 점에 유의하세요.

끊김 없고 속도가 빠르더라도 혼잡한 곳에서는 수신 상태가 좋지 않을 수 있습니다.

728x90
Comments