Actor를 이용하는 방법에는 크게 Untyped Actor와 Typed Actor가 있으며 Typed Actor의 경우 Behavior 패턴을 이용, 처리할 이벤트를 Type정의해 조금더 메시지처리를 엄격하게 관리할수 있습니다.
비교적 최신 스펙인 Typed Actor를 이용 코틀린에서 이용할수 있는 간단한 액터 모델을 생성하고 UnitTest해보겠습니다.
Behavior라는 개념을 채택하였기 때문에 Behaviror의 간단한 설명부터 알아보겠습니다.
Behavior를 정의할 수 있어 코드의 가독성 및 유지보수성이 향상됩니다.![Akka > [Kotlin] HelloActor > image-2024-9-30_19-4-28.png](/download/attachments/93945916/image-2024-9-30_19-4-28.png?version=1&modificationDate=1727690667957&api=v2)
/** HelloActor 처리할 수 있는 명령들 */ sealed class HelloActorCommand data class Hello(val message: String, val replyTo: ActorRef<Any>) : HelloActorCommand() /** HelloActor 반환할 수 있는 응답들 */ sealed class HelloActorResponse data class HelloResponse(val message: String) : HelloActorResponse() |
import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.javadsl.AbstractBehavior
import akka.actor.typed.javadsl.ActorContext
import akka.actor.typed.javadsl.Behaviors
import akka.actor.typed.javadsl.Receive
/** HelloActor 클래스 */
class HelloActor private constructor(
private val context: ActorContext<HelloActorCommand>,
) : AbstractBehavior<HelloActorCommand>(context) {
companion object {
fun create(): Behavior<HelloActorCommand> {
return Behaviors.setup { context -> HelloActor(context) }
}
}
override fun createReceive(): Receive<HelloActorCommand> {
return newReceiveBuilder()
.onMessage(Hello::class.java, this::onHello)
.build()
}
private fun onHello(command: Hello): Behavior<HelloActorCommand> {
if (command.message == "Hello") {
command.replyTo.tell(HelloResponse("Kotlin"))
}
return this
}
} |
import akka.actor.testkit.typed.javadsl.ActorTestKit
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
class HelloActorTest {
companion object {
private lateinit var testKit: ActorTestKit
@BeforeAll
@JvmStatic
fun setup() {
testKit = ActorTestKit.create()
}
@AfterAll
@JvmStatic
fun teardown() {
testKit.shutdownTestKit()
}
}
@Test
fun testHelloActorRespondsWithKotlin() {
val probe = testKit.createTestProbe<Any>()
val helloActor = testKit.spawn(HelloActor.create())
helloActor.tell(Hello("Hello", probe.ref()))
probe.expectMessage(HelloResponse("Kotlin"))
}
} |
TypedActor의 경우 UntypedActor와 비교 코드 리팩토리를 할수 있는 장점도 있습니다. TypeCheck를 하지 않는 액터(Untyped)경우 실행타임에 처리할수 없는 메시지를 알수 있지만 TypedActor의 경우 코드수정 단계에서 알수 있으며 OOP의 특성을 적극 활용할수 있습니다. |
기본 모델을 작성후 Copilot를 활용해 액터모델을 업데이트 할수 있습니다.
Hello를 응답할때마다 카운트를 증가시키고~ 몇번 응답을 했는지 알려주는 Command도 생성 |
/** 이벤트 정의 */
data class GetHelloCount(val replyTo: ActorRef<Any>) : HelloActorCommand()
data class HelloCountResponse(val count: Int) : HelloActorResponse()
/** HelloActor 클래스 */
class HelloActor private constructor(
private val context: ActorContext<HelloActorCommand>,
) : AbstractBehavior<HelloActorCommand>(context) {
override fun createReceive(): Receive<HelloActorCommand> {
return newReceiveBuilder()
.onMessage(Hello::class.java, this::onHello)
.onMessage(GetHelloCount::class.java, this::onGetHelloCount)
.build()
} private var helloCount: Int = 0
private fun onHello(command: Hello): Behavior<HelloActorCommand> {
if (command.message == "Hello") {
helloCount++
command.replyTo.tell(HelloResponse("Kotlin"))
}
return this
}
private fun onGetHelloCount(command: GetHelloCount): Behavior<HelloActorCommand> {
command.replyTo.tell(HelloCountResponse(helloCount))
return this
}
/** 추가되는 유닛테스트 코드 */
helloActor.tell(GetHelloCount(probe.ref()))
probe.expectMessage(HelloCountResponse(1)) |
Copilot를 활용해 코틀린에서 유용한 액터모델을 생성하는 실험을 이어가보겠습니다.