일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 2022 플러터 설치
- 스택 큐 차이
- android ar 개발
- 안드로이드 라이선스 종류
- 안드로이드 유닛 테스트
- 안드로이드 유닛테스트란
- jvm이란
- android retrofit login
- 객체
- 플러터 설치 2022
- 안드로이드 레트로핏 사용법
- 2022 플러터 안드로이드 스튜디오
- ANR이란
- 스택 자바 코드
- 자바 다형성
- 안드로이드 레트로핏 crud
- jvm 작동 원리
- rxjava disposable
- 멤버변수
- 서비스 쓰레드 차이
- 안드로이드 유닛 테스트 예시
- 클래스
- 큐 자바 코드
- 서비스 vs 쓰레드
- rxjava hot observable
- Rxjava Observable
- 안드로이드 라이선스
- rxjava cold observable
- ar vr 차이
- 안드로이드 os 구조
- Today
- Total
나만을 위한 블로그
[Android] 안드로이드 앱 아키텍처 가이드 본문
안드로이드 디벨로퍼에선 앱 아키텍처 가이드 문서들을 통해 안드로이드 개발자들에게 권장하는 아키텍처를 설명하고 있다.
https://developer.android.com/topic/architecture?hl=ko
https://developer.android.com/topic/architecture/recommendations?hl=ko
어떤 책에서도 그랬듯 앱은 크게 UI, 도메인, 데이터 계층의 3계층으로 구분할 수 있다.
이 포스팅에선 디벨로퍼를 바탕으로 안드로이드가 권장하는 아키텍처의 특징을 알아본다. 각 계층 별 특징은 추후 각각 별도의 포스팅으로 정리할 예정이다.
안드로이드 앱에는 흔히 말하는 4대 컴포넌트와 여러 다양한 구성요소들이 들어가 있다. 그리고 핸드폰의 메모리 등 자원은 데스크탑보다 제한적이기 때문에 안드로이드 OS는 메모리가 부족해지면 언제든 앱 프로세스를 종료하거나 리프레시 현상을 일으킬 수 있다.
OS가 일으키는 현상은 앱 개발자가 제어할 수 없어서 앱 구성요소에 데이터, 상태를 저장하면 안 되고 앱 구성요소가 서로 종속되면 안된다.
그럼 앱을 어떻게 설계해야 좋을까? 앱이 견고해지고 쉬운 테스트가 가능하도록 설계해야 한다.
디벨로퍼에서 말하는 일반 아키텍처 원칙 4가지를 제시한다.
관심사 분리
가장 중요한 원칙이다. 액티비티 / 프래그먼트에 모든 코드를 작성하는 실수는 흔하게 발생한다.
관심사 분리는 뭘까? 디벨로퍼에 이 항목에 대한 설명은 없으니 위키백과에서 뭘 말하는지 확인해 본다.
https://en.wikipedia.org/wiki/Separation_of_concerns
컴퓨터 프로그램을 별개의 섹션으로 분리하는 설계 원칙. 각 섹션은 코드에 영향을 주는 정보인 관심사를 다룬다. 관심사는 앱의 하드웨어 세부 정보 같이 일반적인 것일 수 있고, 인스턴스화할 클래스 이름 같이 구체적인 것일 수도 있다. 관심사 분리를 잘 구현한 프로그램을 모듈형 프로그램이라고 한다. 관심사 분리는 잘 정의된 인터페이스를 가진 코드 섹션 안에 정보를 캡슐화해서 달성된다. 계층화된 설계(프레젠테이션 계층, 비즈니스 로직 계층, 데이터 접근 계층 등)는 관심사 분리의 또 다른 구현이다
한 파트가 하나의 일(관심사)만 집중 관리하게 만들고, 이 파트들이 모여서 앱을 만들면 이 앱은 관심사 분리가 잘 되어있다고 부를 수 있을 것이다.
UI 기반의 클래스는 UI와 OS 상호작용을 처리하는 로직만 갖고 있는 가벼운 상태여야 한다. 그래야 구성요소 생명주기와 관련된 여러 문제를 피하고, 클래스의 테스트 가능성을 개선해서 테스트하기 쉬운 앱을 만들 수 있다.
예를 들어 컨텍스트를 MainActivity로 직접 캐스팅하거나, 특정 액티비티 / 프래그먼트가 없으면 작동하지 않는 함수가 있다면 관심사 분리를 지키지 않는 것이다.
데이터 모델에서 UI 도출하기
가급적 지속적인 데이터 모델(Room, 쉐어드 등)을 권장한다. 데이터 모델은 앱의 데이터를 나타내고 앱의 UI 요소, 기타 구성요소로부터 독립돼 있다. 즉 UI 및 앱 컴포넌트 생명주기와 무관하지만 OS가 메모리에서 앱을 지우기로 했다면 데이터 모델도 사라진다.
지속 모델이 이상적인 이유는 2가지다.
- OS가 리소스 확보를 위해 앱을 제거해도 사용자 데이터가 삭제되지 않음
- 네트워크 연결이 취약하거나 연결이 안 돼 있어도 앱이 계속 작동함
그래서 데이터 모델 클래스(data class를 말하는 듯하다)를 기반으로 앱 아키텍처를 구축하면 앱 테스트 가능성, 견고성이 더 높아진다.
단일 소스 저장소(SSOT)
데이터 수정, 변경은 한 곳에서만 가능해야 한다. 이를 위해 불변 타입을 통해 데이터를 노출하고, 다른 타입이 호출할 수 있는 이벤트를 받거나 함수를 노출해 데이터를 바꾼다.
이 방식의 이점은 아래와 같다. 데이터 수정이 여러 곳에서 일어나는 코드와 한 곳에서만 일어나는 코드가 있다고 생각했을 때 전자의 경우 어떤 일이 발생할까 생각해 보면 나올 법한 대답들이다.
- 다른 타입이 조작할 수 없게 데이터 보호
- 데이터 변경사항 추적이 쉬워짐 = 버그 발견이 쉬워짐
뷰모델이나 DB가 단일 소스 저장소 역할을 할 수 있다.
단방향 데이터 흐름
단일 소스 저장소 원칙은 단방향 데이터 흐름(UDF) 패턴과 같이 종종 사용된다.
UDF에서 상태는 한 방향으로만 흐르고, 데이터 수정 이벤트는 반대 방향으로 흐른다. 안드로이드에서 상태 or 데이터는 일반적으로 상위 범위 타입에서 하위 범위 타입으로 흐른다.
이벤트는 보통 하위 범위 타입에서 트리거되서 상응하는 데이터 타입의 SSOT까지 도달한다. 버튼 클릭 같은 사용자 이벤트가 UI에서 SSOT로 흐르고, SSOT에선 데이터가 불변 타입으로 수정되고 노출된다.
이 방식은 데이터 일관성을 강화하고 오류 발생 확률을 줄이며 디버그 하기 쉽다. 또한 SSOT 방식의 모든 장점도 제공한다.
그리고 아래가 안드로이드가 현재 권장하는 앱 아키텍처를 다이어그램으로 나타낸 그림이다.
당연히 최소한 UI 레이어, 데이터 레이어는 포함돼 있어야 한다. UI 레이어가 없으면 어디에 데이터를 표시하고, 데이터 레이어가 없으면 UI에 뭘 보여줘야 할까?
화살표는 클래스 간의 종속성을 나타낸다. UI 레이어는 도메인 레이어에 종속되고, 도메인 레이어는 데이터 레이어에 종속된다. 각 레이어에 대한 설명은 추후 포스팅으로 정리할 것이기 때문에 여기선 생략한다.
다이어그램에서 볼 수 있듯 앱의 클래스는 작동하기 위해 다른 클래스에 종속될 수 있다. 이 때 특정 클래스의 종속 항목을 수집하는 데 2가지 디자인 패턴 중 하나를 쓸 수 있다.
- 종속 항목 주입(DI) : 클래스가 자신의 종속 항목을 구성할 필요 없이 종속 항목을 정의할 수 있다. 런타임 시 다른 클래스가 이 종속 항목을 제공해야 함
- 서비스 로케이터 : 클래스가 자신의 종속 항목을 구성하는 대신 종속 항목을 가져올 수 있는 레지스트리 제공
이 패턴들은 코드가 중복되거나 코드에 복잡성을 추가하지 않아도 종속 항목을 관리하기 위한 명확한 패턴을 제공하므로 코드를 확장할 수 있으며, 테스트-프로덕션 구현 간 신속하게 전환이 가능하다.
DI를 구현하려면 Hilt 라이브러리를 사용하라고 디벨로퍼에서 권장하고 있다. 물론 선택은 자유다.
아래는 디벨로퍼에서 말하는 일반적인 권장사항이다.
- 앱 구성요소에 데이터 저장 : 4대 컴포넌트 같은 앱 진입점을 데이터 소스로 지정하지 마라. 대신 그 진입점과 관련된 데이터 일부만 가져오도록 다른 컴포넌트에 맞춰 조정해야 한다. 각 앱 구성요소는 사용자, 기기의 상호작용 및 시스템의 전반적인 현재 상태에 따라 단기간만 지속된다
- Android 클래스의 종속 항목 줄이기 : 앱 컴포넌트는 컨텍스트, 토스트 같은 안드로이드 프레임워크 SDK API를 쓰는 유일한 클래스여야 한다. 앱 컴포넌트와 별도로 앱의 다른 클래스를 추상화하면 테스트 가능성은 높이고 앱 내 커플링은 줄일 수 있다
- 앱의 다양한 모듈 간 책임이 잘 정의된 경계를 만든다 : 네트워크에서 데이터를 가져오는 코드를 코드베이스의 여러 클래스, 패키지 전체에 분산하면 안 된다. 마찬가지로 데이터 캐시, 결합 등 여러 무관한 책임을 같은 클래스에 정의하면 안 된다
- 각 모듈에서 가능하면 적게 노출한다 : 모듈 내부의 구현 세부정보를 노출하는 단축키를 만들면 안 된다. 코드베이스가 발전함에 따라 기술적 문제가 여러 번 발생할 수 있다
- 다른 앱과 차별되도록 앱의 고유한 핵심에 초점을 맞춘다 : 같은 보일러 플레이트 코드를 반복 작성하느라 시간 버리지 마라. 앱을 독특하게 만드는 데 집중하고 보일러 플레이트는 제트팩, 기타 권장 라이브러리가 처리하게 하라
- 가능한 관련성이 높은 최신 데이터를 보존한다 : 기기가 오프라인일 때도 유저가 앱 기능을 쓸 수 있다. 모든 유저가 끊김 없고 속도가 빠른 연결을 사용하진 않는다. 끊김 없고 속도가 빠르더라도 혼잡한 곳에선 수신 상태가 안 좋을 수 있다
다음은 디벨로퍼에서 제공하는 예시들의 깃허브 링크들이다. 컴포즈로 구현된 게 흠이라면 흠이다.
https://github.com/android/architecture-templates/tree/multimodule
https://github.com/android/architecture-templates/tree/base
https://github.com/android/architecture-samples/tree/main
'Android' 카테고리의 다른 글
[Android] 앱 아키텍처 - 도메인 레이어란? (0) | 2024.05.12 |
---|---|
[Android] 앱 아키텍처 - UI 레이어란? (0) | 2024.05.06 |
[Android] 모바일 브라우저 앱에서 웹뷰 디버깅하는 법 (0) | 2024.04.15 |
[Android] 프래그먼트의 생명주기 (0) | 2024.03.31 |
[Android] 프래그먼트란? 프래그먼트 기본 사용법 (0) | 2024.03.26 |