[Spring/WebFlux] 리액티브 시스템(Reactive System)과 리액티브 프로그래밍(Reactive Programming)

리액티브 시스템과 리액티브 프로그래밍이 무엇인지 살펴봅시다.


리액티브 시스템(Reactive System)이란?

리액티브 시스템은 쉽게 말해 반응을 잘하는 시스템입니다. 

반응을 잘한다는 것은 클라이언트의 요청에 머뭇거리지 않고 반응을 잘해서 즉시 응답해 주는 것을 의미합니다. 다시 말해서,

클라이언트의 요청에 즉각적으로 응답함으로써 지연 시간을 최소화한다

고 볼 수 있습니다.

 

리액티브 선언문

리액티브 선언문은 

리액티브 시스템 구축을 위한 일종의 설계 원칙이자 리액티브 시스템의 특징

을 나타낸 것입니다.

자세한 내용은 아래 링크에서 확인 할 수 있습니다.

https://www.reactivemanifesto.org/ko

 

리액티브 선언문

탄력성(Resilient): 시스템이 장애 에 직면하더라도 응답성을 유지 하는 것을 탄력성이 있다고 합니다. 탄력성은 고가용성 시스템, 미션 크리티컬 시스템에만 적용되지 않습니다. 탄력성이 없는 시

www.reactivemanifesto.org

 

리액티브 선언문에서 자향하는 설계 원칙은 아래 그림 하나로 명확하게 설명됩니다.

MEANS

MEANS(방법) 영역은 리액티브 시스템에서 주요 통신 수단으로 무엇을 사용할 것인지 표현한 것입니다. 위 그림의 비동기 메시지 기반의 통신을 통해 구성요소들 간의 느슨한 결합, 격리성, 위치 투명성을 보장합니다.

FORM

FORM(형태) 영역은 메시지 기반 통신을 통해서 어떠한 형태를 지니는 시스템으로 형성되는지를 나타냅니다. 

탄력성(Elastic)

리액티드 시스템에서 탄력성이란, 

시스템의 작업량이 변화하더라도 일정한 응답을 유지하는 것

을 의미합니다. 즉, 시스템으로 유입되는 입력이 많든 적든 시스템에서 요구하는 응답성을 일정하게 유지하는 것을 말합니다.

회복성(Resilient)

리액티브 시스템에서 회복성이란,

시스템에 장애가 발생하더라도 응답성을 유지하는 것

을 의미합니다. 

이러한 회복성을 확보하기 위해 비동기 메시지 기반 통신을 통해 느슨한 결합격리성을 보장합니다. 즉, 구성요소들이 독립적으로 분리되어 있어 장애가 발생해도 전체 시스템은 여전히 응답 가능하다는 것을 의미합니다. 

VALUE

VALUE(값) 영역은 위의 회복성과 탄력성을 확보함으로써 즉각적으로 응답 가능한 시스템구축할 수 있음을 의미합니다. 즉, 리액티브 시스템의 핵심 가치를 보여 준다고 볼 수 있습니다. 

 

위의 리액티브 시스템을 통해 빠른 응답성을 바탕으로 유지보수와 확장이 용이한 시스템을 구축하는데 활용 가능합니다.

리액티브 프로그래밍(Reactive Programming)이란?

리액티브 프로그래밍은

리액티브 시스템을 구축하는 데 필요한 프로그래밍 모델

입니다.

 

리액티브 시스템에서 비동기 메시지 통신은 Non-Blocking I/O 방식의 통신입니다. Non-Blocking I/O 방식은 해당 스레드가 작업을 처리할 때까지 남은 작업들이 차단되는 Blocking I/O 방식과 달리, 스레드가 차단되지 않습니다. 

특징

declarative programming

선언형 프로그래밍이라는 의미입니다. 흔히 우리가 사용하는 C, Java ... 와 같은 전통적인 프로그래밍 방식은 명령형 프로그래밍 방식입니다. 즉, 실행할 동작을 구체적으로 명시하는 형태입니다.

하지만, 선언형 프로그래밍 방식은 이와 달리 실행할 동작을 구체적으로 명시하지 않고 어떠한 동작을 하겠다는 목표만 선언합니다. 아래에서 자세히 살펴보겠습니다.

data streams 와 the propagation of change

data streams = 데이터 흐름, 데이터가 지속적으로 발생

the propagation of change = data stream을 변화하는 이벤트로 보고, 이 이벤트를 발생시키면서 데이터를 계속 전달하는 것

한 마디로, 데이터 소스의 변경이 있을 때마다 데이터를 전파합니다.

 

명령형 프로그래밍 vs 선언형 프로그래밍

다시 얘기해보면,

  • 명령형 프로그래밍 := 어떤 작업을 처리하기 위해 실행할 동작을 코드에 구체적으로 명시하는 방식
  • 선언형 프로그래밍 := 실행할 동작을 구체적으로 명시하지 않고 목표만 선언하는 방식

간단한 예제를 통해 살펴봅시다.

명령형 프로그래밍

Public class Example1_1 {
	public static void main(String[] args) {
            List<Integer> numbers = Arrays.asList(1, 3, 21, 10, 8, 11);
            int sum = 0;
            for(int number : numbers) {
                if(number > 6 && (number % 2) != 0) {
                    sum += number;
                }
            }

            System.out.println("SUM : " + sum);
	}
}

위 코드에서 처리할 작업은 `numbers` List에 포함된 수 중 6보다 크고 홀수인 숫자들의 합계를 구하는 것입니다. 이 작업을 처리하기 위해 여러 동작들을 코드에 명시합니다. 

  1. for 문을 통해 numbers에서 각 숫자들에 접근하는 동작
  2. if 문을 사용해 6보다 크고 홀수인 숫자만 선택하는 동작
  3. 선택된 숫자들을 sum 이라는 변수에 차례대로 더하는 동작

그럼 위 코드가 선언형 프로그래밍 방식으로 어떻게 바뀌는 지 살펴봅시다. 

선언형 프로그래밍

Public class Example1_2 {
	public static void main(String[] args) {
            List<Integer> numbers = Arrays.asList(1, 3, 21, 10, 8, 11);
            int sum = numbers.stream()
                .filter(number -> number > 6 && (number % 2 != 0))
                .mapToInt(number -> number)
                .sum();

            System.out.println("SUM : " + sum);
	}
}

위 코드의 가장 큰 특징은 List의 각 숫자에 접근하는 동작을 하는 for 문이 사라진 것입니다. 사라진 이유는 for 문에서 하는 구체적인 동작을 java 스트림 내부에서 직접 해 주기 때문입니다. 

또, if 문 대신 `filter` 메서드를 이용한 것, `sum` 메서드를 통해 숫자를 더하는 것 모두 스트림 내부에서 처리하는 방식입니다.

정리해보자면,

  • 동작을 구체적으로 명시하지 않고 목표만 선언합니다.
  • 여러 동작을 각각 별도의 코드로 분리하지 않고 각 동작에 대해 메서드 체인을 형성해 한 문장으로 된 코드로 구성합니다. 이를 통해 코드의 간결함과 가독성을 높일 수 있습니다. 
  • 함수형 프로그래밍 방식으로 구성됩니다.

리액티브 프로그래밍 코드 구성

리액티브 프로그래밍 코드는 크게 Publisher, Subscriber, Data Source, Operator 등으로 구성됩니다. 

Publisher

Publisher는 여러 가지 용어를 사용하지만 발행자 정도로 해석할 수 있습니다. 용어들의 공통점은

입력으로 들어오는 데이터를 제공하는 역할

을 한다는 것입니다. 

Subscriber

Subscriber구독자 혹은 소비자라 불리며, Publisher가 제공한 데이터를 전달받아 사용하는 주체라고 할 수 있습니다. 즉,

Publisher로부터 전달받은 데이터를 사용하는 역할

을 한다는 것입니다. 

Data Source

Data Source는 리액티브 프로그래밍에서 Data Stream이라고도 표현하며

Publisher의 입력으로 전달되는 데이터

를 의미합니다.

Operator

Publisher로부터 전달된 데이터는 애플리케이션의 요구사항에 맞게 적절한 가공 처리를 통해 Subscriber에 전달되는데, 이 가공 처리를 담당하는 것이 바로 Operator입니다. 즉,

Publisher와 Subscriber 중간에서 데이터를 가공하는 역할

을 합니다.

 

데이터를 생성하는 Operator부터, 데이터 필터링, 데이터 변환 등 리액티브 프로그래밍에는 수많은 Operator가 존재합니다. 

 


참고

"스프링으로 시작하는 리액티브 프로그래밍"