앞장에의 HelloActor를 개선해 항상 인사만 하는 액터가 아닌 나의 상태에따라 행위가 달라지는 액터를 만들어보겠습니다.

상태관리가 조금더 복잡해지는 경우 FSM(유한상태머신)패턴을 적용할수도 있지만 여기서는 비교적 두가지의 상태만 가지고 전환하는 액터모델을 만들어보겠습니다.




다음과 같이 감정에 따라 다르게 작동하는 액터모델을 생성시도

코드생성 프롬프트

이 액터(HelloActor)를 업그레이드해 HelloStateActor을 만들고 싶습니다. 나는 항상 인사를 하는 액터가 아니며, 화가나면 말걸지말라고 하고 기분좋을때만 Kotlin이라고 응답을 합니다.  상태전환에 따라 응답이 다른 액터로 업그레이드해주세요


HelloStateActor

/** HelloStateActor 처리할 수 있는 명령들 */
sealed class HelloStateActorCommand
data class Hello(val message: String, val replyTo: ActorRef<Any>) : HelloStateActorCommand()
data class GetHelloCount(val replyTo: ActorRef<Any>) : HelloStateActorCommand()
data class ChangeState(val newState: State) : HelloStateActorCommand()

/** HelloStateActor 반환할 수 있는 응답들 */
sealed class HelloStateActorResponse
data class HelloResponse(val message: String) : HelloStateActorResponse()
data class HelloCountResponse(val count: Int) : HelloStateActorResponse()

/** 상태 정의 */
enum class State {
    HAPPY, ANGRY
}

/** HelloStateActor 클래스 */
class HelloStateActor private constructor(
    private val context: ActorContext<HelloStateActorCommand>,
    private var state: State
) : AbstractBehavior<HelloStateActorCommand>(context) {

    companion object {
        fun create(initialState: State): Behavior<HelloStateActorCommand> {
            return Behaviors.setup { context -> HelloStateActor(context, initialState) }
        }
    }

    override fun createReceive(): Receive<HelloStateActorCommand> {
        return newReceiveBuilder()
            .onMessage(Hello::class.java, this::onHello)
            .onMessage(GetHelloCount::class.java, this::onGetHelloCount)
            .onMessage(ChangeState::class.java, this::onChangeState)
            .build()
    }

    private var helloCount: Int = 0

    private fun onHello(command: Hello): Behavior<HelloStateActorCommand> {
        when (state) {
            State.HAPPY -> {
                if (command.message == "Hello") {
                    helloCount++
                    command.replyTo.tell(HelloResponse("Kotlin"))
                }
            }
            State.ANGRY -> {
                command.replyTo.tell(HelloResponse("Don't talk to me!"))
            }
        }
        return this
    }

    private fun onGetHelloCount(command: GetHelloCount): Behavior<HelloStateActorCommand> {
        command.replyTo.tell(HelloCountResponse(helloCount))
        return this
    }

    private fun onChangeState(command: ChangeState): Behavior<HelloStateActorCommand> {
        state = command.newState
        return this
    }
}

HelloStateActor는 내부 상태에 따라 다르게 응답하는 Akka 액터입니다. 이 액터는 두 가지 상태를 가집니다: HAPPY와 ANGRY. 각 상태에 따라 "Hello" 메시지에 대한 응답이 달라집니다.  


액터는 세 가지 명령을 처리할 수 있습니다:  

  • Hello: 메시지와 응답할 액터의 참조를 포함합니다.
  • GetHelloCount: "Hello" 메시지를 받은 횟수를 요청합니다.\
  • ChangeState: 액터의 내부 상태를 변경합니다.


두 가지 응답을 보낼 수 있습니다:  

  • HelloResponse: 메시지를 포함합니다.
  • HelloCountResponse: "Hello" 메시지를 받은 횟수를 포함합니다.


상태

  • 액터는 두 가지 상태를 가집니다:  
  • HAPPY: "Hello" 메시지를 받으면 "Kotlin"으로 응답합니다.
  • ANGRY: "Hello" 메시지를 받으면 "Don't talk to me!"로 응답합니다.


액터 구현
HelloStateActor 클래스는 AbstractBehavior를 확장하며, createReceive 메서드를 재정의하여 다양한 명령을 처리하는 방법을 정의합니다.


주요 메서드:

  • onHello: Hello 명령을 처리합니다. 현재 상태에 따라 응답하며, 상태가 HAPPY일 경우 helloCount를 증가시킵니다.
  • onGetHelloCount: GetHelloCount 명령을 처리합니다. 현재 helloCount를 응답합니다.
  • onChangeState: ChangeState 명령을 처리합니다. 내부 상태를 업데이트합니다.


HelloStateActorTest

package actor.hellostate

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 HelloStateActorTest {

    companion object {
        private lateinit var testKit: ActorTestKit

        @BeforeAll
        @JvmStatic
        fun setup() {
            testKit = ActorTestKit.create()
        }

        @AfterAll
        @JvmStatic
        fun teardown() {
            testKit.shutdownTestKit()
        }
    }

    @Test
    fun testHelloStateActorRespondsBasedOnState() {
        val probe = testKit.createTestProbe<Any>()

        val helloStateActor = testKit.spawn(HelloStateActor.create(State.HAPPY))

        // Test in HAPPY state
        helloStateActor.tell(Hello("Hello", probe.ref()))
        probe.expectMessage(HelloResponse("Kotlin"))

        helloStateActor.tell(GetHelloCount(probe.ref()))
        probe.expectMessage(HelloCountResponse(1))

        // Change state to ANGRY
        helloStateActor.tell(ChangeState(State.ANGRY))

        // Test in ANGRY state
        helloStateActor.tell(Hello("Hello", probe.ref()))
        probe.expectMessage(HelloResponse("Don't talk to me!"))

        helloStateActor.tell(GetHelloCount(probe.ref()))
        probe.expectMessage(HelloCountResponse(1)) // Count should not change
    }
}

git : https://github.com/psmon/java-labs/blob/master/KotlinBootLabs/src/test/kotlin/actor/hellostate/HelloStateActorTest.kt


상태 StateFul 관리 프로그래밍에 대해

일반적인 웹개발은 RestFul 기반 상태없는(StateLess) 코드 설계를 하지만 스레드와 같이 긴 주기의 라이프 사이클을 가진

객체설계를 한다고 가정하면 상태를 관리하는 프로그래밍이 필요할수도 있습니다. 


기존 MQ(Kafka/RabbitMQ)가 복잡한 도메인의 상태처리도 지원하는것으로 보일수있지만 다음과같은 차이가 존재하며


StateFull한 개발을 도입하면서 단일지점 병목없는 분산처리를 하는것은 사실 난이도가 있는 주제로 AkkaToolKit은 이러한 과제를 도전할때 도움을 줄수 있는 툴중하나입니다.


next : 다음장에서는 액터모델에 속도 흐름 제어기를 탑재해보겠습니다.


  • No labels