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

 1) 명령형 프로그램

  • 예문
public class ImperativeProgrammingExample {
    public static void main(String[] args){
        // List에 있는 숫자들 중에서 4보다 큰 짝수의 합계 구하기
        List<Integer> numbers = List.of(1, 3, 6, 7, 8, 11);
        int sum = 0;
        for(int number : numbers){
            if(number > 4 && (number % 2 == 0)){
                sum += number;
            }
        }
        System.out.println(sum);
    }
}
  • List에 포함된 숫자들을 for문을 이용해서 순차적으로 접근한다
  • if 문으로 특정 조건에 맞는 숫자들만 sum 변수에 더해서 합계를 구한다
  • 코드가 어떤식으로 실행되어야 하는지에 대한 구체적인 로직들이 코드 안에 그대로 나타난다

 2) 선언형 프로그램

  • 예문
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);
    }
}
  • Java에서 선언형 프로그래밍 방식을 이해하기 위한 가장 적절한 예는 바로 Java 8부터 지원하는 Stream API이다
  • List에 포함된 숫자들을 처리하는 것은 명령형 코드와 동일하지만 처리 방식은 전혀 다르다
  • Java Stream API를 사용하기 때문에 코드 상에 보이지 않는 내부 반복자가 명령형의 for문을 대체하고 있다
  • filter() 메서드(Operation)가 if 문을 대신해서 조건에 만족하는 숫자를 필터링하고 있다
  • Stream API를 사용하는 것이 선언형 프로그래밍 방식인 이유는
    - 명령형 프로그래밍 방식으로 작성된 코드는 위에서 아래로 순차적으로 실행한다
     : for문을 만나게 되면 for문 내부로 진입하게 된다
     : if문을 만나면 if 문의 조건을 판단한 후에 조건에 맞으면 sum 변수에 숫자를 더한 뒤에 다시 for 문을 반복한다
    - Stream API를 사용한 선언형 코드는 numbers.stream().filter().mpaToInt().sum()과 같은 메서드 체인이 순차적으로 실행이 되지 않는다
     : filter()나 mapToInt() 메서드에 파라미터를 전달하기 때문에 즉시 호출되는 것처럼 생각될 수 있지만 그렇지 않다
     : Stream의 경우 최종 연산을 수행하는 메서드를 호출하지 않으면 앞에서 작성한 메서드 체인들이 실행 되지 않는다
     : Stream의 메서드 체인(중간 연산)에는 작업을 해 달라고 선언(요청)하는 람다 표현식만 넘겨준다
     : 최종 연산이 호출될 때 전달 받은 람다 표현식을 기반으로 동작을 수행한다
  • 선언형 프로그래밍 방식은 개발자가 로직을 모두 작성하지 않는다
  • 필요한 동작들을 람다 표현식으로 정의(선언)하고 구체적인 동작 수행은 Operation(연산) 메서드 체인에 위임한다

 

2. 리액티브 프로그래밍의 구조

 1) 예제 1

package com.codestates.example;

import reactor.core.publisher.Mono;

public class HelloReactiveExample01 {
    public static void main(String[] args) {
        Mono<String> mono = Mono.just("Hello, Reactive");
        mono.subscribe(message -> System.out.println(message));
    }
}
  • Publisher는 데이터를 emit하는 역할을 하고 Subsciber는 Publisher가 emit한 데이터를 전달 받아서 소비하는 역할을 한다
    - Puhlisher의 역할을 하는 것이 Mono 이다
    - Subscriber의 역할을 하는 것이
     : subscribe() 메서드 내부에 정의된 람다 표현식인 message ->System.out.println(message) 이다
  • Java의 Stream에서 메서드 체인 형태로 사용할 수 있듯이 리액티브 프로그래밍에서도  메서드 체인을 구성할 수 있다

 2) 예제 2

package com.codestates.example;

import reactor.core.publisher.Mono;

public class HelloReactiveExample02 {
    public static void main(String[] args) {
        Mono
                .just("Hello, Reactive")
                .subscribe(message -> System.out.println(message));
    }
}
  • 예제1의 코드를 하나의 메서드 체인 형태로 표현한 코드이다
  • Java의 Stream API와 리액티브 프로그래밍이 선언형 프로그래밍 방식으로 구성되는 공통점을 가지고 있지만 차이점이 더 많이 존재한다

 3) 예제 3

package com.codestates.example;

import org.springframework.http.converter.json.GsonBuilderUtils;
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);
    }
}

 

 3-1) 리액트 프로그램밍 주요 용어

  • Publisher
    - 데이터를 내보내는 주체를 의미한다
    - 예제3에서는 Flux가 Publisher이다

 

  • Emit 
    - Publisher가 데이터를 내보내는 것을 Emit 이라고 한다

 

  • Subscriber
    - Publisher가 emit한 데이터를 전달 받아서 소비하는 주체를 의미한다
    - 예제3에서는 subscribe(System.out::println) 중에서 System.out::println이 Subscriber에 해당된다
    - 람다 표현식을 메서드 레퍼런스로 축약하지 않았다면 람다 표현식 자체가 Subscriber에 해당된다

 

  • Subscribe
    - 구독을 의미한다
    - subscribe를 사용하면 시스템은 자동으로 subscriber를 생성한다
    - 예제3에서 subscribe() 메서드를 호출하면 subscriber를 생성하여 Publisher가 보내 온 데이터를 출력하게 된다

 

  • Signal
    - Publisher가 발생시키는 이벤트를 의미한다
    - 예제3에서 subscribe() 메서드가 호출되면 Publisher인 Flux는 숫자 데이터를 하나씩 하나씩 emit 한다
    - 숫자 데이터를 하나씩 emit하는 자체를 리액티브 프로그래밍에서는 이벤트가 발생하는 것으로 간주한다
    - 이벤트 발생을 다른 컴포넌트에게 전달하는 것을 Signal을 전송한다라고 표현한다

 

  • Operator
    - 리액티브 프로그래밍에서 어떤 동작을 수행하는 메서드를 의미한다
    - 예제3에서 fromIterable(), filter(), reduce() 등의 메서드 하나 하나를 Operator라고 한다
    - 연산자라고도 한다

 

  • Sequence
    - Operator 체인으로 표현되는 데이터의 흐름을 의미한다
    - 예제3에서 Operator 체인으로 작성된 코드 자체를 하나의 Sequence라고 볼 수 있다

 

  • Upstream / Downstream
    - Sequence 상의 특정 Operator를 기준으로 위쪽의 Sequence 일부를 Upstream이라고 한다
    - 아래 쪽 Sequence 일부를 Downstream이라고 한다
    - 예제3에서 filter() Operaotr를 기준에서 보면 filter() Operator 위 쪽의 fromIterable()은 Upstream 이다
    - filter() Operator 아래 쪽의 reduce() Operator는 Downstream 이다

 

▶ declarative programming : https://www.techtarget.com/searchitoperations/definition/declarative-programming 

 

What Is Declarative Programming? Definition from SearchITOperations

Declarative programming focuses on the outcome, not the journey. Learn how it works and how it compares to imperative programming through examples.

www.techtarget.com

 Declarative programming : https://en.wikipedia.org/wiki/Declarative_programming 

 

Declarative programming - Wikipedia

Programming paradigm based on modeling the logic of a computation In computer science, declarative programming is a programming paradigm—a style of building the structure and elements of computer programs—that expresses the logic of a computation witho

en.wikipedia.org

 

1. 리액티브 프로그래밍(Reactive Programming)

 1) 사전 준비

 2) 리액티브

  • 일반적으로 ‘반응을 하는’, ‘반응을 보이는’이란 의미이다
  • 리액티브(Reactive)에 대한 빠른 이해를 위한 단어로 리액션(Reaction)이 있다

 

 3) 리액티브 시스템(Reactive System)

  • 반응을 잘하는 시스템을 의미한다
  • 리액티브 시스템을 이용하는 클라이언트의 요청에 반응을 잘하는 시스템이다
  • 리액티브 시스템 관점에서의 반응은 쓰레드의 Non-Blocking과 관련이 있다
    - 리액티브 시스템은 클라이언트의 요청에 대한 응답 대기 시간을 최소화 할 수 있도록 쓰레드가 차단되지 않게 한다
    - Non-Blocking을 통해 클라이언트에게 즉각적으로 반응하도록 구성된 시스템이다
  • 리액티브 선언문에 나와 있는 리액티브 시스템 설계원칙의 그림을 통해 리액티브 시스템의 특징을 확인해 봅시다.
    - MEANS
      : 리액티브 시스템에서 사용하는 커뮤니케이션 수단이다
      : Message Driven
        → 리액티브 시스템에서는 메시지 기반 통신을 통해 여러 시스템 간에 느슨한 결합을 유지한다
    - FORM
     : 메시지 기반 통신을 통해 리액티브 시스템이 어떤 특성을 가지는 구조로 형성되는지를 의미한다
     : Elastic
        → 시스템으로 들어오는 요청량이 크기에 상관없이 일정한 응답성을 유지한다
     : Resillient
        → 시스템의 일부분에 장애가 발생하더라도 응답성을 유지한다
    - VALUE
     : 리액티브 시스템의 핵심 가치가 무엇인지를 표현하는 영역이다
     : Responsive
        → 리액티브 시스템은 클라이언트의 요청에 즉각적으로 응답할 수 있어야 한다
     : Maintainable
        → 클라이언트의 요청에 대한 즉각적인 응답이 지속가능해야 한다
     : Extensible
        → 클라이언트의 요청에 대한 처리량을 자동으로 확장하고 축소할 수 있어야 한다

 4) 리액티브 프로그래밍(Reactive Programming)

  • 리액티브 시스템에서 사용되는 프로그래밍 모델이다
  • 리액티브 시스템에서의 메시지 기반 통신(Message Driven)은 Non-Blocking 통신과 유기적인 관계를 맺고 있다
  • 리액티브 프로그래밍은 Non-Blocking 통신을 위한 프로그래밍 모델이다

 

3. 리액티브 프로그래밍의 특징

  • 선언형 프로그래밍 방식을 사용하는 대표적인 프로그래밍 모델이다
  • 지속적으로 데이터가 입력으로 들어올 수 있고 데이터에 어떤 변경이 발생하는 이벤트가 발생할 때 마다 데이터를 계속해서 전달한다
  • 지속적으로 발생하는 데이터를 하나의 데이터 플로우로 보고 데이터를 자동으로 전달합니다.
  • 리액티브 스트림즈(Reactive Streams)
    - JPA와 JDBC API의 공통점은
     : JPA(Java Persistence API)는 Java 진영에서 사용하는 ORM(Object-Relational Mapping) 기술의 표준 사양(또는 명세, Specification)이다
     : JDBC는 Java 애플리케이션에서 데이터베이스에 액세스하기 위한 표준 사양(또는 명세, Specification)이다
    - 리액티브 스트림즈(Reactive Streams)는 리액티브 프로그래밍을 위한 표준 사양(또는 명세, Specification)이다

 

4. 리액티브 스트림즈 컴포넌트(구성요소)

 1) Publisher 인터페이스

public interface Publisher<T> {
    public void subscribe(Subscriber<? super T> s);
}
  • 데이터 소스로 부터 데이터를 내보내는(emit) 역할을 한다
  • subscribe() 추상 메서드를 포함하고 있다
  • subscribe()의 파라미터로 전달되는 Subscriber가 Publisher로부터 내보내진 데이터를 소비하는 역할을 한다
  • subscribe() 는 Publisher가 내보내는 데이터를 수신할 지 여부를 결정하는 구독의 의미를 가지고 있다
  • 일반적으로 subscribe()가 호출되지 않으면 Publisher가 데이터를 내보내는 프로세스는 시작되지 않는다

 

 2) Subscriber 인터페이스

public interface Subscriber<T> {
    public void onSubscribe(Subscription s);
    public void onNext(T t);
    public void onError(Throwable t);
    public void onComplete();
}
  • Publisher로부터 내보내진 데이터를 소비하는 역할을 한다
  • Subscriber는 네 개의 추상 메서드를 포함하고 있다
    - onSubscribe(Subscription s)
     : 구독이 시작되는 시점에 호출된다
     : onSubscribe() 내에서 Publisher에게 요청할 데이터의 개수를 지정하거나 구독 해지 처리를 할 수 있다
    - onNext(T t)
     : Publisher가 데이터를 emit할 때 호출된다
     : emit된 데이터를 전달 받아서 소비할 수 있다
    - onError(Throwable t)
     : Publisher로부터 emit된 데이터가 Subscriber에게 전달되는 과정에서 에러가 발생할 경우에 호출된다
    - onComplete()
     : Publisher가 데이터를 emit하는 과정이 종료될 경우 호출된다
     : 데이터의 emit이 정상적으로 완료 된 후, 처리해야 될 작업이 있다면 onComplete() 내에서 수행할 수 있다

 

 3) Subscription 인터페이스

public interface Subscription {
    public void request(long n);
    public void cancel();
}
  • Subscriber의 구독 자체를 표현한 인터페이스이다
    - request(long n)
     : Publihser가 emit하는 데이터의 개수를 요청한다
    - cancel()
     : 구독을 해지하는 역할을 한다
     : 구독 해지가 발생하면 Publisher는 더이상 데이터를 emit하지 않는다

 

 4) Processor 인터페이스

public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}
  • Subscriber 인터페이스와 Publisher 인터페이스를 상속하고 있다
    - Publisher와 Subscriber의 역할을 동시에 할 수 있는 특징을 가진다
    - 별도로 구현해야 되는 추상 메서드는 없다

 

5. 리액티브 스트림즈 구현체

 1) Project Reactor(Reactor)

  • 리액티브 스트림즈를 구현한 대표적인 구현체이다
  • Spring과 가장 잘 맞는 리액티브 스트림즈 구현체이다
  • Reactor는 Spring 5의 리액티브 스택에 포함되어 있ek
  • Sprig Reactive Application 구현에 있어 핵심적인 역할을 담당한다

 2) RxJava

  • .NET 기반의 리액티브 라이브러리를 넷플릭스에서 Java 언어로 포팅한 JVM 기반의 리액티브 확장 라이브러리이다
  • RxJava의 경우 2.0부터 리액티브 스트림즈 표준 사양을 준수하고 있다
  • 이 전 버전의 컴포넌트와 함께 혼용되어 사용이 되고 있다

 3) Java Flow API

  • Java 역시 Java 9부터 리액티브 스트림즈를 지원한다
  • Flow API는 리액티브 스트림즈를 구현한 구현체가 아니라 리액티브 스트림즈 표준 사양을 Java 안에 포함을 시킨 구조라고 볼 수 있다
  • 다양한 벤더들이 JDBC API를 구현한 드라이버를 제공할 수 있도록 SPI(Service Provider Interface) 역할을 하는것 처럼 Flow API 역시 리액티브 스트림즈 사양을 구현한 여러 구현체들에 대한 SPI 역할을 한다

 4) 기타 리액티브 확장(Reactive Extension)

  • RxJava의 Rx는 Reactive Extension의 줄임말이다
  • 이 의미는 특정 언어에서 리액티브 스트림즈를 구현한 별도의 구현체가 존재한다는 의미이다
  • 실제로 다양한 프로그래밍 언어에서 리액티브 스트림즈를 구현한 리액티브 확장(Reactive Extension) 라이브러리를 제공하고 있다
  • 대표적인 리액티브 확장(Reactive Extension) 라이브러리로 앞에서 언급한 RxJava가 있다
  • 이외에도 RxJS, RxAndroid, RxKotlin, RxPython, RxScala 등이 있다

 

The Reactive Manifesto : https://www.reactivemanifesto.org/glossary 

 

Glossary - The Reactive Manifesto

Glossary Asynchronous The Oxford Dictionary defines asynchronous as “not existing or occurring at the same time”. In the context of this manifesto we mean that the processing of a request occurs at an arbitrary point in time, sometime after it has been

www.reactivemanifesto.org

 Reactive Streams : https://github.com/reactive-streams/reactive-streams-jvm 

 

GitHub - reactive-streams/reactive-streams-jvm: Reactive Streams Specification for the JVM

Reactive Streams Specification for the JVM. Contribute to reactive-streams/reactive-streams-jvm development by creating an account on GitHub.

github.com

▶ ReactiveX : https://reactivex.io/ 

 

ReactiveX

CROSS-PLATFORM Available for idiomatic Java, Scala, C#, C++, Clojure, JavaScript, Python, Groovy, JRuby, and others

reactivex.io

 

'Spring WebFlux' 카테고리의 다른 글

Spring WebFlux - 리액티브 프로그래밍 기본구조  (0) 2022.08.10

+ Recent posts