Spring Webflux를 학습하기 이전에 Reactive Programming에 대해서 명령형 프로그래맹, 선언적 프로그래밍 그리고 Data Stream 과 주요 특징들, Reactive Stream 과 주요 요소들에 대해서 자세하게 알아보도록 하겠습니다.
서론
사실 Reactive Programming을 spring에 작성해야 할까 고민을 좀 했다.
Rxjava도 있고 Rxjs도 있는 만큼 Reactive X를 활용하는 곳에 사용해도 되기 때문이다.
WebFlux에 대한 포스팅을 하기전 Programming 패러다임부터 하나하나 전부 작성할 것 이기 때문에 Spring 카테고리에 작성하도록 하겠습니다.
Reactive Programing ?
In computing, reactive programmingis a declarative programming paradigm concerned with data streams
and the propagation of change. With this paradigm, it's possible to express static (e.g., arrays) or dynamic (e.g., event emitters) data streams with ease, and also communicate that an inferred dependency within the associated execution model exists, which facilitates the automatic propagation of the changed data flow
-wiki-
"Reactive Programming은 데이터 스트림과 관련된 선언적 프로그래밍 패러다임입니다.
이 패러다임을 사용하면 정적(예: 배열) 또는 동적(예: 이벤트 이미터) 데이터 스트림을 쉽게 표현할 수 있으며, 관련 실행 모델 내에 추론된 dependency가 존재함을 전달하여 변경된 데이터 흐름의 자동 전파를 용이하게 할 수 있습니다."
wiki에서는 Reactive Programming에 위와 같이 말합니다.
주된 keyword는 두 가지입니다.
1. 선언적 프로그래밍
2. 변경된 데이터 흐름의 자동 전파
선언적 프로그래밍
명령형 프로그래밍 : 어떻게 하는지(how you do) 알고리즘을 묘사합니다.
선언적 프로그래밍 : 무엇을(what you do) 해야 하는지 묘사합니다.
테이블 예시
명령형 프로그래밍
12번 테이블이 비어있으니 저는 저쪽에 가서 앉겠습니다. (어떻게 테이블에 가서 앉을 것인지 단계를 나열해야 함.)
선언적 프로그래밍
혼자 앉을 테이블을 주세요.(원하는 테이블 즉 혼자 앉을 테이블에 포커스.)
월마트 예시
"월마트에서 A의 집까지 어떻게 갈 수 있을까요?"
명령형 프로그래밍
"주차장 북쪽 출구로 나가서 좌회전하세요. 12번가 출구가 나올 때까지 북쪽으로 가는 I-15번 도로를 타세요. 출구로 나와서 이케아로 가듯이 우회전하세요. 직진하여 첫 번째 신호등에서 우회전하세요. 다음 신호등을 통과한 후 다음 좌회전하세요. 저희 집은 298번지입니다."
선언적 프로그래밍
제 주소는 298 West Immutable Alley, Eden, Utah 84310입니다.
변경된 데이터 흐름의 자동 전파
예를 들어
명령형 프로그래밍 설정에서 a := b + c는 표현식이 평가되는 순간 a에 b + c의 결과가 할당되고 있음을 의미하며 나중에 b 및 c의 값이 a 값에 영향을 주지 않고 변경될 수 있습니다.
반면에 Reactive Programming에서는 프로그램이 현재 할당된 a 값을 결정하기 위해 a := b + c 문을 명시 적으로 다시 실행할 필요 없이 b 또는 c의 값이 변경될 때마다 a의 값이 자동으로 업데이트됩니다.
위와 같이 Reactive Programming에서는 시스템의 구성 요소가 선형적인 명령 순서를 따르지 않고 이벤트나 데이터 변경에 반응합니다.
이 접근 방식을 통해 애플리케이션은 여러 이벤트를 동시에 처리하고 리소스 사용을 최소화하며 다양한 워크로드를 원활하게 처리할 수 있습니다.
조금더 Data Stream 적 측면에서 접근해봅시다.
다음은 Spring 프로젝트의 맥락에서 Event와 DataStream에 대한 접근 방식을 비교한 것입니다
Event
기존 Spring 프로젝트에서 이벤트는 비동기 작업을 처리하고 컴포넌트를 분리하는 데 자주 사용됩니다.
이벤트 중심 프로그래밍은 이벤트가 발생할 때 실행할 정확한 단계를 정의하는 필수 접근 방식입니다.
// Event
public class CustomEvent extends ApplicationEvent {
public CustomEvent(Object source) {
super(source);
}
}
// Publisher
@Autowired
private ApplicationEventPublisher eventPublisher;
public void publishEvent() {
eventPublisher.publishEvent(new CustomEvent(this));
}
// Listener
@Component
public class CustomEventListener implements ApplicationListener<CustomEvent> {
@Override
public void onApplicationEvent(CustomEvent event) {
// Handle the event
}
}
이벤트 중심 프로그래밍에서는 이벤트 리스너와 이벤트 퍼블리셔를 정의합니다.
이벤트가 발생하면 퍼블리셔는 등록된 리스너에 이벤트를 전송하고, 리스너는 이벤트에 대한 응답으로 특정 코드 블록을 실행합니다.
Reactive
반면에 반응형 프로그래밍은 데이터 스트림의 사용과 변화의 전파를 강조하는 선언적 접근 방식입니다.
// Data Stream (Flux)
Flux<Integer> dataStream = Flux.just(1, 2, 3, 4, 5);
// Transformation (map, filter)
Flux<Integer> transformedStream = dataStream
.map(number -> number * 2)
.filter(number -> number % 2 == 0);
// Consumption (subscribe)
transformedStream.subscribe(
number -> System.out.println("Received: " + number),
error -> System.err.println("Error: " + error),
() -> System.out.println("Completed")
);
리액티브 프로그래밍에서는 데이터 스트림을 사용하여 애플리케이션의 동작을 모델링합니다.
이벤트 리스너와 퍼블리셔를 정의하는 대신 데이터 스트림을 생성, 변환 및 소비합니다.
Spring WebFlux에서 사용되는 반응형 라이브러리인 Project Reactor는 데이터 스트림을 표현하기 위해 Flux 및 Mono 유형을 제공합니다.
다양한 연산자를 사용하여 이러한 스트림을 선언적으로 조작할 수 있습니다.
이벤트 중심 프로그래밍에서 선언적으로 이벤트를 사용하는 것에서 반응형 프로그래밍에서 선언적으로 스트림을 사용하는 것으로 전환하려면 사고방식의 전환이 필요합니다.
특정 이벤트와 그 핸들러에 집중하는 대신 데이터 스트림과 변환의 관점에서 생각해야 합니다.
즉 가장 중요한 point는 Data의 흐름에 집중해야 한다는 것입니다.
Reactive Programming의 장점
비동기(Asynchronous)
반응형 프로그래밍은 애플리케이션이 이전 작업이 완료될 때까지 기다리지 않고 동시에 작업을 수행할 수 있는 비동기 작업의 사용을 권장합니다.
이렇게 하면 I/O 작업이나 외부 서비스 호출이 완료되기를 기다리는 동안 다른 작업을 처리할 수 있는 스레드를 확보할 수 있으므로 리소스 활용도가 향상되고 전반적인 시스템 성능이 향상됩니다.
non-blocking
반응형 시스템에서 컴포넌트는 non-blocking으로 설계되어 리소스를 차단하거나 리소스를 사용할 수 있게 될 때까지 기다리지 않습니다.
대신 콜백을 등록하거나 이벤트를 구독하여 리소스를 사용할 준비가 되면 알림을 받습니다.
이 접근 방식은 I/O 작업을 기다리는 동안 스레드를 유휴 상태로 둘 필요가 없으므로 리소스를 확보하고 스레드 고갈을 방지할 수 있습니다.
Scalable
반응형 시스템은 다양한 부하 조건에서 잘 확장되도록 설계되었습니다.
리액티브 프로그래밍의 non-blocking 및 이벤트 중심 특성 덕분에 리소스를 효율적으로 관리할 수 있어 애플리케이션이 최소한의 리소스로 많은 수의 동시 연결 또는 요청을 처리할 수 있습니다.
또한 반응형 시스템은 멀티코어 프로세서와 최신 하드웨어를 최대한 활용하여 사용 가능한 코어에 작업을 효율적으로 분산할 수 있습니다.
Responsive
반응형 애플리케이션은 반응형으로 구축되어 부하가 많거나 외부 서비스가 느리거나 불안정한 상황에서도 낮은 지연 시간을 유지하고 원활한 user experienc를 제공할 수 있습니다.
비동기 및 non-blocking 연산을 사용함으로써 반응형 시스템은 느리거나 blocking 되는 연산에 방해받지 않고 사용자 요청이나 입력 변경에 신속하게 응답할 수 있습니다.
Reactive Programming에서의 Data Streams
데이터 흐름의 전파와 Datastream
Data Streams
데이터 스트림은 시간이 지남에 따라 발생하는 event sequence입니다.
마우스 클릭, 키 누름부터 API에서 가져온 데이터까지 모든 것을 나타낼 수 있습니다.
Reactive Programming에서 데이터 스트림은 기본 구성 요소이며, 모든 것을 스트림으로 모델링할 수 있습니다.
Observables and Observers
Observable은 데이터 스트림을 나타내며, Observer는 이러한 observable를 구독하여 스트림에서 emit 되는 이벤트에 반응합니다.
이벤트가 발생하면, observable은 구독한 모든 observers에게 알림을 보내고, 그러면 해당 로직을 실행합니다.
Stream API 와의 차이점
Sync blocking이며 Stream API는 pull model을 사용합니다.
다음 요소를 pull 해와서 Exception, 결과 반환 하는 Return 등의 데이터 처리를 반복합니다.
Reactive Programming은 push 방식을 사용합니다
Asynch non blocking이며 observable은 별도의 스레드에서 실행된 다음 onNext, onError 및 onComplete와 같은 이벤트를 사용하여 결과를 메인 스레드(클라이언트)로 push 합니다.
multithread vs eventloop 관련된 것은 추후에 다시 포스팅하도록 하겠습니다.
Operators
Operators는 스트림의 데이터를 변환하거나 필터링하는 함수입니다.
개발자는 연산자를 사용하여 다양한 방식으로 observables을 조작하고 결합할 수 있습니다.
일반적인 operators는 map, filter, reduce, merge 등이 존재합니다.
Subscription and Unsubscription
Subscriptions은 observers가 observables이 내보내는 이벤트를 수신할 수 있는 메커니즘입니다.
observers가 더 이상 이벤트를 수신할 필요가 없는 경우, observable의 구독을 취소하여 리소스를 효과적으로 정리할 수 있습니다.
Reactive Streams Specification
Reactive Streams is a standard for asynchronous data processing in a streaming fashion with non-blocking back pressure.
Reactive Streams은 non-blocking backpressure를 활용한 비동기 데이터 스트림 처리를 위한 표준입니다.
반응형 시스템에서 서로 다른 컴포넌트 간의 통신을 가능하게 하는 일련의 인터페이스를 정의합니다.
History
Reactive Streams는 2013년에 Netflix와 Pivotal, Lightbend의 엔지니어들이 처음 개발하기 시작했습니다. Netflix는 RxJava, Pivotal은 WebFlux, 그리고 Lightbend는 분산 처리 액터(actor) 모델을 구현한 Akka를 만든 회사인데요. 모두 스트림 API가 꼭 필요한 회사였습니다. 그런데 스트림은 서로 유기적으로 엮여서 흘러야 의미가 있습니다. 데이터가 지속적으로 흐르기 위해서는 서로 다른 회사가 공통의 스펙을 설정하고 구현해야 합니다. 그래서 표준화가 필요했습니다.
Reactive Streams에선 2015년 4월에 JVM에서 사용하기 위한 1.0.0 스펙을 릴리스했습니다. 그리고 2017년 9월에, Reactive Streams의 API와 스펙, 풀(pull) 방식 사용 원칙을 그대로 포팅해서 Flow API라는 이름으로 java.util.concurrent 패키지 아래 포함시킨 Java 9이 릴리스 되었습니다. 이는 커뮤니티와 일부 기업에서 주도해 개발했던 Reactive Streams가 Java의 공식 기능이 되었다는 것을 의미합니다. 이어서 3달 뒤, Reactive Streams에서 Flow와 상호 변환이 가능한 어댑터를 릴리스하면서, 기존에 만들어진 라이브러리를 사용할 수 있게 되었습니다.
- line 엔지니어링 Armeria로 Reactive Streams와 놀자! (1)에서 발췌-
내부는 간단한 api의 조합으로 이루어져 있습니다.
public interface Publisher<T> {
public void subscribe(Subscriber<? super T> s);
}
public interface Subscriber<T> {
public void onSubscribe(Subscription s);
public void onNext(T t);
public void onError(Throwable t);
public void onComplete();
}
public interface Subscription {
public void request(long n);
public void cancel();
}
Publisher
Publisher는 subscribers에게 이벤트를 전송하는 데이터 소스입니다.
데이터 스트림을 관리하고 필요한 경우 백프레셔를 적용하는 역할을 합니다.
Subscriber
Subscriber는 publisher가 내보내는 이벤트를 수신하는 컴포넌트입니다.
데이터를 처리하고, 변환을 적용하고, 다른 Subscriber에게 새로운 이벤트를 내보낼 수도 있습니다.
Subscription
Subscription은 publisher와 subscriber 간의 연결을 나타냅니다.
Subscription은 데이터를 요청하는 메서드와 subscription을 canceling 하는 메서드를 제공합니다.
Processor
Processor는 subscriber 역할과 publisher 역할을 모두 수행하는 구성 요소입니다.
하나 이상의 publisher로부터 데이터를 소비하고 처리한 후 그 결과를 하나 이상의 subscribers에게 내보냅니다.
Backpressure
Backpressure는 subscribers가 이벤트 처리 용량을 알릴 수 있는 방법을 제공하여 publisher가 그에 따라 이벤트 emit 속도를 조정할 수 있도록 합니다.
이를 통해 데이터 과부하를 방지하고 시스템에서 원활하고 효율적인 데이터 흐름을 보장합니다.
Spring WebFlux가 WebProgram과 관련된 Reactive Stream을 활하기에 관한 포스팅을 진행하기 이전에 ReactivePrograming과 Reactive Stream에 대해서 간략하게 알아보았습니다.
다음 포스팅은 Spring WebFlux에 기반이 되는 Reactor lib에 대해서 포스팅하도록 하겠습니다.
참조
What is Reactive Programming? | Baeldung on Computer Science
Reactive programming - Wikipedia
https://ui.dev/imperative-vs-declarative-programming
https://engineering.linecorp.com/ko/blog/reactive-streams-with-armeria-1/
댓글