본문 바로가기

책/코틀린 리액티브 프로그래밍

1. 리액티브 프로그래밍의 소개

리액티브 프로그래밍이란 무엇인가

리액티브 프로그래밍은 데이터 스트림에 영향을 미치는 모든 변경사항을 관련된 모든 당사자(최종사용자, 컴포넌트, 하위 구성요소, 연결되어있는 프로그램)들에게 전파하는 프로그램을 리액티브 프로그램이라고 한다.

예) 엑셀 시트

  1. A1셀에는 숫자 B1셀에는 =ISEVEN(A1) 함수를 입력
  2. A1셀 짝수 입력 B1셀 TRUE가 자동으로 표시
  3. A1셀 홀수 입력 B1셀 FALSE가 자동으로 표시

A1셀의 입력값에 따라 B1셀이 자동으로 변경되는데 이와 같은 작용을 리액티브라고 한다.

 

숫자가 짝수인지 홀수인지를 판단하는 코틀린 코드이다.

fun main(args: Array<String>){
    var number = 4
    var isEven = isEven(number)
    println("The number is" + (if (isEven) "Even" else "Odd"))
    number = 9
    println("The number is" + (if (isEven) "Even" else "Odd"))
}

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

프로그램을 출력을 확인하면 number 에 새로운 값이 할당됐음에도 isEven이 여전히 참이라는 것을 알수 있다. 그러나 isEven이 number 변수의 변경 사항을 추적하도록 설정됐다면 자동으로 false가 됐을 것이다.

함수형 리액티브 프로그래밍을 적용해야 하는 이유

  • 콜백지옥의 제거
    • 미리 정의된 이벤트가 발생할 때 호출되는 메서드를 콜백이라고 하는데, 이 메커니즘에는 인터페이스와 그 구현등 많은 코드가 필요
  • 오류 처리를 위한 표준 메커니즘
    • 복잡한 작업과 HTTP를 사용해 작업하는동안 발생하는 오류의 처리에 필요
  • 간결해진 스레드 사용
  • 간단한 비동기 연산
  • 전체를 위한 하나, 모든 작업에 대해 동일한 API
  • 함수형 접근
  • 유지보수 가능하고 테스트 가능한 코드

리액티브 선언

리액티브 선언 : 네가지 리액티브 원리를 정의해 놓은 문서(http://www.reactivemanifesto.org)

 

The Reactive Manifesto

Responsive: The system responds in a timely manner if at all possible. Responsiveness is the cornerstone of usability and utility, but more than that, responsiveness means that problems may be detected quickly and dealt with effectively. Responsive systems

www.reactivemanifesto.org

  • 응답성 : 시스템은 즉각 응답해야 한다.
  • 탄력성 : 시스템에 장애가 발생하더라도 응답성을 유지해야 한다.
    • 복제, 봉쇄, 격리, 위임에 의해서 이루어진다.
    • 장애는 내부로 억제되 각 컴포넌트들을 서로 격리시키는데, 그래서 하나의 컴포넌트에 장애가 발생하더라도 전체 시스템에 영향을 끼치지 못하게 된다.
  • 유연성 : 작업량이 변하더라도 그 변화에 대응하고 응답성을 유지해야 한다.
  • 메시지기반 : 탄력성의 원칙을 지키려면 리액티브 시스템은 비동기적인 메시지 전달에 의존해 컴포넌트들 간의 경계를 형성해야 한다.

리액티브 스트림 표준사양

링크 : http://www.reactive-streams.org

 

http://www.reactive-streams.org

Reactive Streams Reactive Streams is an initiative to provide a standard for asynchronous stream processing with non-blocking back pressure. This encompasses efforts aimed at runtime environments (JVM and JavaScript) as well as network protocols. JDK9 java

www.reactive-streams.org

코틀린을 위한 리액티브 프레임워크

코틀린에 리액티브 프로그래밍을 하기 위해서는 다음과 같은 라이브러리가 필요한다.

  • RxKotlin : 함수 컴포지션을 선호 동시에 전역 상태와 함수의 사이드 이펙트를 방지
  • Reactor-Kotlin : 함수형 프로그래밍을 기반으로 하며 스프링 프로그래밍에 널리 지원
  • Redux-Kotlin
  • FunKTionale
  • RxJava 외 자바 리액티브 프레임워크

RxKotlin 시작하기

RxKotlin 다운로드와 설정

깃 허브 빌드

$ git clone https://github.com/ReactiveX/RxKotlin.git
$ cd RxKotlin/
$ ./gradlew build

그레이들 의존성 추가

compile 'io.reacivex.rxjava2:rxkotlin:2.x.y'

메이븐 의존성 추가

<dependency>
    <groupId>io.reactivex.rxjava2</groupId>
    <artifactId>rxkotlin</artifactId>
    <version>2.x.y</version>
</dependency>

RxJava의 푸시 메커니즘과 풀 메커니즘 비교

반복자(Iterator) 패턴의 풀 메커니즘

fun main(args: Array<String>) {
    var list:List<Any> = listOf("One", 2, "Three", "Four", 4.5, "Five", 6.0f)  //1
    var iterator = list.iterator() // 2
    while( iterator.hasNext() ) {  // 3
        print(iterator.next())     // 4 각 엘리먼트를 출력한다.
    }
}

 

옵져버블(observable)패턴의 푸시 메커니즘

  • observable을 구독했기 때문에
    • 모든 변경 사항은 onNext로 푸시
    • 모든 데이터가 푸시됬을 때는 onComplete 호출
    • 에러가 발생했을때는 onError가 호출
fun main(args: Array<String>) {
    var list:List<Any> = listOf("One", 2, "Three", "Four", 4.5, "Five", 6.0f)  //1
    var observable: Observable<Any> = list.toObservable() // 2
    
    observable.subscibeBy(    // 람다로 된 구독자로 이름 있는 인자를 사용한다.
        onNext = { println(it) },
        onError = { it.printStackTrace() },
        onComplete = { println("Done!") }
    )
}

ReactiveEvenOdd 프로그램

위의 풀방법의 프로그램을 변경해보자

  • subject에서 숫자를 통지
  • map 내의 메서드를 호출 차례대로 map의 반환값과 함께 subscribe 내의 함수가 호출
  • subscribe의 메서드에서는 map의 리턴값을 입력받아 각각 even 과 odd를 출력한다.
  • subject.onNext 메서드는 새로운 값을 subject로 전달해 처리할 수 있다.
fun main(args: Array<String>) {
    var subject:Subject<Int> = PublishSubject.create()
    subject.map({ isEven(it) })
           .subscribe({ println("The number is ${(if (it) "Even" else "Odd")}") })
    
    subject.onNext(4)
    subject.onNext(9)
}

ReactiveCalculator 프로젝트

fun main(args: Array<String>) {
    println("Initail Out put with a=15, b=10")
    var calculator: ReactiveCalculator = ReactiveCalculator(15, 10)
    println("Enter a = <number> or b = <number> in sparate lines\nexit to exit the program")
    
    var line: String?
    do {
        line = readLine()
        calculator.handleInput(line)
    } while( line != null && !line.toLowerCase().contains("exit"))
}

 

ReactiveCalculator 클래스

class ReactiveCalculator(a:Int, b:Int) {
    internal val subjectAdd: Subject<Pair<Int, Int>> = PublishSubject.create()
    internal val subjectSub: Subject<Pair<Int, Int>> = PublishSubject.create()
    internal val subjectMult: Subject<Pair<Int, Int>> = PublishSubject.create()
    internal val subjectDiv: Subject<Pair<Int, Int>> = PublishSubject.create()
    
    internal val subjectCalc: Subject<ReactiveCalculator> = PublishSubject.create()
    
    internal var nums: Pair<Int, Int> = Pair(0, 0)
    
    init {
        nums = Pair(a, b)
        
        subjectAdd.map({ it.first + it.second }).subscribe({ println("Add = $it") })
        subjectSub.map({ it.first - it.second }).subscribe({ println("Substract = $it") })
        subjectMult.map({ it.first * it.second }).subscribe({ println("Multiply = $it") })
        subjectDiv.map({ it.first / (it.second*1.0) }).subscribe({ println("Divide = $it") })
        
        subjectCalc.subscribe({
            with(it) {
                calculateAddition()
                calculateSubstraction()
                calculateMultiplication()
                calculateDivision()
            }
        })
        subjectCalc.onNext(this)
    }
    fun calculateAddition() {
        subjectAdd.onNext(nums)
    }
    fun calculateSubstraction() {
        subjectSub.onNext(nums)
    }
    fun calculateMultiplication() {
        subjectMult.onNext(nums)
    }
    fun calculateDivision() {
        subjectDiv.onNext(nums)
    }
    
    fun modifyNumbers(a: Int = nums.first, b: Int = nums.second) {
        nums = Pair(a, b)
        subjectCalc.onNext(this)
    }
    
    fun handleInput(inputLine: String?) {
        if( !inputLine.equals("exit") ) {
            val pattern: Pattern = Pattern.compile("([a|b])(?:\\s)?=(?:\\s)?(\\d*)")
            
            var a: Int? = null
            var b: Int? = null
            
            val matcher: Matcher = pattern.matcher(inputLine)
            
            if( matcher.matches() && matcher.group(1) != null && matcher.group(2) != null ) {
                if( matcher.group(1).toLowerCase().equals("a") ) {
                    a = matcher.group(2).toInt()
                } else if (  matcher.group(1).toLowerCase().equals("b") ) {
                    a = matcher.group(2).toInt()
                }
            }
            
            when {
                a != null && b != null -> modifyNumbers(a, b)
                a != null -> modifyNumbers(a = a)
                b != null -> modifyNumbers(b = b)
                else -> println("Invalid Input")
            }
        }
    }
    
    
}