AKKA 프레임워크를 사용한 비헤이버기반 액터모델을 코틀린이 지원하는 순수 액터모델로 변환을 시도 코프링을 이용한다면 로컬전용 이벤트 드리븐방식의 처리기를 만들때 액터모델이 필요할때 채택할수 있습니다.
AKKA를 이용하는 다양한 언어에서 액터모델사용은 다음을 참고
- 02.Actor - JAVA버전
- ReceiveActor - NET버전
- HelloActor with AKKA - 코틀린버전
액터모델을 학습하기전 기본언어가 지원하는 동시성 프로그래밍 방식을 먼저 알아두는것은 중요합니다.
액터모델은 기본 언어가 지원하는 동시성 프로그래밍과 병렬프로그래밍을 함께 활용하기때문에
언어가 지원하는 기본기를 먼저 선행하는것은 중요합니다.
- Terminology - 병렬처리 VS 동시성처리
- Future and Promise - 비동기 동시성 처리
의존
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
- core만 있으면 되며, reactor이용시 포함됨으로 둘중 하나만 사용
- reactor를 활용한 reactive 프로그래밍이 지원됨을 의미
- Open AI가 채택해 유명해진 분산처리 프레임워크인 Ray, Core영역이 왜 액터모델을 채택하고 있는지 주시할 필요가 있습니다.
- 코틀린에서 채택한 코르틴 코어도 액터모델을 기본으로 포함하고 있으며 액터모델을 중심으로 Reactor(리액티브 스트림의 구현체) 로 확장해나가는 기술을 주시할 필요가 있습니다.
구현
package com.example.kotlinbootlabs.kactor import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.launch sealed class HelloKActorCommand data class Hello(val message: String, val replyTo: kotlinx.coroutines.CompletableDeferred<HelloKActorResponse>) : HelloKActorCommand() data class GetHelloCount(val replyTo: kotlinx.coroutines.CompletableDeferred<HelloKActorResponse>) : HelloKActorCommand() sealed class HelloKActorResponse data class HelloResponse(val message: String) : HelloKActorResponse() data class HelloCountResponse(val count: Int) : HelloKActorResponse() class HelloKActor { private val channel = Channel<HelloKActorCommand>() private var helloCount = 0 private val scope = CoroutineScope(Dispatchers.Default) init { scope.launch { for (command in channel) { when (command) { is Hello -> handleHello(command) is GetHelloCount -> handleGetHelloCount(command) } } } } private fun handleHello(command: Hello) { if (command.message == "Hello") { helloCount++ command.replyTo.complete(HelloResponse("Kotlin")) } else if (command.message == "InvalidMessage") { throw RuntimeException("Invalid message received!") } } private fun handleGetHelloCount(command: GetHelloCount) { command.replyTo.complete(HelloCountResponse(helloCount)) } suspend fun send(command: HelloKActorCommand) { channel.send(command) } fun stop() { scope.cancel() } }
- AKKA 의 액터와 구분하기위해 KActor(코액터) 로 네이밍 ( K-컨텐츠아님)
- 송수신 Typed를 사용하고 비헤이버 패턴 채택
테스트
package com.example.kotlinbootlabs.kactor import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test class HelloKActorTest { companion object { private const val TIMEOUT_DURATION = 3000L // Timeout duration in milliseconds } private val actor = HelloKActor() @AfterEach fun tearDown() { actor.stop() } @Test fun testHelloCommand() = runBlocking { val response = CompletableDeferred<HelloKActorResponse>() actor.send(Hello("Hello", response)) val result = withTimeout(TIMEOUT_DURATION) { response.await() } as HelloResponse //The response message should be 'Kotlin' assertEquals("Kotlin", result.message, "Kotlin") } @Test fun testGetHelloCountCommand() = runBlocking { val response1 = CompletableDeferred<HelloKActorResponse>() actor.send(Hello("Hello", response1)) withTimeout(TIMEOUT_DURATION) { response1.await() } val response2 = CompletableDeferred<HelloKActorResponse>() actor.send(GetHelloCount(response2)) val result = withTimeout(TIMEOUT_DURATION) { response2.await() } as HelloCountResponse assertEquals(1, result.count, "The hello count should be 1") } @Test fun testInvalidMessage() { val response = CompletableDeferred<HelloKActorResponse>() try { runBlocking { withTimeout(TIMEOUT_DURATION) { actor.send(Hello("InvalidMessage", response)) } } } catch (e: RuntimeException) { assertEquals("Invalid message received!", e.message, "The exception message should be 'Invalid message received!'") } } }
- Akka test tooli kit 을 이용하는 경우 테스트 중단없는 관찰자(수신검증) 기능을 제공하며, 액터의 수신검증은 기본적으로 위와같은 코드로 가능합니다.
- 코틀린모드에서도 관련 테스트툴 준비중..