“RxJava — the production line” 번역

며칠 전, RxJava를 처음 접하는 사람들에게도 그 구체적인 실체를 머릿 속에 좀 더 쉽게 그려줄 만한 좋은 글을 발견하여 번역해봅니다.

RxJava에 대해서 설명한 글은 많다. 그렇지만 대부분은 단지 코드일 뿐이고, 예를 들어 설명한 경우가 있다해도, 거의 모두 ‘stream’이라는 개념 설명이 대부분이다. 물론 일반적으로 예제 코드는 그 자체로 충분하다(우리는 프로그래머니까, 맞죠?) 하지만 RxJava는 일상적인 안드로이드 개발과는 다른 점이 아주 많아서 코드만으로 처음 접하는 사람들이 이해하기에 쉽지 않다. 그래서 실제적인 사례로 비유하여 설명하는 예제들이 좀 더 필요하다고 생각한다. 당신의 머릿속에도 RxJava를 더 잘 설명하는 하나의 예제가 떠오르지 않았었나? 나는 그랬었고, 그걸 여기서 공유하려고 한다.

The production line

사실 나는 RxJava를 이해하기 위해서 여러가지 사례를 생각해냈다. 동물원의 우리들을 observe하는 예를 생각했었고, 강과 물고기의 예도 생각해봤다. 심지어는 배트맨과 범죄사건에 적용해보기도 했다. 결국 생산라인의 예가 가장 좋은 설명이 될 거라고 생각했다.

App functionalities

동물의 정보를 표시하는 앱을 만들어보자. 요구사항으로 다음 기능들이 있다.
 * 고양이에 대한 임의의 3가지 사실(facts)
 * 각각의 사실은 유일해야 하고
 * 300자가 넘지 않아야 하며
 * 고양이 이미지가 포함되어야 한다

Start the production!

몇 개의 작업자들로 이 기능들을 구현해보자

  1. 먼저 누군가 생산라인을 시작해야 한다. 예를 들면 결과물을 보고자 하는 생산 관리자 말이다.

2. 이제 API를 이용해서 고양이에 대한 임의의 facts 가져(GET)온다.

3. API의 response에서 HTTP status code는 관심이 없으므로, 첫번째 작업자는 이 코드를 제거하고 facts list를 전달한다.

4. 각 fact에 대해서 길이를 확인하는 등의 작업을 위해서 facts list에서 각각의 fact를 분리한다.

5. 각 fact는 유일해야 하므로 다음 작업자는 중복된 fact들을 제거한다.

6. 300자를 넘는 fact를 제거한다.

7. 이제 각각 유일하면서 300자를 넘지 않는 fact를 획득했지만 fact가 너무 많다. 우리는 3개만 있으면 된다. 다음 작업자는 3개만 추려서 전달한다.

8. fact에 고양이 이미지를 추가한다.

9. 생산 관리자에게 fact 3개를 하나씩 전달하지는 않을테니, 3개의 fact를 하나의 list로 만든다.

10. 생산 관리자는 이제 이 리스트를 화면에 표시할 수 있다.

여기까지 모든 요구사항을 구현했고 결과를 화면에 표시했다.

이 과정이 RxJava와 어떤 연관이 있을까?
매우 깊은 관련이 있다. 일반적으로 RxJava는 (observe할 수 있는 데이터를 생성해내는)Observable과 (생성되는 데이터를 받아서 처리하는)Observer로 구성되어 있는데, 위의 예의 생산라인은 Observable이고 생산관리자는 Observer이다. 중요한 것은 Observer가 생산라인을 시작한다는 점이다. observer가 없으면 생산라인은 가동되지 않는다.

생산라인에서 일하는 작업자들은? RxJava에 있어서 이들은 Operator이다. Operator는 위 예에 있어서 작업자들과 정확히 같은 역할을 한다. 즉 전 단계에서 생성되어 전달된 데이터를 가지고 특정한 작업을 하는 것이다.(예를 들면 중복을 제거하는 일)

생산라인을 코드로 표현해보자

mCatFactsService.getCatFacts(100)
.map(catFactResponse -> catFactResponse.getFacts())
.flatMap(catFacts -> Observable.from(catFacts))
.distinct()
.filter(catFact -> catFact.length() <= 300)
.take(3)
.map(catFact -> new CatFactWithImage(catFact, getRandomCatImageId()))
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(mCatFactsAdapter::setCatFactWithImages, Throwable::printStackTrace);

이 RxJava 코드를 살펴보자. 위에서 부터 아래로 순차적으로 하나씩 실행된다.

* mCatFactsService.getCatFacts(100) — 이 부분이 GET 인데 여기서는 응답으로 받은 HTTP status와 cat facts list를 객체에 저장해 놓는다. 이 객체(CatFactResponse라고 하자)는 Observable로 감싸져 있고, 이 Observable에 Operator를 이용해서 이후에 나오는 RxJava 오퍼레이션을 수행할 수 있다.

* map(), flatMap(), distinct(), filter(), take(), toList() — 이들이 Operator이고, 여기서는 전달받은 데이터를 조작한다. 위 생산라인 예에 있어서의 작업자들과 같은 역할을 한다.

* subscribe() — Observer가 Observable을 subscribe한다(생산관리자가 공정의 산출물을 관찰하고 결과를 받는다.로 이해하면 될 듯하다) subscribe 없이는 위의 코드가 동작을 시작하지 않는다.

* subscribeOn() — Observable이 시작될 쓰레드를 지정한다. 각 Operator는 다시 지정하기 전까지 이 쓰레드에서 수행된다.

* observeOn() — 이 코드 이후의 Operator들이 실행될 쓰레드를 지정한다.

(요건 제 의견 — 혹시 subscribeOn, observeOn이 잘 이해가 가지 않는다면 http://reactivex.io/documentation/operators/subscribeon.htmlhttp://reactivex.io/documentation/operators/observeon.html 을 참고하면 좋을 듯.)

Operator 들의 코드를 좀 더 살펴보자

.map(new Func1<CatFactResponse, List<String>>() {
@Override
public List<String> call(CatFactResponse catFactResponse) {
return catFactResponse.getFacts();
}
})

retrolambda로 Java 8 lambdas 표현을 사용할 수 있지만 이해를 위해서 전체 코드를 그대로 두었다.

위의 코드에서 보듯이 mCatFactsService.getCatFacts() 는 CatFactResponse 객체에 특정 데이터를 담아서 준다. 하지만 HTTP status code가 필요없으므로 MAP operator를 이용해서 CatFactResponse의 fact부분만을 List<String> 객체로 변환한다. 다음 Operator가 이 리스트를 이용할 것이다.

.flatMap(new Func1<List<String>, Observable<? extends String>>() {
@Override
public Observable<? extends String> call(List<String> catFacts) {
return Observable.from(catFacts);
}
})

FlatMap Operator는 cat facts list를 전달받아서, 각각의 개별 fact를 하나씩 Observable로 감싸서 다음으로 전달한다. 그래서 list로부터(FROM) 각각의 fact를 꺼내서 전달했다.

.distinct()

이 operator는 전달받은 fact String 객체를, 중복을 제거하고 전달한다.

.filter(new Func1<String, Boolean>() {
@Override
public Boolean call(String catFact) {
return catFact.length() <= 300;
}
})

Filter는 true/false를 리턴하는데 300자 이하일 경우만 true를 리턴하여 300자 이하인 fact만 다음으로 전달한다.

.take(3)

전달받은 fact 객체에서 3개까지만 다음으로 전달한다.

.map(new Func1<String, CatFactWithImage>() {
@Override
public CatFactWithImage call(String catFact) {
return new CatFactWithImage(catFact, getRandomCatImageId());
}
})

또 map operator이다. 각 fact에 고양이 이미지를 포함시켜서 새로운 객체를 넘긴다.

.toList()

이렇게 전달받은 각 객체들을 모아서 하나의 리스트로 만든다.

.subscribe(new Observer<List<CatFactWithImage>>() {
@Override
public void onCompleted() {
//no-op
}
     @Override
public void onError(Throwable e) {
e.printStackTrace();
}
     @Override
public void onNext(List<CatFactWithImage> catFactWithImages) {
mCatFactsAdapter.setCatFactWithImages(catFactWithImages);
}
});

이제 이 리스트를 받아서 처리하면 된다.

Conclusion

RxJava는 강력한 도구이긴 하지만 ‘stream’ 방식으로 생각하고 코딩한 경험이 없다면 이해하기 쉬운 것은 아니다. 안드로이드 개발자 입장에서는 상당히 다른 접근 방식이기 때문에 코드 이상의 다른 설명이 필요하다고 생각했다. 이 글이 RxJava의 동작에 대해서 이해하는 데 도움이 되길 바란다.

여기까지입니다.

원글 by Mateusz Budzar : http://www.thedroidsonroids.com/blog/android/rxjava-production-line/