클라이언트의 요청에 반응을 잘하는 시스템
리액티브 시스템 관점에서의 반응은 쓰레드의 Non-Blocking과 관련이 있다.
리액티브 시스템은 클라이언트의 요청에 대한 응답 대기 시간을 최소화 할 수 있도록 요청 쓰레드가 차단되지 않게 함으로써(Non-Blocking) 클라이언트에게 즉각적으로 반응하도록 구성된 시스템이라고 볼 수 있다.
MEANS
MEANS는 리액티브 시스템에서 사용하는 커뮤니케이션 수단을 의미한다.
Message Driven
FORM
FORM은 메시지 기반 통신을 통해 리액티브 시스템이 어떤 특성을 가지는 구조로 형성되는지를 의미한다.
Elastic
Resillient
VALUE
리액티브 시스템의 핵심 가치가 무엇인지를 표현하는 영역이다.
Responsive
Maintainable
Extensible
리액티브 시스템에서 사용되는 프로그래밍 모델
리액티브 시스템에서의 메시지 기반 통신(Message Driven)은 Non-Blocking 통신과 유기적인 관계를 맺고 있으며, 리액티브 프로그래밍은 Non-Blocking 통신을 위한 프로그래밍 모델
선언형 프로그래밍 방식을 사용하는 대표적인 프로그래밍 모델
지속적으로 데이터가 입력으로 들어올 수 있고, 리액티브 프로그래밍에서는 데이터가 지속적으로 발생하는 것 자체를 데이터에 어떤 변경이 발생함을 의미한다.
그리고 이 변경 자체를 이벤트로 간주하고, 이벤트가 발생할 때 마다 데이터를 계속해서 전달한다.
지속적으로 발생하는 데이터를 하나의 데이터 플로우로 보고 데이터를 자동으로 전달한다.
public interface Publisher<T> {
public void subscribe(Subscriber<? super T> s);
}
Publisher
인터페이스는 데이터 소스로 부터 데이터를 내보내는(emit) 역할을 한다.
Publisher
인터페이스는 코드와 같이 subscribe()
추상 메서드를 포함하고 있으며, subscribe()
의 파라미터로 전달되는 Subscriber가 Publisher로부터 내보내진 데이터를 소비하는 역할을 한다.
subscribe()
는 메서드 이름에서도 알 수 있듯이 Publisher가 내보내는 데이터를 수신할 지 여부를 결정하는 구독의 의미를 가지고 있으며, 일반적으로 subscribe()
가 호출되지 않으면 Publisher가 데이터를 내보내는 프로세스는 시작되지 않는다.
public interface Subscriber<T> {
public void onSubscribe(Subscription s);
public void onNext(T t);
public void onError(Throwable t);
public void onComplete();
}
Subscriber
인터페이스는 Publisher로부터 내보내진 데이터를 소비하는 역할을 한다.
Subscriber
는 네 개의 추상 메서드를 포함하고 있으며, 각 메서드의 역할은 다음과 같다.
onSubscribe(Subscription s)
구독이 시작되는 시점에 호출되며, onSubscribe()
내에서 Publisher에게 요청할 데이터의 개수를 지정하거나 구독 해지 처리를 할 수 있다.
onNext(T t)
Publisher가 데이터를 emit할 때 호출되며, emit된 데이터를 전달 받아서 소비할 수 있다.
onError(Throwable t)
Publisher로부터 emit된 데이터가 Subscriber에게 전달되는 과정에서 에러가 발생할 경우에 호출된다.
onComplete()
Publisher가 데이터를 emit하는 과정이 종료될 경우 호출되며, 데이터의 emit이 정상적으로 완료 된 후, 처리해야 될 작업이 있다면 onComplete() 내에서 수행할 수 있다.
public interface Subscription {
public void request(long n);
public void cancel();
}
Subscription
인터페이스는 Subscriber의 구독 자체를 표현한 인터페이스이다.
request(long n)
Publisher가 emit하는 데이터의 개수를 요청한다.
cancel()
구독을 해지하는 역할을 한다. 즉, 구독 해지가 발생하면 Publisher는 더이상 데이터를 emit하지 않는다.
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}
Processor 인터페이스는 Subscriber 인터페이스와 Publisher 인터페이스를 상속하고 있기 때문에 Publisher와 Subscriber의 역할을 동시에 할 수 있는 특징을 가지고 있으며, 별도로 구현해야 되는 추상 메서드는 없다.
Project Reactor(줄여서 Reactor)는 리액티브 스트림즈를 구현한 대표적인 구현체로써 Spring과 궁합이 가장 잘 맞는 리액티브 스트림즈 구현체이다.
Reactor는 Spring 5의 리액티브 스택에 포함되어 있으며 Sprig Reactive Application 구현에 있어 핵심적인 역할을 담당하고 있다.
RxJava는 .NET 기반의 리액티브 라이브러리를 넷플릭스에서 Java 언어로 포팅한 JVM 기반의 리액티브 확장 라이브러리입니다.
RxJava의 경우 2.0부터 리액티브 스트림즈 표준 사양을 준수하고 있으며, 이 전 버전의 컴포넌트와 함께 혼용되어 사용이 되고 있다.
Java 역시 Java 9부터 리액티브 스트림즈를 지원하고 있다. 그런데 Flow API는 리액티브 스트림즈를 구현한 구현체가 아니라 리액티브 스트림즈 표준 사양을 Java 안에 포함을 시킨 구조라고 볼 수 있다.
즉, 다양한 벤더들이 JDBC API를 구현한 드라이버를 제공할 수 있도록 SPI(Service Provider Interface) 역할을 하는것 처럼 Flow API 역시 리액티브 스트림즈 사양을 구현한 여러 구현체들에 대한 SPI 역할을 한다고 보면 될 것 같다.
RxJava의 Rx
는 Reactive Extension의 줄임말이다.
이 의미는 특정 언어에서 리액티브 스트림즈를 구현한 별도의 구현체가 존재한다는 의미이며, 실제로 다양한 프로그래밍 언어에서 리액티브 스트림즈를 구현한 리액티브 확장(Reactive Extension) 라이브러리를 제공하고 있다.
대표적인 리액티브 확장(Reactive Extension) 라이브러리로 앞에서 언급한 RxJava가 있으며, 이외에도 RxJS, RxAndroid, RxKotlin, RxPython, RxScala 등이 있다.
public class DeclarativeProgramingExample {
public static void main(String[] args){
// List에 있는 숫자들 중에서 4보다 큰 짝수의 합계 구하기
List<Integer> numbers = List.of(1, 3, 6, 7, 8, 11);
int sum =
numbers.stream()
.filter(number -> number > 4 && (number % 2 == 0))
.mapToInt(number -> number)
.sum();
System.out.println("# 선언형 프로그래밍: " + sum);
}
}
Stream의 경우 최종 연산을 수행하는 메서드를 호출하지 않으면 앞에서 작성한 메서드 체인들이 실행 되지 않는다.
다시 말해 Stream의 메서드 체인(중간 연산)에는 이러 이러한 작업을 해 달라고 선언(요청)하는 람다 표현식만 넘겨주고, 최종 연산이 호출될 때 비로소 전달 받은 람다 표현식을 기반으로 동작을 수행한다.
따라서 선언형 프로그래밍 방식은 하나부터 열까지 개발자가 일일이 로직을 모두 작성하지 않는다.
대신에 정말 필요한 동작들을 람다 표현식으로 정의(선언)하고 구체적인 동작 수행은 Operation(연산) 메서드 체인에 위임한다.
import reactor.core.publisher.Mono;
public class HelloReactiveExample01 {
public static void main(String[] args) {
// 1 Publisher의 역할
Mono<String> mono = Mono.just("Hello, Reactive");
// 2 Subscriber의 역할
mono.subscribe(message -> System.out.println(message));
}
}
Puhlisher
의 역할을 하는 것이 바로 Mono
이다.
그리고 Subscriber의 역할을 하는 것이 바로 subscribe()
메서드 내부에 정의된 람다 표현식인 message -> System.out.println(message)
이다.
import reactor.core.publisher.Mono;
public class HelloReactiveExample02 {
public static void main(String[] args) {
Mono
.just("Hello, Reactive")
.subscribe(message -> System.out.println(message));
}
}
위의 코드를 메서드 체인 형태로 표현이 가능하다.
import reactor.core.publisher.Flux;
import java.util.List;
public class ReactiveGlossaryExample {
public static void main(String[] args) {
Flux
.fromIterable(List.of(1, 3, 6, 7, 8, 11))
.filter(number -> number > 4 && (number % 2 ==0))
.reduce((n1, n2) -> n1 + n2)
.subscribe(System.out::println);
}
}
Publisher
리액티브 스트림즈 사양에서도 확인한 것 처럼 Publisher는 데이터를 내보내는 주체를 의미한다.
위 코드 에서는 Flux가 Publisher이다.
Emit
Publisher가 데이터를 내보내는 것을 Emit 이라고 한다.
Subscriber
Subscriber는 Publisher가 emit한 데이터를 전달 받아서 소비하는 주체를 의미한다.
위 코드에서는 subscribe(System.out::println) 중에서 System.out::println
이 Subscriber에 해당 된다. 람다 표현식을 메서드 레퍼런스로 축약하지 않았다면 람다 표현식 자체가 Subscriber에 해당된다.
Subscribe
Subscribe는 구독을 의미한다.
위 코드와 같이 subscribe()
메서드를 호출하면 구독을 하는 것이다.
Signal
리액티브 프로그래밍 관련 문서를 보다보면 Signal이라는 용어를 굉장히 많이 볼 수 있다.
Signal은 Publisher가 발생시키는 이벤트를 의미한다.
예를 들어서 위 코드에서 subscribe() 메서드가 호출되면 Publisher인 Flux는 숫자 데이터를 하나씩 하나씩 emit 한다.
이때 숫자 데이터를 하나씩 emit하는 자체를 리액티브 프로그래밍에서는 이벤트가 발생하는 것으로 간주하며, 이 이벤트 발생을 다른 컴포넌트에게 전달하는 것을 Signal을 전송한다라고 표현한다.
Operator
Operator는 리액티브 프로그래밍에서 어떤 동작을 수행하는 메서드를 의미한다.
위 코드에서 fromIterable()
, filter()
, reduce()
등 메서드 하나 하나를 Operator라고 한다.
Sequence
Sequence는 Operator 체인으로 표현되는 데이터의 흐름을 의미한다.
위 코드에서 Operator 체인으로 작성된 코드 자체를 하나의 Sequence
라고 이해하면 된다.
Upstream / Downstream
Sequence 상의 특정 Operator를 기준으로 위쪽의 Sequence 일부를 Upstream이라고 하며, 아래 쪽 Sequence 일부를 Downstream이라고 표현한다.
위 코드에서 filter()
Operator를 기준에서 보면 filter() Operator 위 쪽의 fromIterable()은 Upstream
이 된다.
그리고 filter() Operator 아래 쪽의 reduce() Operator는 Downstream
이 된다.