RxJava 1 과 비교해서 정리한 RxJava 2

RxJava 1 적용한지 얼마 안 되었는데, 벌써 RxJava 2 라니, 말도 안 돼! 심지어 3.X 에 대한 정보도 심심찮게 들리는데…

서론

RxJava1과 RxJava2 를 먼저 비교하기에 앞서, 왜 이 진입 장벽 높은 라이브러리가 버전업을 했는지부터 알아야 할 필요가 있다. Reactive Extension (Rx)은 옵져버 패턴을 이용해서 이벤트 기반 비동기식 프로그래밍을 할 수 있게 만들어준 라이브러리다. 다양한 언어에서 이 Rx의 프로토콜을 이용할 수 있게 기준이 되는 것이 Reactive Stream Specification인데, RxJava 1은 이 기준에 맞지 않는 것들이 많아서, 버전업을 시키면서 완전히 처음부터 다시 작성하기로 결정된 것이다.

다른점

RxJava 2 에서 변경된 것들은 많지만, 일단 대분류로 그 중 인상적인 것들만 모아봤다. 이들을 제외하고도 달라진 점들은 꽤 많으니, 직접 진행하는 프로젝트에 일단 넣어보고 고민하는 것도 괜찮은 방법이다. 목록이 긴 관계로 관심 있는 대분류를 클릭해서 보면 편할 것이다.
Observable.create
Null
Functional Interfaces
Testing
Observable vs Flowable
Single, Completable and Maybe

결론

RxJava1 을 사용하면서 이미 Backpressure 에 대한 이해가 있었다면, 빠른 시일 내에 업데이트하는 게 좋을 것 같다. Dagger 1 에서 Dagger 2로 넘어가는 것만큼 사고의 변환이 크지 않고, Technical Debt 기간이 길어질수록 이자가 쌓여서, 어차피 나중에 전환할 때 더 힘들어지기 때문이다. 이 블로그와 같이 90% 이상이 RxJava 1 베이스인 경우라면, PR을 여러 기능별로 끊어서 넣고 배포 때 플레이스토어의 Staged Rollout 기능을 통해서 예상치 못하는 이슈들을 잡아나가면서 진행하는 걸 추천한다.

Observable.create()

RxJava 1의 가장 큰 진입 장벽의 하나던 .create() 함수가 전면적으로 개편되었다. API를 처음 배울 때 가장 처음 확인하게 되는 .create() 함수를 올바르게 쓰기가 너무 어려운 관계로 .just().defer() 을 함께 많이 이용했는데, RxJava 2에서부터는 Flowable 의 경우 .create() 의 경우 BackpressureStrategy 를 미리 지정해줘야 하게 된다. Back pressure 을 책임져야 하는 존재는 source이어야 하며, chain이 아니라는 철학이 반영되었다.

Null

RxJava 1 에서 자주 보이던 Observable.just(null), 특히 RxBinding 을 같이 쓰는 프로젝트에서 아주 자주 보게 되는데, RxJava 2 에서는 더는 null을 정상적인 값으로 받거나 넘기지 않게 된다.

위와 같이 null을 값으로 받거나 넘기게 될 경우, NullPointerException 으로 취급해서 에러로 핸들 하게 된다. Observable 의 경우 오로지 완료되거나 오류를 던지는 것 외에는 값을 넘길 수 없게 되며, API 제작자들은 enum 타입으로 분리하던 객체를 이용해서 Observable 처럼 내려줄 객체를 정의해야 한다.

Functional Interfaces

RxJava 1.X 와 2.X 모두 Java 6의 호환을 고려하고 설계되어서 Java 8의 java.util.function.Function 에서 제공하는 Functional Interface 들을 사용하지 못해서, RxJava 내부에서 별도로 정의했었다. 단지, 이제 RxJava 2.X부터는 Exception을 핸들 하게 되어서, 더는 .map() 안에서 try-catch 코드를 보지 않아도 된다! 예를 들면, 아래와 같다:

Testing

RxJava 1.X의 TestSubject 가 드디어 없어졌다! TestSchedulerPublishSubject 를 통해서 손쉽게 테스팅을 할 수 있게 되었다. 개인적으로는 유닛 테스트 작성할 때 PublishSubjectRxJavaPlugins로 구현했었는데, 이제 injection을 통해서 스케쥴러를 주입할 수 있게 짜는 방향으로 가게 될 것 같다.

Observable vs Flowable

상용 프로젝트 환경에서 Rx 스트림 내에서 MissingBackpressureException 혹은 OutOfMemoryError 을 겪게 되면 매우 대응하기가 난감하다. 특히 RxJava 1 에서는 백프레셔를 대응할 수 있는 오퍼레이터인지 아닌지, 그리고 어떻게 대응을 할지에 대해서 알기 위해선 일단 한번 당하고(?) 문서를 확인하며 배우는 방식이다. 근본적으로 Hot Observable은 올바르게 백프레셔를 처리하기 힘들며, 애초에 RxJava 1.X에서는 Hot Observable과 Cold Observable으로 명확하게 나뉘어있지 않기 때문이다.

RxJava 2.X에서는 이 둘을 명확하게 나누기 위해서 백프레셔가 일어나지 않는 Observable과 백프레셔를 주의해야 하는 Flowable로 나누게 되었다. 아래는 RxJava 위키에서 정의하는 Observable과 Flowable 을 사용해야 할 때를 정리한 것이다:

Observable을 사용 해야 할 때

  1. 흐르는 객체에 따라 다르겠지만, OOM이 일어날 수 없을 정도의 객체의 개수를 관리할 때
  2. 터치이벤트나 클릭 좌표 이동 등 sampling 또는 debouncing으로 쉽게 핸들 할 수 있는 GUI 이벤트 사용할 때
  3. 이때 항상 유의해야 하는 것은, Observable이 Flowable 보다 적은 자원을 필요로 하다는 점이다

Flowable 을 사용 해야 할 때

  1. 수많은 객체들이 생성되며 흘러내려 올 때 chain이 생성하는 소스의 수를 제한할 수 있을 때
  2. 디스크에서 읽기 혹은 파싱 해서 객체를 내려줄 때
  3. JDBC를 통해서 데이터베이스에서 읽고 내려줄 때
  4. 네트워크 IO를 통해서 값을 내려줄 때

Single, Completable and Maybe

Single

싱글 타입은 하나의 onSuccess 혹은 onError 만을 내려줄 수 있는 타입이다. 소비자 역할이 되는 SingleObserver은 3개의 메소드만 가지고있는 인터페이스이며 API는 아래와 같다:

Completable

Completable 타입은 onComplete 혹은 onError 만을 내려줄 수 있는 타입이다. 소비자 역할이 되는 CompletableObserver은 3개의 메소드만 가지고 있으며 API 는 아래와 같다:

Maybe

RxJava 2.0.0-RC2 에서 Maybe 라는 새로운 베이스 타입을 소개했다. 이론적으로는 위의 Single 과 Completable 을 합친것과 같다. API 는 아래와 같다:

결론

결론다시보기

Reference

  • https://msdn.microsoft.com/en-us/library/hh242985(v=vs.103).aspx
  • http://www.reactive-streams.org/
  • https://github.com/ReactiveX/RxJava/wiki/What’s-different-in-2.0
  • https://artemzin.com/blog/reply-to-kaushik-gopals-aricle-rxjava-1-rxjava-2-understanding-the-changes/
  • http://blog.kaush.co/2017/06/21/rxjava1-rxjava2-migration-understanding-changes/
  • https://blog.mindorks.com/migrating-from-rxjava1-to-rxjava2-5dac0a94b4aa