봄날은 갔다. 이제 그 정신으로 공부하자

1. Reactive 프로그래밍이란? 본문

Reactive

1. Reactive 프로그래밍이란?

길재의 그 정신으로 공부하자 2021. 6. 1. 12:27

Reactive에 대한 글을 써보려 합니다. ^^;;

병렬 처리 라이브러리 중 하나인 Coroutine에 대한 글은 이전에 작성했었는데 Reactive에 대한 글은 작성한 적이 없더라구요.

Reactive에 대한 글을 정리하려는 이유는 정리 안해놓으면 까먹을 것 같아서 입니다.

 

Reactive는 정리할 내용이 많아 차근차근 시리즈로 게시할 예정입니다.

 

리액티브 프로그래밍이란?

위키피디아에는 아래와 같이 정리하고 있습니다.

리액티브 프로그래밍은 데이터 스트림과 변경 사항 전파를 중심으로하는 비동기 프로그래밍 패러다임이다. 이것은 프로그래밍 언어로 정적 또는 동적인 데이터 흐름을 쉽게 표현할 수 있어야하며, 데이터 흐름을 통해 하부 실행 모델이 자동으로 변화를 전파할 수 있는 것을 의미한다.

 

데이터와 데이터 스트림에 영향을 미치는 모든 변경 사항을 관련된 모든 당사자들에게 전파하는 모든 프로그램을 리액티브 프로그램이라고 할 수 있습니다. 리액티브 프로그래밍을 하면 다음과 같은 이점을 얻을 수 있습니다.

  - 간결해진 Thread 사용

  - 간단한 비동기 연산

  - 콜백 지옥의 제거

 

리액티브 프로그래밍에 입문하는 개발자를 위해 리액트브 프로그래밍의 정의에 대해 선언한 선언문이 있는데 이 선언문에 리액티브란 무엇이고 어떤 원리가 있는지 잘 설명되어 있습니다.

리액티브 선언이 정의하는 네가지 원칙의 요지는 아래와 같습니다.

 

Responsive (응답성)

시스템은 즉각 응답해야 합니다. 응답성 있는 시스템은 신속하고 일관성 있는 응답 시간을 유지해 일관된 서비스 품질을 제공합니다.

 

Resilient (탄력성)

시스템에 장애가 발생하더라도 응답성을 유지해야 합니다. 탄력성은 복제(replication), 봉쇄(containment), 격리(isolation), 위임(delegation)에 의해서 이루어집니다.

장애는 각 컴포넌트 내부로 억제돼 각 컴포넌트들을 서로 격리시키는데, 그래서 하나의 컴포넌트에 장애가 발생하더라도 전체 시스템에 영향을 끼치지 못하게 됩니다.

 

Elastic (유연성)

리액티브 시스템은 작업량이 변하더라도 그 변화에 대응하고 응답성을 유지해야 합니다. 리액티브 시스템은 상용 하드웨어 및 소프트웨어 플랫폼에서 효율적인 비용으로 유연성을 확보합니다.

 

Message driven (메시지 기반)

탄력성의 원칙을 지키려면 리액티브 시스템은 비동기적인 메시지 전달에 의전해 컴포넌트들 간의 경계를 형성해야 합니다.

 

Reactive 프로그래밍의 주요 구성 요소

Reactive 프로그래밍의 주요 구성 요소는 아래와 같이 세 가지로 구분됩니다.

   - Observable

   - Observers

   - Schedulers

 

Observable

Observable은 데이터 스트림으로, Observable은 하나의 스레드에서 다른 스레드로 전달 할 데이터를 압축합니다. 주기적 또는 설정에 따라 생애 주기동안 데이터를 방출합니다. Observable은 데이터를 처리하고 다른 구성요소에 전달하는 역할을 수행합니다.

 

Observers

Observers는 Observable에 의해 방출된 데이터 스트림을 받아서 처리합니다. Observers는 subscribe() 메서드를 사용해서 observable을 구독하고 observable이 방출하는 데이터를 수신할 수 있습니다. observable이 데이터를 방출할 때 마다 등록된 모든 observer는 onNext() 콜백으로 데이터를 수신하고 데이터 방출이 완료되면 onComplete() 에러가 발생하면 onError() 콜백으로 수신합니다.

 

Schedulers

Reactive 프로그래밍은 비동기 프로그래밍을 위한 것으로, 개발자는 스레드를 관리해야할 필요가 있습니다. Schedulers는 Observable과 Observers 에게 그들이 실행되어야 할 스레드를 알려주는 구성요소 입니다. observeOn() 메서드로 observers에게 관찰해야 할 스레드를 알려줄 수 있고, scheduleOn() 메서드로 observable이 실행해야 할 스레드를 알려줄 수 있습니다.

 

Reactive 프로그래밍 시작하기

이 글에 나오는 샘플 코드는 모두 RxKotlin 라이브러리를 사용하도록 하였습니다. RxKotlin은 코틀린을 위한 리액티브 프로그래밍의 특정 구현으로 함수형 프로그래밍의 영향을 받았습니다.

RxKotlin은 함수 컴포지션을 선호하며 동시에 전역 상태와 함수의 사이드 이펙트를 방지하며, 프로듀서와 컨슈머 구조의 옵저버 패턴에 의존하며 결합, 스케쥴링, 스토틀링, 변형, 에러 처리와 라이프 사이클 관리등을 가능하게 해주는 다양한 연산자를 가지고 있습니다.

 

Step 1.

우선 프로젝트의 build.gradle 파일에 아래와 같이 종속성을 선언해 줍니다.

implementation "io.reactivex.rxjava3:rxkotlin:3.0.0"

 

Step 2.

데이터를 방출할 Observable 객체를 생성합니다.

아래 예제 코드에서는 리스트 이터레이터를 옵저버블로 생성했습니다.

var list: List<Any> = listOf("One", “Two”, "Three", "Four", "Five")
var observable: Observable<String> = list.toObservable()

Step 3. 

Observable에서 방출하는 데이터를 수신할 Observer 객체를 생성합니다.

val observer: Observer<String> = object: Observer<String>{
    override fun onComplete(){
        println("All Complete")
    }

    override fun onNext(item: Any){
        println("Next $item")
    }

    override fun onError(e: Throwable){
        println("Error Occured $e")
    }

    override fun onSubscribe(e: Disposable){
        println("New Subscription”)
    }
}

 

Step 4. 

마지막으로 옵저버블에 옵저버를 연결하면서 동시성을 관리하는 스케줄러를 정의합니다.

subscribeOn() 메소드를 사용해 옵저버블은 백그라운드 스레드에서 실행되도록 처리하였으며, 

observeOn() 메소드를 사용해 옵저버의 콜백은 MainThread에서 실행되도록 처리하였습니다.

observable
        .subscribeOn(Schedulers.computation())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(observer)

 

이제 Reactive 프로그래밍의 기초에 대해 알아보았으니 조금 더 심화된 내용을 살펴보도록 하겠습니다.

 

함수형 프로그래밍 소개

함수형 프로그램은 작고 재사용 가능한 선언적인 함수의 사용을 권장하는 선언적인 프로그래밍 패러다임으로 Reactive 프로그래밍은 함수형 프로그래밍의 지원을 받습니다. 함수형 프로그래밍이란 언어의 인터페이스와 지원을 필요로 합니다. 따라서 언어에서 특정 종류의 지원을 제공하지 않는다면 그것은 함수형이라고 할 수 없습니다.

 

함수형 리액티브 프로그래밍(Functional Reactive Programming)의 개념은 실제로 리액티브 프로그래밍과 함수형 프로그래밍을 혼합한 개념으로  함수형 프로그래밍의 주요 목적은 쉽게 모듈화가 가능한 프로그램을 구현하는 것 입니다. 

모듈화된 프로그램은 반응형 프로그래밍을 구현하거나 필요에 따라 리액티브 선언문의 네가지 원칙(Responsive, Resilient, Elastic, Message driven)을 구현하는데 필요합니다.

 

함수형 프로그래밍은 람다, 순수함수, 고차함수, 함수유형, 인라인 함수 같은 몇가지 새로운 개념으로 구성됩니다.

 

Lamda Expression (람다식)

람다 또는 람다식은 일반적으로 이름이 없는 익명함수를 의미합니다. 람다식은 함수라고 말할 수 있지만 모든 함수가 람다식인 것은 아닙니다. Kotlin은 람다 표현을 잘 지원하는데 람다를 구현하는 것은 매우 쉽고 자연스럽습니다.

아래 코드는 kotlin에서 람다식이 작동하는 예시 입니다.

fun funExam(){
    val sum = {x: Int, y: Int -> x + y }
    println("Sum ${sum(12, 14)}")

    val randomMulti = {x: Int -> (Random().nextInt(15)+1) * x}
    println("Random output ${randomMulti(2)}")
}

 

위 코드의 sum과 randomMulti가 람다식입니다.

sum은 두개의 정수를 입력받아 더한 값을 반환하는 람다식이고

randomMulti는 한개의 정수를 입력 받아 난수에 곱한 수를 반환하는 람다식입니다.

 

Pure Function (순수 함수)

함수의 반환값이 인수/매개 변수에 전적으로 의존하면 이 함수를 순수함수라고 합니다.

앞의 예제에서 첫 번째 람다식 sum은 순수 함수였지만 두번째 randomMulti의 경우 반환값이 동일한 값이 전달된 경우에도 달라질 수 있으므로 순수 함수가 아닙니다.

 

Higher-Order Function (고차 함수)

고차 함수는 함수를 인자로 전달받거나 함수를 결과로 반환하는 함수를 말합니다.

고차 함수는 인자로 받은 함수를 필요한 시점에 호출하거나 클로저를 생성하여 반환합니다.

스크립트의 함수는 First-class citizen(일급 객체)이므로 값처럼 인자로 전달할 수 있으면 반환할 수도 있습니다.

아래 코드는 일급 함수 사용 예시입니다.

fun funHigherOrder(a: Int, funValidityCheck: (a:Int)-> Boolean){
    if(funValidityCheck(a)){
        println("a $a is Valid")
    }else{
        println("a $a is Invalid")
    }
}

fun isEven(n: Int): Boolean = ((n%2) == 0)

fun funExam(){
    funHigherOrder(12, {a: Int -> isEven(a)})
    funHigherOrder(19, {a: Int -> isEven(a)})
}

 

위 예시에서 funHigherOrder() 함수를 선언했는데 정수와 funValidityCheck() 함수를 인자로 받아들입니다.

funHigherOrder 함수 내에서 funValidityCheck 함수를 호출해 값이 유효한지 여부를 검증합니다.

그러나 funExam 함수 내에서 funHigherOrder 함수를 호출하는 시점에 런타임으로 funValidityCheck 함수를 정의하고 있습니다.

 

Inline Function (인라인 함수)

함수는 이식 가능한 코드를 작성하는 좋은 방법 이지만 함수의 스택 유지 관리 및 오버헤드로 인해 프로그램 실행 시간이 늘어나고 메모리 최적화를 저하시킬 수 있습니다. 이러한 문제를 해결하기 위해 만들어진 것이 인라인 함수입니다.

인라인 함수는 프로그램의 성능 및 메모리 최적화를 향상시키는 개선된 기능입니다.

따라서 함수 호출 스택 유지 보수를 위해 많은 메모리를 필요로 하지 않으며 동시에 함수의 장점도 얻을 수 있습니다.

인라인 함수 사용 예시는 아래와 같습니다.

inline fun doSomeStuff(a: Int = 0) = a+(a*a)

fun funExam(){
    for(i in 1..10){
        println("i Output ${doSomeStuff(i)}")
    }
}

 

Maybe

Maybe class는 RxJava2부터 도입된 Observable의 또다른 특수 형태로 아이템을 방출할 수도 있고 방출을 하지 않을 수도 있습니다.

방출할 아이템이 있는 경우엔 onSuccess 이벤트가 호출되고, 방출할 아이템이 없는 경우에는 onComplete 이벤트가 호출됩니다.

 

fun funExam(){
    // 1
    val maybeValue: Maybe<Int> = Maybe.just(1)
    maybeValue.subscribeBy(
            onComplete = {println("Completed Empty")},
            onError = {println("Error $it")},
            onSuccess = {println("Completed value $it")}
    )
    
    // 2
    val maybeEmpty:Maybe<Int> = Maybe.empty()
    maybeEmpty.subscribeBy(
            onComplete = {println("Completed Empty")},
            onError = {println("Error $it")},
            onSuccess = {println("Completed with value $it")}
    )
}

 

위 에제 코드에서  첫번째 주석의 maybeValue는 방출할 아이템이 있으므로 onSuccess 이벤트가 호출 되어 “Completed value 1”을 출력하지만, maybeEmpty는 방출할 아이템이 없으므로 onComplete 메서드가 호출 되어 Completed Empty가 출력됩니다.

 

Maybe class는 Monad로 Monad는 객체지향 언어의 제너릭 유사한데 제너릭은 값만 추상화하는데 비해 Monad는 연산까지 추상화한 것이라고 설명할 수 있습니다.

Monad는 하나의 타입이며, 인자를 타입으로 받아(Java와 C++의 제너릭) 값을 캡슐화하여 값을 가공할 수 있는 추가기능(일반적으로 map이라 부릅니다.) 오퍼레이터를 사용할 수 있는 Functor를 사용하여 최종적으로 이러한 프로세스를 구현하는 구조를 새롭게 생성하는 특징을 가집니다. 좀 더 쉽게 설명하자면, 프로그래밍적으로 아래 세가지를 충족하면 모나드라고 부를 수 있습니다.

   - 타입을 인자로 받는 타입이다.

   - unit(return) operator가 있어야 한다.

   - bind operator가 있어야 한다.

 

monad에 대한 자세한 설명은 아래 두 개의 링크 참조 부탁 드리겠습니다.

https://teamdable.github.io/techblog/Moand-and-Functional-Architecture

https://overcurried.com/3%EB%B6%84%20%EB%AA%A8%EB%82%98%EB%93%9C/

 

정리

이번 글의 중요내용을 정리하면 다음과 같습니다.

 

Reactive란 무엇인가? 

   최근 핫한 프로그래밍 패러다임으로 데이터 스트림과 변경 사항 전파를 중심으로하는 비동기 프로그래밍 패러다임입니다.

 

Reactive 선언 4가지

   - Responsive (응답성)

   - Resilient (탄력성)

   - Elastic (유연성)

   - Message driven (메시지 기반)

 

Reactive 프로그래밍의 주요 구성 요소

   - Observable

   - Observers

   - Schedulers

 

RxJava2에 추가된 새로운 class인 Maybe에 대한 간략한 소개

 

다음 글에서는 이번 글에서 언급한 “Reactive 프로그래밍의 주요 구성 요소인 Observable 대해 자세히 설명하도록 하겠습니다.

'Reactive' 카테고리의 다른 글

6. Reactive - 변환 연산자  (0) 2021.06.14
5. Reactive - 연산자 구분  (0) 2021.06.12
4. Reactive - BackPressure & Flowable  (0) 2021.06.10
3. Reactive - Hot & Cold Observable, Subject  (0) 2021.06.07
2. Reactive - Observable  (0) 2021.06.03
Comments