액터모델은 TopLevel  아키텍처로 계층형구조로 동적으로 생성할수 있으며  부모가 자식의 생성과 유지를 책임지게 하는 관리감독(SuperVisor) 전략을 이용할수도 있습니다.

액터가 생성되고 나면 부모액터는 자식액터를 알수 있기때문에 상대경로로 접근할수 있으며~ 계층형주소 체계를 가지기 때문에 api path처럼 주소체계로 접근할수도 있습니다.


액터를 계층적으로 구성하고 관리감독하는 SuperVisor액터 생성을 시도하보겠습니다.

LLM TIP : 코드생성을 이용할때 표준이되는 기본 모델의  코드를 어시스트로 활용해 분석을 먼저 의뢰하면, 기존 코드 컨셉을 활용해 생성을 하기때문에 코드생성의 범위를 벗어나지 않게됩니다.

코틀린으로 작성된 다음 액터모델을 분석해 

코파일럿을 이용하는 경우 IDE에서 참조 아닌경우 코드를 그대로 이용

코드 : https://github.com/psmon/java-labs/blob/master/KotlinBootLabs/src/main/kotlin/actor/HelloActor.kt



헬로우 액터를 동적으로 생성하고 관리감독할수 있는 부모액터 생성을 시도해 보겠습니다.

HelloActor 를 관리감독하는 SuperVisor 부모액터를 만들고 자식 액터를 동적으로 만들고 싶습니다.


SuperVisorActor 

package actor.supervisor

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

import actor.Hello
import actor.HelloActor
import actor.HelloActorCommand
import actor.HelloActorResponse
import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.Terminated

/** SupervisorActor가 처리할 수 있는 명령들 */
sealed class SupervisorCommand

data class CreateChild(val name: String) : SupervisorCommand()
data class SendHello(
    val childName: String,
    val message: String,
    val replyTo: ActorRef<HelloActorResponse>
) : SupervisorCommand()
data class GetChildCount(val replyTo: ActorRef<Int>) : SupervisorCommand()
data class TerminateChild(val name: String) : SupervisorCommand()

class SupervisorActor private constructor(
    context: ActorContext<SupervisorCommand>
) : AbstractBehavior<SupervisorCommand>(context) {

    companion object {
        fun create(): Behavior<SupervisorCommand> {
            return Behaviors.setup { context -> SupervisorActor(context) }
        }
    }

    private val children = mutableMapOf<String, ActorRef<HelloActorCommand>>()

    override fun createReceive(): Receive<SupervisorCommand> {
        return newReceiveBuilder()
            .onMessage(CreateChild::class.java, this::onCreateChild)
            .onMessage(SendHello::class.java, this::onSendHello)
            .onMessage(GetChildCount::class.java, this::onGetChildCount)
            .onMessage(TerminateChild::class.java, this::onTerminateChild)
            .onSignal(Terminated::class.java, this::onChildTerminated)
            .build()
    }

    private fun onCreateChild(command: CreateChild): Behavior<SupervisorCommand> {
        val childName = command.name
        val childActor = context.spawn(
            Behaviors.supervise(HelloActor.create())
                .onFailure(SupervisorStrategy.restart()),
            childName
        )
        context.watch(childActor)

        children[childName] = childActor
        context.log.info("Created child actor with name: $childName")
        return this
    }

    private fun onSendHello(command: SendHello): Behavior<SupervisorCommand> {
        val child = children[command.childName]
        if (child != null) {
            child.tell(Hello(command.message, command.replyTo))
        } else {
            context.log.warn("Child actor [${command.childName}] does not exist.")
        }
        return this
    }

    private fun onGetChildCount(command: GetChildCount): Behavior<SupervisorCommand> {
        command.replyTo.tell(children.size)
        return this
    }

    private fun onChildTerminated(terminated: Terminated): Behavior<SupervisorCommand> {
        val childActor = terminated.ref
        val childName = childActor.path().name()
        children.remove(childName)
        context.log.info("Child actor terminated: $childName")
        return this
    }

    private fun onTerminateChild(command: TerminateChild): Behavior<SupervisorCommand> {
        val child = children[command.name]
        if (child != null) {
            context.stop(child)
            context.log.info("Terminated child actor: ${command.name}")
        } else {
            context.log.warn("Attempted to terminate non-existent child actor: ${command.name}")
        }
        return this
    }

}


여기서 추가되는 핵심 개념은 다음과 같습니다.



SupervisorActor 를 테스트하는 코드도 생성


SuperVisorActorTest

package actor.supervisor

import actor.HelloActorResponse
import actor.HelloResponse
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 SupervisorActorTest {

    companion object {
        private lateinit var testKit: ActorTestKit

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

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

    @Test
    fun testSupervisorCreatesChildAndResponds() {
        val responseProbe = testKit.createTestProbe<HelloActorResponse>()
        val supervisor = testKit.spawn(SupervisorActor.create())

        // 자식 액터 생성 요청
        supervisor.tell(CreateChild("child1"))

        // 자식 액터에게 메시지 전송 요청
        supervisor.tell(SendHello("child1", "Hello", responseProbe.ref()))

        // 응답 확인
        responseProbe.expectMessage(HelloResponse("Kotlin"))

        // 자식 액터 수 확인
        val countProbe = testKit.createTestProbe<Int>()
        supervisor.tell(GetChildCount(countProbe.ref()))
        countProbe.expectMessage(1)
    }

    @Test
    fun testSupervisorHandlesUnknownChild() {
        val responseProbe = testKit.createTestProbe<HelloActorResponse>()
        val supervisor = testKit.spawn(SupervisorActor.create())

        // 존재하지 않는 자식 액터에게 메시지 전송
        supervisor.tell(SendHello("unknownChild", "Hello", responseProbe.ref()))

        // 응답이 없음을 확인
        responseProbe.expectNoMessage()

        // 자식 액터 수는 0이어야 함
        val countProbe = testKit.createTestProbe<Int>()
        supervisor.tell(GetChildCount(countProbe.ref()))
        countProbe.expectMessage(0)
    }

    @Test
    fun testSupervisorRestartsChildOnFailure() {
        val responseProbe = testKit.createTestProbe<HelloActorResponse>()
        val supervisor = testKit.spawn(SupervisorActor.create())

        // 자식 액터 생성 및 의도적인 예외 발생
        supervisor.tell(CreateChild("child2"))

        // 자식 액터에게 잘못된 메시지 전송하여 예외 유발
        supervisor.tell(SendHello("child2", "InvalidMessage", responseProbe.ref()))

        // 응답이 없음을 확인 (자식 액터가 예외로 인해 재시작됨)
        responseProbe.expectNoMessage()

        // 올바른 메시지 재전송
        supervisor.tell(SendHello("child2", "Hello", responseProbe.ref()))

        // 응답 확인 (자식 액터가 재시작되어 정상 동작함)
        responseProbe.expectMessage(HelloResponse("Kotlin"))
    }

    @Test
    fun testSupervisorDetectsChildTermination() {

        val supervisor = testKit.spawn(SupervisorActor.create())

        // 자식 액터 생성
        supervisor.tell(CreateChild("child4"))

        // 자식 액터 수 확인 (1이어야 함)
        val countProbe = testKit.createTestProbe<Int>()
        supervisor.tell(GetChildCount(countProbe.ref()))
        countProbe.expectMessage(1)

        // 자식 액터 종료 요청
        supervisor.tell(TerminateChild("child4"))

        // 잠시 대기하여 종료 처리를 기다림
        Thread.sleep(500)

        // 자식 액터 수 확인 (0이어야 함)
        supervisor.tell(GetChildCount(countProbe.ref()))
        countProbe.expectMessage(0)
    }

}



유닛테스트 결과

23:05:28.555 [SupervisorActorTest-akka.actor.default-dispatcher-5] INFO  actor.supervisor.SupervisorActor - Created child actor with name: child4
23:05:28.557 [SupervisorActorTest-akka.actor.default-dispatcher-3] INFO  actor.supervisor.SupervisorActor - Terminated child actor: child4
23:05:28.562 [SupervisorActorTest-akka.actor.default-dispatcher-3] INFO  actor.supervisor.SupervisorActor - Child actor terminated: child4
23:05:29.086 [SupervisorActorTest-akka.actor.default-dispatcher-3] INFO  actor.supervisor.SupervisorActor - Created child actor with name: child1
23:05:29.092 [SupervisorActorTest-akka.actor.default-dispatcher-5] INFO  actor.HelloActor - Received valid Hello message. Count incremented to 1
23:05:29.098 [SupervisorActorTest-akka.actor.default-dispatcher-3] WARN  actor.supervisor.SupervisorActor - Child actor [unknownChild] does not exist.
23:05:29.214 [SupervisorActorTest-akka.actor.default-dispatcher-5] INFO  actor.supervisor.SupervisorActor - Created child actor with name: child2
23:05:29.220 [SupervisorActorTest-akka.actor.default-dispatcher-5] ERROR akka.actor.typed.Behavior$ - Supervisor RestartSupervisor saw failure: Invalid message received!
java.lang.RuntimeException: Invalid message received!
23:05:29.319 [SupervisorActorTest-akka.actor.default-dispatcher-3] INFO  akka.actor.typed.Behavior$ - Received valid Hello message. Count incremented to 1
23:05:29.332 [SupervisorActorTest-akka.actor.default-dispatcher-5] INFO  akka.actor.CoordinatedShutdown - Running CoordinatedShutdown with reason [ActorSystemTerminateReason]
> Task :test


액터의 최상위 아키텍처와 Supervisor의 특징 요약

액터 모델의 최상위 아키텍처

액터 모델은 병렬 프로그래밍과 분산 시스템에서 상태와 행위를 캡슐화하여 안전하고 효율적인 동시성을 제공하는 프로그래밍 모델입니다. Akka 프레임워크는 이러한 액터 모델을 구현한 대표적인 라이브러리로, 다음과 같은 최상위 아키텍처를 가지고 있습니다.

1. 액터 시스템 (Actor System)

2. 액터 계층 구조

3. 액터의 통신 방식

Supervisor의 특징

Supervisor는 액터 시스템에서 자식 액터의 오류를 관리하고 시스템의 안정성을 유지하기 위한 핵심적인 역할을 합니다.

1. 오류 관리 (Error Handling)

2. 자식 액터의 라이프사이클 관리

3. 계층적 구조와 책임 분리

4. 동적 액터 관리

결론

액터 모델의 최상위 아키텍처는 액터 시스템, 액터 계층 구조, 그리고 비동기 메시지 전달로 구성되어 있습니다. 이 구조는 동시성과 병렬성을 효율적으로 처리하기 위한 기반을 제공합니다.

Supervisor는 액터 시스템에서 핵심적인 역할을 수행하며, 자식 액터의 오류 처리, 라이프사이클 관리, 그리고 시스템의 안정성을 유지하는 데 중요한 요소입니다. 적절한 Supervisor 전략을 통해 시스템은 예외 상황에서도 안정적으로 동작할 수 있으며, 동적인 자식 액터 관리로 유연성과 확장성을 확보할 수 있습니다.



next : 라우터를 활용한 분산처리 전략