안드로이드 개발자 로드맵 Part 4: Design Patterns and Architecture

skydoves·2023년 1월 16일
34
post-thumbnail

원문은 Design Patterns and Architecture: The Android Developer Roadmap – Part 4에서 확인하실 수 있습니다.

저자가 운영 중인 구독형 리파지토리인 Dove Letter를 통해 Android와 Kotlin에 대한 다양한 소식과 학습자료를 접하실 수 있습니다. 관심 있으신 분들은 Dove Letter를 방문해 주시길 바랍니다.

Android developer roadmap은 총 5부로 연재될 예정이며, 각 회차는 안드로이드 생태계의 다양한 측면을 다룹니다. 지난 포스트에서는 Fragment, App Navigation, Architecture Components, Jetpack과 관련한 내용들을 살펴보았습니다.

이번 4부에서는 Android 로드맵의 다음 6가지 섹션을 살펴봅니다.

  1. Design Patterns
  2. Architecture
  3. Asynchronous
  4. Network
  5. Image Loading
  6. Local Storage

향후 게시물에 대한 알림을 받고 싶으시다면, GitHub의 watchers에서 알림 등록을 해주시거나 메인테이너를 팔로우해 주시길 바랍니다.

이제 시작해 보도록 하겠습니다!

Design Patterns (디자인 패턴)

소프트웨어 디자인 패턴은 소프트웨어 엔지니어링에서 반복되고 일반적인 소프트웨어 문제를 해결하기 위한 재사용 가능한 솔루션입니다. 디자인 패턴은 어떤 문제를 해결하느냐에 따라 Creational 패턴, Behavioral 패턴, Concurrency 패턴으로 분류할 수 있습니다.

Android 개발에서는 Dependency InjectionObserver 패턴과 같은 Android 플랫폼의 일반적인 문제를 해결하고 리소스 수명 주기를 관리하기 위해 자주 사용되는 대표적인 디자인 패턴들이 있습니다.

가령, Builder 패턴, Factory Method 패턴, Singleton 패턴과 같은 Creational 패턴들은 다양한 Android framework에서 이미 많이 활용되고 있기 때문에, 누구나 한 번 쯤은 사용해본 경험이 있을 것입니다.

이 섹션에서는 모던 Android 개발에서 전반적인 아키텍처 구조에 많은 영향을 주는 세 가지 주요 패턴에 대하여 살펴봅니다.

Dependency Injection (의존성 주입)

의존성 주입은 모던 Android 개발에서 가장 인기 있는 패턴 중 하나이며, 클래스의 인스턴스를 생성해야 하는 의무를 외부로 이전하는 패턴입니다.

객체 생성 의무를 외부로 이전함으로써 클래스는 서로 종속성을 가질 필요가 없습니다. 따라서 클래스 간에 느슨하게 결합된 종속성을 설계할 수 있습니다.

의존성 주입을 올바르게 활용하면 다음과 같은 이점을 얻을 수 있습니다.

  • 객체 생성을 위한 보일러 플레이트 코드를 줄일 수 있습니다.
  • 클래스 간의 결합이 느슨하여 단위 테스트를 쉽게 작성할 수 있습니다.
  • 클래스 재사용성을 향상시킵니다.
  • 결과적으로 코드 유지 보수성을 향상시킵니다.

수동으로 의존성 주입하기 가이드에 따라서 직접 의존성 주입을 구현하실 수도 있지만 Hilt, DaggerKoin(엄밀히 말하자면 service locator pattern)과 같은 효율적인 솔루션을 사용하시는 것을 권장드립니다.

  • Hilt: Hilt는 Dagger 위에서 동작하는 Android 전용 컴파일 타임 의존성 주입 라이브러리입니다. Hilt는 표준 Android Dagger 컴포넌트들을 생성하고 Dagger-Android 라이브러리에 비해 필요한 보일러 플레이트 코드의 양을 줄입니다. 또한 Google에서 Hilt와 관련하여 ViewModel, Jetpack Compose, NavigationWorkManager와의 호환성을 제공하며, 현재 포스트 작성 시점에서 모던 Android 개발을 위해 적극 권장되는 의존성 주입 라이브러리입니다. 자세한 내용은 Hilt를 이용한 의존성 주입을 확인하세요.

  • Dagger: Dagger는 javax.inject annotation (JSR 330)을 기반으로 하는 컴파일 타임 의존성 주입 라이브러리입니다. Dagger를 사용하여 Android 프로젝트에서 의존성 주입을 구성할 수 있지만 Dagger는 Android 프로젝트만을 위한 라이브러리가 아니기 때문에, 많은 추가 셋업 비용이 필요합니다. 셋업 비용을 크게 줄이려면 Hilt 또는 Dagger-Android를 사용하는 것을 권장드립니다.

  • Koin: Koin은 Kotlin 프로젝트에서 인기 있는 의존성 주입(엄밀히 말하자면 service locator pattern) 라이브러리이기도 하며 사용하기 쉽고 셋업 비용이 낮아서 Dagger 및 Hilt의 사용이나 도입이 당장 어려우신분들께 권장드립니다. 하지만, 런타임에 의존성을 생성하고 제공한다는 부분에서 대규모 프로젝트일 수록 컴파일 타임 기반 라이브러리인 Hilt 및 Dagger 보다 성능이 제한됩니다. 자세한 내용은 insert-koin.io를 확인하세요.

의존성 주입에 대해 더 자세한 학습이 필요하시다면, Dependency injection in Android를 확인하세요.

Observer Pattern (옵저버 패턴)

옵저버 패턴은 관찰자에게 상태 변경을 자동으로 알리는 구독 메커니즘을 활용한 behavioral design 패턴입니다.

옵저버 패턴은 Android 개발에서 컴포넌트 간에 느슨하게 결합된 아키텍처를 구축하기 위해 가장 자주 사용되는 패턴 중 하나입니다. 또한 독립적인 Android 컴포넌트 간의 통신과 같은 Android 플랫폼에서의 제약을 극복하는 데 활용할 수 있습니다.

상황에 따라 직접 옵저버 패턴을 구현할 수도 있으며 RxKotlin, Kotlin FlowsLiveData과 같은 라이브러리와 사용하여 쉽게 구현하실 수 있습니다.

  • LiveData: LiveData는 안드로이드 수명 주기를 인식하고 스레드로부터 안전한 데이터 홀더 옵저버 패턴입니다. LiveData의 관찰자는 Android 수명 주기에 바인딩되어 있으므로, 관찰자를 수동으로 구독 취소할 필요가 없으며 수명 주기가 활성화되지 않은 경우 방출되는 데이터를 구독하지 않습니다. 결과적으로 예측할 수 없고 식별하기 어려운 메모리 누수를 별다른 처리 없이 쉽게 방지할 수 있습니다. 또한 LiveData-ktx 라이브러리는 유용한 연산자를 제공하고 Data BindingRoom 호환성을 지원합니다. 그러나 최신 Android 개발에서는 코루틴이 광범위하게 채택되는 양상을 보이고 있기 때문에 LiveData보다 Kotlin의 Flow를 선호하고 있습니다. Flow로 마이그레이션하는 데 관심이 있다면 Migrating from LiveData to Kotlin’s Flow를 확인하세요.

  • Kotlin Flows: 먼저 코루틴은 Kotlin의 언어 수준에서 지원되는 비동기 및 non-blocking 솔루션입니다. Asynchronous Flow코루틴과 함께 동작하며 sequences와 유사한 cold streams 비동기 솔루션입니다. Flows는 Transform 연산자, Flattening 연산자flowOn 연산자와 같은 유용한 연산자를 제공하여 다양한 비동기 연산을 수행할 수 있도록 합니다. Android에서 StateFlowSharedFlow를 활용하여 state를 holding하고 관찰 가능한 flow를 구현하고 여러 구독자에게 값을 내보낼 수 있습니다.

  • RxKotlin(RxJava): RxKotlin은 옵저버 패턴, iterator 패턴 및 함수형 프로그래밍 등을 한꺼번에 제공하는 ReactiveX에서 유래하였습니다. RxKotlin은 관찰 가능한 시퀀스를 사용하여 비동기 및 이벤트 기반 프로그램을 구성할 수 있는 많은 유용한 연산자를 제공합니다. 또한 이러한 연산자를 사용하면 저수준 스레딩, 동기화 및 스레드 안전성과 같이 골치아픈 동시성 문제를 쉽게 해결할 수 있으며, RxAndroid와 같은 Android를 위한 유용한 솔루션이 많이 있습니다. 그러나 RxKotlin에는 많은 연산자가 포함되어 있어 새로 접하신분들께는 상당히 복잡할 수 있습니다. (많은 computational한 오퍼레이터를 필요로하는 회사에서 RxJava/RxKoltin/RxAndroid을 여전히 활발하게 사용하고 있으며, 다양한 오퍼레이터를 사용한다는 가정하에 일반적으로 러닝커브를 1년 이상 잡기도 합니다.) Kotlin Flows 또는 LiveData는 특히 프로젝트에서 복잡한 연산을 많이 수행할 필요가 없는 경우 사용하기 훨씬 쉽기 때문에, 프로젝트의 성향에 따라서 올바른 라이브러리를 선택하는 것을 권장드립니다.

Repository Pattern (리파지토리 패턴)

Repository Pattern은 데이터의 추상화를 제공하여 도메인과 데이터를 중재하는 소프트웨어 접근 방식인 Domain-driven design에서 비롯되었습니다.

프리젠테이션 레이어는 로컬 데이터베이스에서 데이터를 쿼리하고 네트워크에서 원격 데이터를 가져오는 것과 같은 비즈니스 로직에 근접한 인터페이스와 함께 간단한 추상화를 사용합니다. 실제 구현 클래스는 무거운 작업을 수행하고 도메인 관련 작업을 실행합니다.

리파지토리는 도메인과 관련된 실행 가능한 함수 집합체를 개념적으로 캡슐화하고, 다른 계층에 보다 객체 지향적인 측면을 제공합니다.

모던 Android 개발에서 데이터 계층은 다른 계층에 공개 인터페이스로 노출되고 단일 정보 출처 원칙(single source-of-truth)을 따르는 리파지토리들로 구성됩니다.

따라서 다른 레이어들은 Kotlin의 Flow 또는 LiveData와 같은 스트림으로 도메인 데이터를 관찰할 수 있으며 신뢰할 수 있는 소스(source of truth)를 보장받을 수 있습니다. 리파지토리 패턴 및 데이터 계층에 대한 자세한 내용은 App Architecture를 확인하십시오.

이제 아키텍처에 대해 살펴보도록 하겠습니다.

Architecture (아키텍처)

아키텍처는 프로젝트의 전체 코드 복잡성과 유지 보수 비용을 결정하기 때문에 모든 확장 가능한 소프트웨어 개발에 있어서 필수적으로 고려되어야 하는 개념입니다.

프로젝트의 함수 수가 증가함에 따라 코드 라인과 코드 응집력도 그에 따라 증가합니다. 애플리케이션 아키텍처는 프로젝트의 복잡성, 확장성 및 견고성에 광범위하게 영향을 미치며 테스트를 더 쉽게 만듭니다. 각 계층 간의 경계를 정의하여 책임을 명확하게 정의하고 전담 역할로 모듈화하여 각 책임을 분리할 수 있습니다.

역사적으로 Android 아키텍처의 트렌드는 현업에서 사용 가능한 솔루션과 모범 사례에 따라 지난 몇 년 동안 변화했습니다.

7~8년 전에는 Android 프로젝트가 MVCMVP 아키텍처 패턴으로 빌드되었지만 지금은 RxKotlin, Kotlin Flows 및 LiveData와 같은 유용한 옵저버 패턴의 구독 솔루션이 도입된 이후 대부분의 프로젝트가 MVVM 및 MVI 아키텍처 패턴으로 빌드됩니다.

이 섹션에서는 최근 몇 년 동안 가장 인기 있는 아키텍처 패턴인 MVVM, MVI 및 클린 아키텍처에 대해 알아봅니다.

MVVM

오늘날 MVVM(Model-View-ViewModel)은 Google이 공식적으로 ViewModel, LiveDataData Binding과 같은 Architecture Components를 발표한 이후 최신 Android 개발에서 가장 인기 있는 아키텍처 디자인 중 하나입니다.

역사적으로 Model-View-ViewModel 패턴이 Microsoft에서 도입된 이후로 WPF 개발자자들에게는 이미 10년 이상 된 익숙한 개념입니다.

MVVM 패턴은 아래 그림과 같이 View, ViewModel 및 Model로 구성됩니다.

각 구성 요소는 아래에 정의된 대로 Android 개발에서 서로 다른 책임이 있습니다.

  • View: 사용자가 화면에서 보는 사용자 인터페이스 구성을 담당합니다. 보기는 TextView, Button 또는 Jetpack Compose UI와 같은 UI 컴포넌트를 포함하는 Android 컴포넌트들로 구성됩니다. UI 컴포넌트는 ViewModel에 대한 사용자 이벤트를 트리거하고 ViewModel에서 데이터 또는 UI 상태를 관찰하여 UI 화면을 구성합니다. 이상적으로 View에는 리스너와 같은 화면 및 사용자 상호 작용을 나타내는 UI 로직만 포함되고 비즈니스 로직은 포함되지 않습니다.

  • ViewModel: View에 대한 종속성이 없는 독립적인 구성 요소이며 Model의 비즈니스 데이터 또는 UI 상태를 보유하여 UI 컴포넌트로 전파합니다. 일반적으로 ViewModel과 Model 사이에는 여러(일대다) 관계가 있으며 ViewModel은 데이터 변경 사항을 도메인 데이터 또는 UI 상태로 View에 알립니다. 최신 Android 개발에서 Google은 개발자가 비즈니스 데이터를 쉽게 유지하고 화면 전환이 발생하는 동안 상태를 유지하는 데 도움이 되는 ViewModel 라이브러리를 사용할 것을 권장하고 있습니다. 하지만, 엄밀히 말하면 ViewModel 라이브러리를 사용하는 것만으로 마이크로소프트에서 공개했던 MVVM 아키텍처의 원래 설계 목적에는 가깝지 않기 때문에 마이크로소프트의 ViewModel과 다르다고 할 수 있습니다. Microsoft의 MVVM의 기존 설계에 가깝게 구현하려면 Data Binding과 같은 다른 솔루션과 RxKotlin, Kotlin Flows 및 LiveData와 같은 구독 메커니즘 솔루션을 활용해야 합니다. 자세한 내용은 Microsoft의 The Model-View-ViewModel Pattern을 확인하세요.

  • Model: 일반적으로 비즈니스 로직, 복잡한 계산 작업 및 유효성 검사 로직을 포함하는 앱의 도메인/데이터 모델을 캡슐화합니다. 모델 클래스는 일반적으로 실행 가능한 도메인 함수의 집합체 같은 데이터 접근을 캡슐화하는 리파지토리의 remote service 및 로컬 데이터베이스와 함께 사용됩니다. 리파지토리는 앱 전반에서 활용되는 데이터에 대한 여러 데이터 소스 및 불변성을 제공하고 신뢰할 수 있는 단일 소스(single source of truth)를 보장합니다.

위에서 살펴본 디자인 패턴과 MVVM 아키텍처로 구축된 오픈 소스 프로젝트를 살펴보고 싶으시다면 GitHub의 Pokedex를 확인해 주세요.

MVI

MVI(Model-View-Intent)는 Jetpack Compose의 선언적 프로그래밍 도입으로인해 모던 Android 개발에서 인기 있는 아키텍처 중에 하나 입니다.

MVI 패턴은 다른 계층에 불변 상태를 제공하고 사용자 작업의 결과를 나타내며 UI 화면을 구성하는 상태의 단방향 및 불변성을 제공하는 단일 진실 원칙(single source of truth)에 중점을 둡니다.

MVI는 상태 관리 메커니즘은 MVP 또는 MVVM과 같은 다른 패턴과 함께 구현할 수 있습니다. 즉, MVI 아키텍처는 아키텍처 설계에 따라 Presenter 또는 ViewModel 개념을 가져올 수 있습니다.

MVVM 및 MVP와는 달리 MVI(Model-View-Intent)의 각 구성 요소에 대한 정의는 조금 다릅니다.

  • Intent: Intent는 사용자 작업(버튼 클릭 이벤트와 같은 UI 이벤트)을 처리하는 인터페이스 및 기능에 대한 정의입니다. 함수는 UI 이벤트를 Model의 인터페이스로 변환하고 결과를 조작할 수 있도록 Model에 전달합니다. 이름에서 알 수 있듯이 Model 함수를 수행하려는 '의도 (Intent)'가 있다고 말할 수 있습니다.

  • Model: MVI의 Model 정의는 MVP 및 MVVM과 완전히 다릅니다. MVI에서 Model은 Intent의 출력을 가져와 View에서 렌더링할 수 있는 UI state로 조작하는 기능적 메커니즘입니다. UI state는 변경할 수 없으며, 단일 소스(single source of truth) 및 단방향 데이터 흐름 (unidirectional data flow)을 따르는 비즈니스 로직에서 가져옵니다.

  • View: MVI에서 View는 MVP 및 MVVM과 동일한 역할을 하며 화면과 리스너와 같은 사용자 상호 작용을 나타내며 비즈니스 로직을 포함하지 않습니다. 다른 패턴과의 구현에서 가장 큰 차이점 중 하나는 MVI가 단방향 데이터 흐름을 보장하므로 View는 Model에서 오는 UI 상태에 따라 UI 컴포넌트를 렌더링한다는 것입니다.

MVI에 조금 더 대해 자세히 알아보고 싶다면 Hannes DorfmannReative Apps with Model-View-Intent를 통해 MVI 아키텍처를 더 잘 이해할 수 있습니다.

위에서 설명한 MVI 아키텍처 및 디자인 패턴으로 구축된 오픈 소스 프로젝트가 궁금하시다면 GitHub에서 WhatsApp Clone Compose를 확인하십시오.

Clean Architecture (클린 아키텍처)

클린 아키텍처Robert C. Martin(Uncle Bob)이 출간한 clean 서적 시리즈 중 하나인 "Clean Architecture: A Craftsman's Guide to Software Structure and Design"에서 소개했습니다. Robert는 OOP(객체 지향 프로그래밍) 패러다임을 기반으로 하는 응용 프로그램을 위한 강력하고 클린한 아키텍처를 구축하기 위한 몇 가지 설계 접근 방식을 이론화하고 소개했습니다.

클린 아키텍처는 Dagger와 같은 종속성 주입 솔루션과 다중 모듈 프로젝트 환경이 도입된 이후 모던 Android 개발에서 널리 사용되고있습니다. 또한 이 이론은 MVP, MVVM 및 MVI와 같은 다른 아키텍처와 함께 사용할 수 있습니다.

클린 아키텍처는 모듈 분리, 재사용성 증가, 확장성 향상, 단위 테스트 사례 작성 용이성과 같은 이점을 제공합니다. 그러나 복잡한 비즈니스 로직이 필요하지 않은 소규모~중규모 프로젝트를 (혹은 프로젝트 성격에 따라 도입이 반드시 필요하지 않은 경우) 진행하는 경우 이 아키텍처가 오버 엔지니어링이 수 있으므로 아키텍처가 프로젝트에 이점을 제공하는지 반드시 사전 조사해야 합니다.

클린 아키텍처 이론에 대해 알아보기 전에 Uncle Bob의 클린북 시리즈 중 하나에서 소개된 SOLID 설계 원칙에 대해 살펴볼 것입니다. Robert는 이해하기 쉽고 유연하며 유지 보수가 가능한 프로젝트를 구축할 수 있도록 아래의 5가지 소프트웨어 설계 원칙을 목적으로 삼았습니다.

  • Single Responsibility (단일 책임 원칙): 클래스나 모듈과 같은 각 소프트웨어 구성 요소는 변경해야 할 이유가 하나만 있어야 합니다. 즉, 동일한 구성 요소, 클래스 또는 모듈에서 관련 없는 기능을 설계하면 코드의 목적을 이해하기 어렵고 목적이 명확하지 않게 됩니다.

  • Open/Closed (개방-폐쇄의 원칙): 지점을 중단하거나 용도를 수정하지 않고(수정을 위해 폐쇄) 구성 요소의 기능을 확장할 수 있어야 합니다 (확장을 위해 개방).

  • Liskov Substitution (리스코프 치환 원칙): 확장 클래스는 상위 클래스를 대체할 수 있어야 합니다. 즉, 상위 클래스에는 확장할 간결한 목적을 제공하는 최소한의 인터페이스가 있어야 하며 하위 클래스는 상위 클래스의 모든 추상화를 구현해야 합니다.

  • Interface Segregation (인터페이스 분리 원칙): 이름으로 추정할 수 있듯이 원자 중심 원칙이며 단일 책임 및 Liskov 대체 원칙과 연결될 수 있습니다. 비대한 함수 작성을 방지하고 Liskov 대체 원칙을 위반하지 않으려면 거대한 인터페이스보다 작은 인터페이스를 많이 만드는 것이 좋습니다.

  • Dependency Inversion (의존관계 역전 원칙): 클래스와 모듈은 단방향 및 선형 종속성을 달성하기 위해 구체화가 아닌 추상화에 종속되어야 합니다. 또한 이는 컴포넌트의 순결성을 보장하므로 각 컴포넌트는 본인만의 역할을 담당합니다. 이 개념은 의존성 주입 디자인 패턴과는 다릅니다.

클린 아키텍처는 기본적으로 위의 SOLID 설계 원칙을 따릅니다. Robert는 아래 그림과 함께 클린 아키텍처를 설명했습니다.

원의 중심은 다른 레이어에 대한 종속성이 없는 가장 순수한 범위입니다. 각 계층은 내부 원 종속성이 있는 외부 계층에서 사용할 추상화를 노출해야 합니다. 아시다시피 이것은 SOLID 디자인 원칙의 최대 조합입니다.

각 계층에는 고유한 단일 책임이 있으며 이들 간의 종속성 역전 원칙을 따릅니다. 이제 각 레이어가 무엇을 담당하는지 살펴보겠습니다.

  • Entity: 애플리케이션의 비즈니스 규칙 및 개체 집합을 캡슐화합니다. 이 계층은 또한 최고 수준의 규칙을 따르고 쉽게 사용할 수 있도록 다른 계층에 추상화를 노출합니다. Google의 공식 아키텍처 가이드에서는 엔티티 레이어를 데이터 레이어로 취급하고 있습니다.

  • Use Cases: Use Cases는 엔티티 계층에서 기능을 트리거하는 사용자 작업과 같은 애플리케이션 비즈니스 규칙의 정의가 포함됩니다. 이 계층은 엔터티 계층에만 종속되며 응용 프로그램별 비즈니스 로직를 실행하기 위해 외부 계층에 추상화를 노출합니다.

  • Presenters (Interface Adapters): 이 계층은 Use Cases 계층에서 애플리케이션 비즈니스 규칙의 정의인 노출된 모든 인터페이스를 수행하고 UI 계층과 통신합니다. MVVM에서 ViewModel은 여기에 속합니다.

  • UI (프레임워크 및 드라이버): UI 레이어는 Android 화면에서 사용할 Android 위젯과 같은 모든 UI 컴포넌트와 Activity, Fragment를 포함하여 UI가 렌더링되는 방식을 나타냅니다.

클린 아키텍처에 대한 자세한 개념을 더 알아보고 실제 프로젝트에 도입을 하고 계신다면 아래 링크를 통해서 개념을 더 학습하실 수 있습니다.

Asynchronous and Concurrency (비동기와 동시성)

Android에서 시스템은 애플리케이션의 모든 UI 관련 작업을 처리하는 기본 스레드(소위 UI 스레드)를 생성합니다. 기본 스레드는 UI 요소 렌더링, 적절한 사용자 인터페이스로 이벤트 전달, Android UI toolkit의 컴포넌트 간의 모든 상호 작용을 담당합니다.

따라서 네트워크 요청 및 데이터베이스 쿼리와 같은 I/O 또는 비용이 많이 드는 계산 작업을 수행하려는 경우 기본 스레드 (메인 스레드)가 아닌 다른 스레드(소위 작업자 스레드)에서 처리해야 합니다. 이렇게 하면 기본 스레드가 화면 렌더링 및 사용자와의 상호 작용만을 담당할 수 있습니다.

즉, 멀티스레드가 많은 프로그램을 작성하는 것은 유지 보수 및 디버깅이 어렵고 경쟁 조건(race conditions)을 피하고 리소스를 관리하는 것과 같은 다양한 고려 사항들이 있습니다. 다행스럽게도 메인 스레드를 차단하지 않고 계산량이 많은 비즈니스 작업을 실행할 수 있는 솔루션이 이미 있으며 개발자가 각 스레드를 하나씩 수동으로 처리할 필요가 없습니다.

이제 스레드와 관련한 비즈니스 로직을 수행하는 다양한 솔루션들에 대해 살펴보겠습니다.

RxJava/RxKotlin

디자인 패턴 섹션에서 RxJava에 대해 이미 살펴보았습니다. RxJava 사용의 이점 중 하나는 백그라운드 스레드에서 비즈니스 로직을 실행하고 UI 스레드에서 결과를 받아보는 것과 같은 멀티 스레딩 문제를 쉽게 제어할 수 있다는 것입니다.

RxJava는 Schedulers라는 스레드 풀을 제공하며 스레드 풀에 있는 io, computation, single 스레드와 같은 다양한 종류의 스레드를 사용하거나, 새로운 사용자 정의 스레드를 생성할 수 있습니다.

작동해야 하는 스레드와 결과를 소비해야 하는 스레드를 정의하는 SubscribeOnObserveOn 연산자로 스케줄러를 지정하여 스레딩 동작을 조작할 수 있습니다.

RxJava 및 멀티스레딩에 대해 자세히 알아보려면 Aritra Roy의 Multi-Threading Like a Boss in Android With RxJava 2를 확인하세요.

Coroutines (코루틴)

코루틴은 언어 수준에서 코드를 비동기적으로 실행하는 훌륭한 동시성 솔루션입니다.

스레드와 달리 코루틴은 순전히 사용자 수준 언어의 추상화이므로, OS 리소스에 직접적으로 연결되지 않으며 각 코루틴 개체는 JVM 힙 메모리에 할당됩니다. 즉, 코루틴은 사용자 측에서 제어할 수 있고 훨씬 더 가벼운 리소스를 사용하며 스레드보다 컨텍스트 스위칭 (context switching) 비용이 저렴합니다.

Android 문서에 따르면 코루틴은 가볍기 때문에, 단일 스레드에서 많은 코루틴을 실행할 수 있고 범위를 기반으로 작동하기 때문에 메모리 누수가 적습니다. 또한 Google은 ViewModelScopeLifecycleScope와 같은 많은 Jetpack 라이브러리 통합 및 호환성을 지원하므로 코루틴을 동시성 솔루션으로 채택하신다면 다양한 이점을 얻을 수 있습니다.

Android에서의 코루틴 활용법에 대해 자세히 알아보려면 Kotlin coroutines on Android를 살펴보시길 바랍니다.

Network (네트워크)

네트워크 통신은 모든 최신 애플리케이션 개발의 필수적인 영역입니다. 그러나 자체 네트워크 솔루션을 구축하려면 connection pooling, 응답 캐싱, HTTP(Hypertext Transfer Protocol)과 같은 저수준 기능, 예를들어 인터셉터 및 비동기 호출 지원 등을 직접 구현해야하는 방대한 리소스가 요구됩니다.

7~8년 전 Android 개발자들은 HttpURLConnection 또는 Apache의 HttpClient를 사용하여 HTTP 요청을 수행했습니다. 하지만 이러한 라이브러리에는 많은 보일러 플레이트 코드가 필요하고 연결 기능, 보안 지원 및 DNS(Domain Name System) 해결책들이 Android 플랫폼과의 호환성을 지원하지 않았습니다.

이 섹션에서는 Android에서 가장 많이 사용되는 HTTP 요청 라이브러리인 OkHttpRetrofit을 살펴봅니다.

OkHttp

OkHttpSquare에서 개발되었으며, 내부적으로 Okio로 빌드된 JVM 및 Android용 HTTP 클라이언트로 Android, Java 및 Kotlin 멀티플랫폼용 최신 I/O 라이브러리입니다.

OkHttp는 효율적으로 동작하며 HTTP 클라이언트를 빠르게 셋업하는데 장점이 있습니다. 또한, 자체 복구 시스템이 있어 연결성 문제와 같이 네트워크에 문제가 발생했을 때 수동으로 처리할 필요가 없습니다.

이 라이브러리는 최신 TLS(Transport Layer Security) 기능, Android용 보안 지원, 캐싱인터셉터를 제공합니다.

OkHttp에서 가장 유능한 기능 중 하나는 호출 로그를 기록, 모니터링, 수정, 재작성 및 재시도할 수 있는 강력한 메커니즘인 interceptors입니다.

Bearer Authentication과 같은 액세스 토큰(access token)을 헤더에 추가하거나 요청 본문에 gzip 압축을 추가하는 등 필요에 따라 모든 네트워크 요청을 쉽게 변환할 수 있습니다.

Retrofit

Retrofit은 Android 및 JVM용 type-safe HTTP 클라이언트이며 Square에서 개발되었습니다. Retrofit은 OkHttp 위에 추상화 계층을 제공하므로 저수준 구현을 처리하지 않고도 쉽고 간결하게 요청 사양을 정의할 수 있습니다.

또한 Retrofit에서 제공하는 annotation을 활용하여 URL, 헤더 조작, method 및 body를 쉽게 요청할 수 있으며, 원하는 HTTP 요청을 쉽게 빌드할 수 있습니다. 또한 플러그인 가능한 Converter.Factory를 연결하면 보일러 플레이트 코드를 작성할 필요 없이 모든 JSON 응답을 쉽게 직렬화할 수 있습니다.

원시 응답을 처리하고 응답 유형을 원하는 유형으로 모델링할 수 있는 CallAdapter를 연결하여 네트워크 응답을 조작할 수도 있습니다. 이에 대한 자세한 내용은 Modeling Retrofit Responses With Sealed Classes and Coroutines에서 확인해보실 수 있습니다. 국문 포스트는 안드로이드 Retrofit + Coroutines의 API 응답 및 에러 핸들링 - Sandwich에서 확인하실 수 있습니다.

Retrofit에 대한 자세한 내용은 Retrofit 공식 페이지를 참조하십시오.

Image Loading (이미지 로딩)

이미지 로딩은 네트워크를 통해서 사용자 프로필 이미지나 기타 콘텐츠를 로드할 때 모던 애플리케이션 개발의 필수 부분입니다.

자체 이미지 로딩 시스템을 구현할 수 있지만 네트워크에서 이미지 다운로드, 크기 조정, 캐싱, 렌더링 및 메모리 관리와 같은 수많은 기능들이 필요합니다.

이 섹션에서는 인기 있는 Android 전영 이미지 라이브러리를을 살펴봅니다.

Glide

Glide는 가장 인기 있는 이미지 라이브러리 중 하나이며 오랫동안 사용되어 왔습니다. Google의 공식 오픈 소스 프로젝트를 포함하여 많은 글로벌 제품 및 오픈 소스 프로젝트에서 사용되었습니다.

필자가 Google에 재직 중인 엔지니어분들께 들은 이야기 중 하나는, Glide는 기업에서 개발되는 오픈소스 라이브러리가 아닌 Google에 재직 중인 단 한명의 엔지니어에 의해서 개발 및 유지보수가 되고 있으며, 버그 패치 및 새버전 릴리스는 구글 포토 앱에서 필요한 기능이 생기거나 에러 리포트가 발행될 때마다 진행된다고 합니다.

Glide는 기본적으로 타 이미지 로딩 라이브러리에 비해서 제공하는 기본적인 기능들이 압도적으로 다양하며, Animated GIF 지원, placeholders, transformations, caching, 및 리소스 재사용과 같은 유용한 기능을 제공합니다.

자세한 내용은 Glide의 공식 문서를 확인하세요.

Coil

CoilColin White가 개발하였으며, 2019년부터 점점 인기를 얻고 있습니다.

Coil은 100% Kotlin으로 작성되었으며 API들이 Kotlin 친화적입니다. 한 가지 주목할만한 점은 코일이 OkHttpCoroutine과 같이 Android 프로젝트에서 이미 널리 사용되는 솔루션들을 사용하기 때문에 다른 대체재 보다 가볍다는 것입니다.

Coil은 또한 Jetpack Compose를 지원하며 Transformations, animated GIF 지원, SVG 지원비디오 프레임 지원과 같은 유용한 기능을 제공합니다.

자세한 내용은 Coil의 공식 가이드를 확인하세요.

Fresco

Fresco는 인기 있는 이미지 로딩 라이브러리이기도 하며 Glide와 함께 오랫동안 사용되어 왔고, Meta에서 개발하였습니다.

다른 라이브러리와 달리 Fresco는 특히 Android 버전 4.x 이하를 대상으로 메모리를 효율적으로 사용하는 데 중점을 둡니다. 그러나 최근 프로젝트는 최소 SDK 21~23을 대상으로 하며 API가 복잡합니다. 저수준 OS를 타깃하면서 메모리에 민감한 애플리케이션을 구축하지 않는 한 Glide 또는 Coil을 사용하시는 것을 권장드립니다.

자세한 내용은 Fresco의 공식 가이드를 확인하세요.

Landscapist

Landscapist이 글의 저자인 skydoves가 개발한 Glide, Coil 및 Fresco로 네트워크 또는 로컬 이미지를 가져와서 표시하는 Jetpack Compose 이미지 로딩 라이브러리입니다.

Jetpack Compose에서는 UI 렌더링 메커니즘이 기존의 XML 기반에 비해서 완전히 변경되었습니다. 그 때문에 Landscapist는 널리 사용되는 이미지 로드 라이브러리를 사용하여 Jetpack Compose에서 이미지 로딩을 쉽고 효율적으로 할 수 있도록 개발되었습니다.

Landscapist는 이미지 상태 추적, 사용자 지정 Composable 구성, circular reveal 및 crossfade와 같은 애니메이션을 지원합니다. 또한 최신 버전에서 ImagePlugin이라는 새로운 개념이 도입되어 이미지 로딩 동작을 더 쉽고 효율적으로 구현할 수 있습니다.

자세한 내용은 GitHub의 Landscapist를 확인하세요.

Local Storage (로컬 저장소)

로컬 저장소는 Android에서 자주 사용되는 데이터베이스 솔루션입니다. 사용자의 로컬 장치에 사용자 입력 또는 원격 리소스를 유지해야 하는 경우 로컬 저장소에 저장하고 복원해야 합니다.

이 섹션에서는 Jetpack의 주요 로컬 스토리지 솔루션인 RoomDataStore를 살펴보겠습니다.

Room

Room은 복잡한 SQL 문 없이 데이터베이스 쿼리 및 액세스를 단순화 할 수 있도록 SQLite를 사용하여 추상화 계층을 제공하는 Google의 Android Jetpack 라이브러리입니다.

Annotation Processor(KSP(Kotlin Symbol Processing)도 지원)를 기반으로 작동하므로 쿼리 및 열 삽입과 같은 구현은 모두 컴파일 타임에 생성됩니다.

이 라이브러리 가장 큰 장점 중 하나는 추상화 계층이 매우 간결하고 이해하기 쉽기 때문에 개발자가 CRUD에 대한 기본적인 이해만 바탕이 된다면, SQL 쿼리를 특별히 학습할 필요가 없다는 것입니다. 또한 코루틴, RxJava 호환성, 자동 마이그레이션 전략 및 Type converters와 같은 유용한 기능을 제공합니다.

Room에 대해 자세히 알아보려면 Save data in a local database using Room를 확인하세요.

DataStore

DataStore는 Google의 또 다른 Android Jetpack 라이브러리로, 로컬 저장소에 키-값 쌍을 저장할 수 있는 데이터 저장소 솔루션입니다. 이 라이브러리는 SharedPreferences의 대체 솔루션이라고 볼 수 있습니다.

DataStore는 Coroutines 및 Flow와 같은 다른 라이브러리와의 뛰어난 호환성을 지원하여 데이터를 비동기식으로 저장하고 RxJava도 지원합니다. 또한 protocol buffers를 사용하여 타입 객체의 저장도 지원합니다.

DataStore에 대해 자세히 알아보려면 Google의 공식 문서를 확인하세요.

Conclusion

이로써 2022 Android Developer Roadmap 4부 포스트를 마무리합니다. 이번 4부에서는 Design Patterns, Architecture, Asynchronous, Network, Image Loading, Local Storage와 같이 모던 앱 개발에 있어서 중요하고 많은 곳들에서 언급되는 개념들에 대하여 살펴보았습니다.

다시 한 번 말씀드리지만, 로드맵의 방대한 양에 절대 당황하지마시고 학습에 필요한 부분만 선택적으로 학습하시는 것을 권장드립니다.

Android 로드맵의 이전 섹션을 놓친 경우 아래의 링크도 읽어보시는 것을 권장드립니다.

다음 포스트는 Stream 블로그에 가장 먼저 연재될 예정이며, 추후 velog에 번역본이 포스팅 될 예정입니다.

즐거운 코딩 되시길 바랍니다!

작성자 엄재웅 (skydoves)

profile
http://github.com/skydoves

0개의 댓글