개요
- 리액티브 프로그래밍의 개념에 대해 알아봅니다.
- 리액티브 스트림즈의 개념에 대해 알아봅니다.
- 스프링으로 시작하는 리액티브 프로그래밍 책 1~2장을 참고하였습니다.
리액티브 시스템
- 리액티브(reactive)의 사전적 의미는 반응을 하는 이라는 뜻 입니다.
- 리액티브는 시스템은 어떤 이벤트나 상황이 발생했을 때 반응을 잘하는 시스템을 말합니다.
- 반응을 잘한다는 의미는 클라이언트 요청에 즉각적으로 응답하여 지연 시간을 최소화하는 것을 말합니다.
리액티브 선언문
- MEANS는 리액티브 시스템에서 주요 통신 수단을 말합니다. 비동기 메시지 기반의 통신을 통해 느슨한 결합, 격리, 위치 투명성을 보장합니다.
- FORM은 메시지 기반 통신을 통해 어떤 형태를 지니는 시스템으로 형성되는지 나타냅니다. 리액티브 시스템은 비동기 메시지 통신 기반으로 탄력성과 회복성을 가지는 시스템이어야 합니다.
- 리액티브 시스템에서 탄력성(Elastic)이란 시스템의 작업량이 변화하더라도 일정한 응답을 유지하는 것을 의미합니다. 일정한 응답성을 유지하기 위헤 입력을 처리하기 위한 시스템 자원을 증가시키거나 감소시키면서 변화에 대응합니다.
- 리액티브 시스템에서 회복성(Resilient)이란 시스템에 장애가 발생하더라도 응답성을 유지하는 것을 말합니다. 회복성을 확보하기 위해서 리액티브 시스템의 구성 요소들은 비동기 메시지 기반 통신을 통해 느슨한 결합과 격리성을 보장합니다. 즉 구성 요소들이 독립적으로 분리되어 장애가 발생하더라도 전체 시스템은 응답 가능하고 장애가 발생한 부분만 복구하면 된다는 의미입니다.
- VALUE는 비동기 메시지 기반 통신을 바탕으로 한 회복성과 탄력성을 통해 즉각적으로 응답 가능한 시스템을 구축할 수 있음을 의미합니다. 즉 리액티브 시스템의 핵심 가치를 말합니다.
리액티브 프로그래밍
- 리액티브 프로그래밍은 리액티브 시스템을 구축하는데 필요한 프로그래밍 모델입니다.
- 리액티브 시스템은 비동기 메시지 통신을 기반으로 한다고 설명하였습니다. 비동기 메시지 통신은 Non-Blocking I/O 방식의 통신입니다.
- 리액티브 프로그래밍은 리액티브 시스템의 설계 원칙에 잘 부합하는 비동기 Non-Blocking 통신을 위한 프로그래밍 모델입니다.
리액티브 프로그래밍의 특징
- 선언형 프로그래밍
- 기존의 전통적인 명령형 프로그래밍 방식은 실행할 동작을 구체적으로 명시하는 프로그래밍 코드 형태입니다. ex) 반복문을 통한 필터링
- 선언형 프로그래밍 방식은 실행할 동작을 구체적으로 명시하지 않고 목표만 선언합니다. ex) Java 스트림을 통한 필터링
- 데이터 스트림
- 데이터가 지속적으로 발생하고, 그 때마다 이벤트를 발생시키면서 데이터를 계속적으로 전달하는 것을 의미합니다.
리액티브 프로그래밍 코드 구성 요소
리액티브 프로그래밍 코드는 크게 Publisher, Subscriber, Data Source, Operator 등으로 구성됩니다.
- Publisher
- 발행인, 발행자 정도로 해석합니다. 입력으로 들어오는 데이터를 제공하는 역할을 합니다.
- Subscriber
- Publisher가 제공한 데이터를 전달받아서 사용하는 구독자입니다.
- Data Source
- Publisher의 입력으로 들어오는 데이터를 말합니다.
- Operator
- Publisher로부터 전달된 데이터는 애플리케이션의 요구사항에 맞게 Publisher와 Subscriber 사이에서 적절한 가공 처리가 이루어지는데 이 처리를 담당하는 것이 Operator입니다.
리액티브 스트림즈
- 개발자가 리액티브한 코드를 작성하기 위해서는 이러한 코드 구성을 용이하게 해주는 리액티브 라이브러리가 있어야 합니다. 리액티브 라이브러리를 어떻게 구현할지 정의해 놓은 표준 사양이 있는데 이것을 리액티브 스트림즈(Reactive Streams)라고 부릅니다.
- 리액티브 스트림즈는 '데이터 스트림을 Non-Blocking이면서 비동기적인 방식으로 처리하기 위한 리액티브 라이브러리의 표준 사양' 이라고 표현할 수 있습니다.
- 리액티브 스트림즈를 구현한 구현체로 RxJava, Reactor, Akka Streams, Java 9 Flow API 등이 있습니다.
리액티브 스트림즈의 구성요소
- 리액티브 스트림즈(표준 사양)를 통해 구현해야 되는 API 컴포넌트에는 Publisher, Subscriber, Subscription, Processor가 있습니다.
- Publisher
- Subscriber
- 구독한 Publisher로부터 통지된 데이터를 전달받아 처리하는 역할을 합니다.
- Subscription
- Publisher에 요청할 데이터의 개수를 지정하고, 데이터의 구독을 취소하는 역할을 합니다.
- Processor
- Publisher, Subscriber의 기능 모두를 가지고 있습니다. Subscriber로서 다른 Publisher를 구독할 수 있고, Publisher로서 다른 Subscriber가 구독할 수 있습니다.
아래와 같은 과정을 통해 Publisher와 Subscriber간에 데이터가 전달됩니다.
그림에서는 Publisher와 Subscriber가 같은 스레드에서 동기적으로 상호작용하는 것처럼 보이지만 실제로 각각 다른 스레드에서 비동기적으로 상호작용 하는 경우가 대부분입니다. 따라서 Publisher가 통지하는 속도가 Subscriber가 통지된 데이터를 처리하는 속도보다 더 빠르면 처리를 기다리는 데이터가 쌓이게 됩니다. 따라서 Subscription.request를 통해 Subscriber가 데이터의 요청 개수를 지정합니다.
리액티브 스트림즈 컴포넌트 코드
public interface Publisher<T> {
public void subscribe(Subscriber<? super T> s);
}
- subscribe 메서드는 파라미터로 전달받은 Subscriber를 등록하는 역할을 합니다.
public interface Subscriber<T> {
public void onSubscribe(Subscription s);
public void onNext(T t);
public void onError(Throwable t);
public void onComplete();
}
- onSubscribe 메서드는 구독 시작 시점에 어떤 처리를 하는 역할을 합니다. 처리는 Publisher에게 요청할 데이터의 개수를 지정하거나 구독을 해지하는 것을 의미합니다. 이것은 onSubscribe 메서드의 파라미터로 전달된 Subscription 객체를 통해서 이루어집니다.
- onNext 메서드는 Publisher가 통지한 데이터를 처리하는 역할을 합니다.
- onError 메서드는 Publisher가 데이터 통지를 위한 처리 과정에서 에러가 발생했을 때 해당 에러를 처리하는 역할을 합니다.
- onComplete 메서드는 Publisher가 데이터 통지를 완료했음을 알릴 때 호출되는 메서드입니다.
public interface Subscription {
public void request(long n);
public void cancel();
}
- request 메서드를 통해 Publisher에게 데이터의 개수를 요청합니다.
- cancel 메서드를 통해 구독을 해지합니다.
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}
- Processor는 Subscriber 인터페이스와 Publisher 인터페이스를 상속합니다. Processor는 Publisher와 Subscriber의 기능을 모두 가지고 있기 때문입니다.
리액티브 스트림즈 관련 용어
- Signal
- Publisher와 Subscriber간에 주고받는 상호작용을 신호, Signal이라고 표현합니다.
- 리액티브 스트림즈의 인터페이스 코드의
onSubscribe
, onNext
, onComplete
, onError
, request
, cancel
메서드가 Signal 입니다.
- Demand
- Demand는 사전적으로 수요, 요구 등을 의미하는데, 리액티브 스트림즈에서도 비슷한 의미입니다.
- Demand는 Subscriber가 Publisher에게 요청하는 데이터를 의미합니다. 구체적으로 Publisher가 아직 Subscriber에게 전달하지 않은 데이터 중 Subscriber가 요청한 데이터를 말합니다.
- Emit
- Upstream/Downstream
- Upstream은 위로 흐르는 것을, Downstream은 아래로 흐르는 것을 의미합니다. 흐르는 주체는 데이터입니다.
- 아래와 같은 Reactor 코드에서,
just
메서드 호출을 통해 반환된 Flux를 기준으로 filter
메서드 호출을 통해 반환된 Flux는 하위이므로 Downstram 입니다. 반대로 filter
메서드 호출을 통해 반환된 Flux를 기준으로 하면 just
메서드 호출을 통해 반환된 Flux는 Upstream이 됩니다.
Flux.just(1, 2, 3, 4, 5, 6)
.filter(n -> n % 2 == 0)
.map(n -> n * 2)
.subscribe(System.out::println);
- Sequence
- Sequence는 Publisher가 emit하는 데이터의 연속적인 흐름을 정의해 놓은 것 자체를 의미합니다.
- Sequence는 Operator 체인 형태로 정의됩니다.
- 위의
Flux
의 메서드 체인을 통해 데이터를 생성하고 필터링하고 변환하는 과정 자체를 Sequence라고 합니다.
- Operator
- 연산자를 의미하는데, 위 코드의
just
, filter
, map
같은 메서드를 리액티브 프로그래밍에서는 연산자라고 부릅니다.
- Source
- 리액티브 스트림즈 또는 리액티브 프로그래밍 관련 문서에서 Source라는 용어는 대부분 '최초의' 라는 의미로 사용됩니다. 비슷한 의미로 Original 이라는 용어도 사용됩니다.
리액티브 스트림즈 구현체
- RxJava
- Project Reactor
- Akka Streams
- Java Flow API
- Java 9부터 Flow API를 사용하여 리액티브 스트림즈를 지원합니다.
- Flow API는 다른 리액티브 스트림즈 구현체들과 차이가 있습니다. Flow API는 Reactor, RxJava, Akka Streams처럼 리액티브 스트림즈를 구현한 구현체가 아니라 리액티브 스트림즈의 표준 사양이 Service Provider Interface로써 Java API에 정의되어 있습니다.
- 이 외에도 RxAndroid, RxJS, RxKotlin등 다양한 구현체들이 있습니다.