-
[RxJava] RxJava (1) - 시작ANDROID/RXJAVA 2022. 3. 30. 21:49
[RxJava] RxJava 프로그래밍(1) - 리액티브 프로그래밍
서버 다수와 통신하게 되면 API 호출 각각에 콜백을 추가하게 된다. 콜백이 늘어나면 애플리케이션의 복잡성도 증가(callback hell)하게 된다. RxJava는 자바로 리액티브 프로그래밍을 할 수 있는 라이
12bme.tistory.com
예전에 인턴을 하던 중 봤던 Rx에 관한 글인데 Rx관한 글 중에서 가장 도움이 많이 되는 것 같아 북마크 해두었던 글인데 Rx를 다시 공부하게 되서 거의 위에 블로그를 그대로 따라 쓰다시피 글을 적어봅니다.
RxJava는 비동기 프로그래밍과 함수형 프로그래밍 기법을 활용하는 자바로 리액티브 프로그래밍을 할 수 있는 라이브러리이다.
리액티브 프로그래밍은 복잡한 비동기 프로그램을 쉽게 만들 수 있게 해준다. 리액티브는 사용자가 이벤트를 발생시키면 비동기로 반응하여 처리한다. 또한 비동기에서 처리하기 힘든 에러 처리나 데이터 가공을 쉽게 할 수 있게 도와주기도 한다. 이벤트를 콜백이 아닌 데이터의 모음으로 모델링하기 때문이다.
리액티브 프로그래밍은 데이터 흐름과 전달에 관한 프로그래밍 패러다임(틀)이다. 기존의 명령형 프로그래밍은 주로 프로그래머가 작성한 코드가 정해진 절차에 따라 순서대로 실행된다. 그러나 리액티브 프로그래밍은 데이터 흐름을 먼저 정의하고 데이터가 변경되었을 때 연관되는 함수나 수식이 업데이트되는 방식이다.
명령형 프로그래밍 방식은 변경이 발생했다는 통지를 받아서 연말 매출액을 새로 계산하는 당겨오는(pull) 방식이지만, 리액티브 프로그래밍은 데이터 소스가 변경된 데이터를 밀어주는(push) 방식이다. 일종의 옵저버 패턴이다.
즉, 어떤 기능을 직접 실행되는 것이 아니라 시스템에 어떤이벤트가 발생했을때 처리하는 것이다.
네트워크 프로그래밍을할 때 사용하는 콜백(callback)이나 ui 프로그래밍할때 버튼 이벤트를 처리하는 클릭 리스너도 개념상으로는 리액티브 프로그래밍에 해당한다.
자바 언어와 리액티브 프로그래밍
자바 언어는 객체지향 프로그래밍 언어이다. 예를 들어 연간 매출액을 계산하는 프로그램을 자바로 작성한다면 데이터베이스에서 월간 매출액의 합계를 가져와서(pull 방식) 결과를 다시 계산하게 된다. 그리고 계산 시점은 아마 사용자가 <새로 고침> 버튼을 눌렀을 때가 될 것이다. 즉, 별도의 이벤트를 받아서 다시 계산하는 방식으로 개발하지 않는다는 뜻이기도 하다.
함수형 프로그래밍은 부수 효과가 없다. 콜백이나 옵저버 패턴이 스레드에 안전하지 않은 이유는 같은 자원에 여러 스레드가 동시에 단일 자원에 접근하면 계산 결과가 꼬이고 디버깅하기가 매우 어렵게 된다.
함수형 프로그래밍은 부수 효과가 없는 순수 함수(pure function)를 지향한다. 따라서 멀티 스레드 환경에서도 안전하다. 자바 언어로 리액티브 프로그래밍을 하기 위해서는 함수형 프로그래밍의 지원이 필요하다.
RxJava를 비롯한 리액티브 프로그래밍을 공부하다보면 새롭게 등장하는 개념 때문에 혼란에 빠질 때가 있다. 프로그래밍 스타일도 다르고 프로그래머가 문제를 바라봐야하는 관점도 달라지기 때문이다. 리액티브 프로그래밍은 데이터 흐름과 변화의 전달에 관한 프로그래밍 패러다임이다.
RxJava를 만들게 된 이유
Netflix에서 REST 기반의 서비스 API 호출 횟수와 서비스의 전반적인 성능을 개선하는 프로젝트를 진행했고, 그결과 .NET환경의 리액티브 확장 라이브러리(RX)를 JVM에 포팅하여 RxJava를 만들었다. 넷플릭스에서 RxJava를 만들게된 핵심적인 이유를 다음과 같이 밝혔다.
- 동시성을 적극적으로 끌어안을 필요가 있다
- 자바 Future를 조합하기 어렵다는 점을 해결해야 한다
- 콜백 방식의 문제점을 개선해야 한다
첫번째 이유의 원인은 자바가 동시성 처리를 하는데 번거로움이 있기 때문이다. 이를 해결하려고 넷플릭스는 클라이언트의 요청을 처리하는 서비스 계층에서 동시성을 적극적으로 끌어안았다. 클라이언트의 요청을 처리할때 다수의 비동기 흐름(스레드 등)을 생성하고 그것의 결과를 취합하여 최종 리턴하는 방식으로 내부 로직을 변경했다.
두번째 이유의 원인은 2013년 당시 자바 8에서 제공하는 CompletableFuture 같은 클래스가 제공되지 않았기 때문이다. 그래서 비동기 흐름을 조합할 방법이 거의 없었다. RxJava에서는 이를 해결하려고 비동기 흐름을 조합(compose)할 수 있는 방법을 제공한다. RxJava에서는 조합하는 실행 단위를 리액티브 연산자(Operators)라고 한다.
세번째 이유의 원인은 콜백을 부르는 콜백 지옥(Callback Hell) 상황이 코드의 가독성을 떨어뜨리고 문제 발생시 디버깅을 어렵게 만들기 때문이다. 비동기 방식으로 동작하는 가장 대표적인 프로그래밍 패턴은 콜백이다. 그래서 RxJava는 콜백을 사용하지 않는 방향으로 설계해 이를 해결했다.
리액티브 프로그래밍은 비동기 연산을 필터링, 변환, 조합해 위 세가지 핵심이유를 해결할 수 있다. 따라서 RxJava는 Observable과 같은 데이터 소스와 map(), filter(), reduce()와 같은 리액티브 연산자를 제공한다.
RxJava 사용하기
import io.reactivex.Observable class ObservableExample { fun emit() { Observable.just("Hello", "RxJava 2!!") .subscribe(System.out::println) } void main(args : Array<String>) { val demo : ObservableExample = ObservableExample() demo.emit() } }1) Observable
Observable은 데이터의 변화가 발생하는 데이터 소스이다.
2) just 함수
Observable의 메서드는 just는 가장 단순한 Observable 선언 방식이다.
3) subscribe 함수
subscribe 메서드는 Observable을 구독한다. Observable은 subscribe 함수를 호출해야 비로소 변환한 데이터를 구독자에게 발행한다. (just() 함수만 호출하면 데이터를 발생하지 않는다.) 이 부분은 옵저버 패턴과 동일하다고 생각하면 된다. 반드시 데이터를 수신할 구독자가 subscribe()를 호출해야 Observable에서 데이터가 발행된다.
4) System.out::println
수신한 데이터를 System.out.println을 통해 호출했다. System.out::println 부분은 자바 8의 메서드 레퍼런스를 활용한 것이다. 만약 메서드 레퍼런스를 사용하지 않으면 data -> System.out.print(data)와 동일하다. Observable이 발행하는 데이터 인자로 들어온다.
5) emit() 메서드
RxJava 개발 문서에서는 Observable이 subscribe() 함수를 호출한 구독자에게 데이터를 발행하는 표현하는 용어로 사용한다. RxJava 관련 문서에서 자주 등장하는 단어이다.
4. RxJava를 어떻게 공부할 것인가.
자바는 전통적인 스레드 기반의 프로그래밍이다. 하지만 RxJava는 비동기 프로그래밍을 위한 라이브러리라서 개념과 접근 방식이 다르다.
따라서 배우는데 진입 장벽이 높은 편이다.
전통적인 스레드 기반의 프로그래밍은 다수의 스레드를 활용하는 경우 예상치 못한 문제가 발생하고 디버깅하기도 어려웠다. 특히 문제를 재현하기 어렵거나 미묘한 경우도 상당수 발행한다. 이러한 문제를 해결하기 위해 RxJava는 함수형 프로그래밍 기법을 도입했다.
함수형 프로그래밍은 부수 효과가 없는 순수 함수를 지향하므로 스레드에 안전하다.
자바는 함수형 언어가 아니므로 RxJava 라이브러리는 순수 함수로 작성된 리액티브 연산자를 제공한다. 이 리액티브 연산자 덕분에 RxJava는 리액티브 프로그래밍이 되는 것이다. 리액티브 연산자를 활용하면 목적을 당성할 수 있는 도구인 '함수형 프로그래밍' 방식으로 '스레드에 안전한 비동기 프로그램'을 작성할 수 있다.
RxJava를 활용하여 코딩하는데 어려움을 느끼는이유는 함수형 연산자를 어떻게 호출애햐 하는지 모르기 때문이다. 당장 map(), filter(), reduce(), flatMap()과 같은 함수 정의는 물론, 함수의 입력과 출력 방법이 무엇인지 알기 어려울 수 있다.
따라서 다음과 같은 학습 순서를 권장한다.
1. Observable 클래스를 명확하게 이해한다. 특히 Hot ObservableRhk ColdObservable의 개념을 꼭 이해해야 한다.
2. 간단한 예제로 map(), filter(), reduce(), flatMap() 함수의 사용법을 익힌다.
3. 생성 연산자, 결합 연산자, 변환 연산자 등 카테고리별 주요 함수를 공부한다.
4. 스케줄러의 의미를 배우고 subscribeOn()과 observeOn() 함수의 차이를 알아둔다.
5. 그밖의 디버깅, 흐름 제어 함수를 익힌다.
RxJava 프로그래밍 챕터별 흐름.
- 가장 먼저 데이터 변경이 발생하는 Observable 클래스를 배운다. 배열과 같은 단순한 자료구조부터 마우스 이벤트, 버튼 클릭 이벤트, 센서 이벤트 등 데이터 소스라면 무엇이든 Observable 클래스의 인스턴스가 될 수 있다. 또한 Observable의 개념을 바탕으로 Subject, Single, Maybe 등의 클래스를 배운다. 모두 observable의 특수한 경우이다.
- 함수형 프로그래밍의 기본 패턴인 map-filter-reduce에 대해서 배운다. 이것만으로 어떠한 부수 효과가 없는 데이터 처리를 경해 볼 수 있다. RxJava에서는 위에 대응되는 map(), filter(), reduce() 함수를 제공한다. 특히 flatMap()은 map() 등의 기본이 되는 함수로 처음에는 이해하기 어려울 수 있다.
- interval(), zip(), combineLastest(), switchMap() 함수 등 주요 카테고리별로 연산자를 배운다. 각 함수는 생성 연산자, 반환 연산자, 결합 연산자, 조건 유틸리티 연산자 등으로 나뉘어 있으니 카테고리의 의미를 잘 안다면 개별 연산자의 동작을 이해하는 것이 어렵지는 않다. 또한 각 함수의 마블 다이어그램을 보면서 어떻게 동작하는지 살펴볼 것이므로 반드시 마블 다이어그램을 보고 동작 내용을 머릿속에 그려볼 것을 권장한다.
- RxJava 때문에 달라지는 비동기 프로그래밍에 대해 다룬다. 스케줄러에 대한 개념을 배우고 RxJava에서 제공하는 IO 스케줄러, 계산 스케줄러 및 트램펄린 스케줄러의 활용법에 대해서 배운다. 또한 비동기 프로그래밍을 하는데 왜 subscribeOn()과 observeOn() 함수가 필요한지도 알아본다. 스레드로 대표되는 전통적인 비동기 프로그래밍이나 콜백 방식과는 차별화된 다른 방식을 배우게 될 것이다.
- 디버깅과 예외 처리를 다룬다. RxJava로 코딩하면 부수 효과를 염려하여 로그를 찍을 수 없다고 생각하지만 doOnNext() 함수 등은 프로젝트에서 꼭 필요하다. 또한 기존 자바의 try-catch 문을 사용하지 않는 예외 처리에 대해 배운다.
'ANDROID > RXJAVA' 카테고리의 다른 글
[RxJava] RxJava에 대한 이해 (0) 2022.12.21 [RxJava] RxJava (3) - Single 과 Maybe (0) 2022.05.03 [RxJava] RxJava (2) - Observable (0) 2022.04.01