리액티브 프로그래밍이란 무엇인가
리액티브 프로그래밍은 데이터 스트림에 영향을 미치는 모든 변경사항을 관련된 모든 당사자(최종사용자, 컴포넌트, 하위 구성요소, 연결되어있는 프로그램)들에게 전파하는 프로그램을 리액티브 프로그램이라고 한다.
예) 엑셀 시트
- A1셀에는 숫자 B1셀에는 =ISEVEN(A1) 함수를 입력
- A1셀 짝수 입력 B1셀 TRUE가 자동으로 표시
- 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)
- 응답성 : 시스템은 즉각 응답해야 한다.
- 탄력성 : 시스템에 장애가 발생하더라도 응답성을 유지해야 한다.
- 복제, 봉쇄, 격리, 위임에 의해서 이루어진다.
- 장애는 내부로 억제되 각 컴포넌트들을 서로 격리시키는데, 그래서 하나의 컴포넌트에 장애가 발생하더라도 전체 시스템에 영향을 끼치지 못하게 된다.
- 유연성 : 작업량이 변하더라도 그 변화에 대응하고 응답성을 유지해야 한다.
- 메시지기반 : 탄력성의 원칙을 지키려면 리액티브 시스템은 비동기적인 메시지 전달에 의존해 컴포넌트들 간의 경계를 형성해야 한다.
리액티브 스트림 표준사양
링크 : http://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")
}
}
}
}
'책 > 코틀린 리액티브 프로그래밍' 카테고리의 다른 글
6. 연산자 및 오류처리 (0) | 2020.05.28 |
---|---|
5. 비동기 데이터 연산자와 변환 (0) | 2020.05.27 |
4. 백프레셔와 플로어블 소개 (0) | 2020.05.26 |
3. 옵저버블과 옵저버 구독자 (0) | 2020.05.26 |
2. 코틀린과 RxKotlin을 사용한 함수형 프로그래밍 (0) | 2020.05.25 |