작성중 컨텐츠...
이미 설계되어 잘작동하는 복합구성 액터모델
작성된 액터기능
- 일반 사용자가 접속하면 PersnalRoom이라는 개인룸의 액터를 받게되어 개인전용 실시간 처리 공간이 생성됩니다.
- 상담원을 관리하는 메니저가 있으며 메니저는 개인사용자가 상담을 요청하면 가용 상담원을 찾은후 상담방을 생성합니다.
- 상담방이 생성되면 요청한 개인과 상담원을 연결합니다.
AI에 의해 추가할기능
- 상담방에 옵저버 기능을 만들어 상담원을 초청합니다.
- 옵저버 상담원은 상담진행을 개입을 못하며 관찰할수 있습니다.
- typed actor를 사용하는 경우 이벤트의 상관관계를 InteliJ다이어그램을 통해 파악가능합니다.
- untyped actor인 경우 컴파일타임 안전하지 못한 단점이 있었지만, akka 2.6x부터 typed 액터를 지원합니다.
핵심 포인트
- 소켓세션을 n개 만드는것이 아닌, 사용자는 1개의 소켓세션만 이용 n개의 채널을 이용가능
- n개의 세션 액터로 논리개념을 만들어 기능을 확장
CounselorRoomActor에 상담원 옵저버도 초청해 조인시키고 싶습니다.
옵저버 상담원은 N개로 대화내용을 모두 수신받을수 있습니다.
하지만 고객에게 메시지 전송은 할당된 counselor만 가능합니다.
To invite observer counselors to the `CounselorRoomActor` and allow them to receive all conversation messages, but restrict sending messages to the client to only the assigned counselor, you can modify the `CounselorRoomActor` class as follows: 1. Add a list to store observer counselors. 2. Update the `AssignCounselor` command to handle observer counselors. 3. Modify the message handling methods to send messages to observer counselors. Here is the updated code: ```kotlin sealed class CounselorRoomCommand data class InvitePersonalRoomActor(val personalRoomActor: ActorRef<PersonalRoomCommand>, val replyTo: ActorRef<CounselorRoomResponse>) : CounselorRoomCommand() data class ChangeStatus(val status: CounselorRoomStatus, val replyTo: ActorRef<CounselorRoomResponse>) : CounselorRoomCommand() data class AssignCounselor(val counselorActor: ActorRef<CounselorCommand>, val observers: List<ActorRef<CounselorCommand>> = emptyList()) : CounselorRoomCommand() data class SendMessageToPersonalRoom(val message: String) : CounselorRoomCommand() data class SendToCounselor(val message: String) : CounselorRoomCommand() ``` ```kotlin class CounselorRoomActor private constructor( context: ActorContext<CounselorRoomCommand>, private val name: String ) : AbstractBehavior<CounselorRoomCommand>(context) { private var status: CounselorRoomStatus = CounselorRoomStatus.WAITING private lateinit var personalRoom: ActorRef<PersonalRoomCommand> private lateinit var counselor: ActorRef<CounselorCommand> private val observerCounselors = mutableListOf<ActorRef<CounselorCommand>>() companion object { fun create(name: String): Behavior<CounselorRoomCommand> { return Behaviors.setup { context -> CounselorRoomActor(context, name) } } } override fun createReceive(): Receive<CounselorRoomCommand> { return newReceiveBuilder() .onMessage(InvitePersonalRoomActor::class.java, this::onInvitePersonalRoomActor) .onMessage(ChangeStatus::class.java, this::onChangeStatus) .onMessage(AssignCounselor::class.java, this::onAssignCounselor) .onMessage(SendMessageToPersonalRoom::class.java, this::onSendMessageToPersonalRoom) .onMessage(SendToCounselor::class.java, this::onSendToCounselor) .build() } private fun onSendToCounselor(sendToCounselor: SendToCounselor): Behavior<CounselorRoomCommand>? { if(::counselor.isInitialized){ counselor.tell(SendToCounselorHandlerTextMessage(sendToCounselor.message)) observerCounselors.forEach { it.tell(SendToCounselorHandlerTextMessage(sendToCounselor.message)) } } else { context.log.error("CounselorActor is not initialized") } return this } private fun onSendMessageToPersonalRoom(sendMessageToPersonalRoom: SendMessageToPersonalRoom): Behavior<CounselorRoomCommand> { if(::personalRoom.isInitialized){ personalRoom.tell(SendTextMessage(sendMessageToPersonalRoom.message)) } else { context.log.error("PersonalRoomActor is not initialized") } return this } private fun onAssignCounselor(command: AssignCounselor): Behavior<CounselorRoomCommand> { counselor = command.counselorActor observerCounselors.clear() observerCounselors.addAll(command.observers) counselor.tell(SendToCounselorSystemMessage("You are assigned to the room: $name")) observerCounselors.forEach { it.tell(SendToCounselorSystemMessage("You are observing the room: $name")) } return this } private fun onInvitePersonalRoomActor(command: InvitePersonalRoomActor): Behavior<CounselorRoomCommand> { context.log.info("Invited PersonalRoomActor: ${command.personalRoomActor}") personalRoom = command.personalRoomActor command.replyTo.tell(InvitationCompleted) return this } private fun onChangeStatus(command: ChangeStatus): Behavior<CounselorRoomCommand> { status = command.status context.log.info("CounselorRoom status changed to: $status") command.replyTo.tell(StatusChangeCompleted(status)) return this } } ``` In this updated code, the `AssignCounselor` command now accepts a list of observer counselors. The `onAssignCounselor` method assigns the main counselor and adds the observers to the `observerCounselors` list. The `onSendToCounselor` method sends messages to both the main counselor and the observers.
onAssignCounselor 와 Observer 초대는 구분하고 싶습니다.
To separate the assignment of a counselor and the invitation of observers, you can create a new command for inviting observers. Here is the updated code: 1. Add a new command `InviteObservers` to handle the invitation of observer counselors. 2. Implement the `onInviteObservers` method to process this new command. ```kotlin sealed class CounselorRoomCommand data class InvitePersonalRoomActor(val personalRoomActor: ActorRef<PersonalRoomCommand>, val replyTo: ActorRef<CounselorRoomResponse>) : CounselorRoomCommand() data class ChangeStatus(val status: CounselorRoomStatus, val replyTo: ActorRef<CounselorRoomResponse>) : CounselorRoomCommand() data class AssignCounselor(val counselorActor: ActorRef<CounselorCommand>) : CounselorRoomCommand() data class InviteObservers(val observers: List<ActorRef<CounselorCommand>>) : CounselorRoomCommand() data class SendMessageToPersonalRoom(val message: String) : CounselorRoomCommand() data class SendToCounselor(val message: String) : CounselorRoomCommand() ``` ```kotlin class CounselorRoomActor private constructor( context: ActorContext<CounselorRoomCommand>, private val name: String ) : AbstractBehavior<CounselorRoomCommand>(context) { private var status: CounselorRoomStatus = CounselorRoomStatus.WAITING private lateinit var personalRoom: ActorRef<PersonalRoomCommand> private lateinit var counselor: ActorRef<CounselorCommand> private val observerCounselors = mutableListOf<ActorRef<CounselorCommand>>() companion object { fun create(name: String): Behavior<CounselorRoomCommand> { return Behaviors.setup { context -> CounselorRoomActor(context, name) } } } override fun createReceive(): Receive<CounselorRoomCommand> { return newReceiveBuilder() .onMessage(InvitePersonalRoomActor::class.java, this::onInvitePersonalRoomActor) .onMessage(ChangeStatus::class.java, this::onChangeStatus) .onMessage(AssignCounselor::class.java, this::onAssignCounselor) .onMessage(InviteObservers::class.java, this::onInviteObservers) .onMessage(SendMessageToPersonalRoom::class.java, this::onSendMessageToPersonalRoom) .onMessage(SendToCounselor::class.java, this::onSendToCounselor) .build() } private fun onSendToCounselor(sendToCounselor: SendToCounselor): Behavior<CounselorRoomCommand>? { if(::counselor.isInitialized){ counselor.tell(SendToCounselorHandlerTextMessage(sendToCounselor.message)) observerCounselors.forEach { it.tell(SendToCounselorHandlerTextMessage(sendToCounselor.message)) } } else { context.log.error("CounselorActor is not initialized") } return this } private fun onSendMessageToPersonalRoom(sendMessageToPersonalRoom: SendMessageToPersonalRoom): Behavior<CounselorRoomCommand> { if(::personalRoom.isInitialized){ personalRoom.tell(SendTextMessage(sendMessageToPersonalRoom.message)) } else { context.log.error("PersonalRoomActor is not initialized") } return this } private fun onAssignCounselor(command: AssignCounselor): Behavior<CounselorRoomCommand> { counselor = command.counselorActor counselor.tell(SendToCounselorSystemMessage("You are assigned to the room: $name")) return this } private fun onInviteObservers(command: InviteObservers): Behavior<CounselorRoomCommand> { observerCounselors.clear() observerCounselors.addAll(command.observers) observerCounselors.forEach { it.tell(SendToCounselorSystemMessage("You are observing the room: $name")) } return this } private fun onInvitePersonalRoomActor(command: InvitePersonalRoomActor): Behavior<CounselorRoomCommand> { context.log.info("Invited PersonalRoomActor: ${command.personalRoomActor}") personalRoom = command.personalRoomActor command.replyTo.tell(InvitationCompleted) return this } private fun onChangeStatus(command: ChangeStatus): Behavior<CounselorRoomCommand> { status = command.status context.log.info("CounselorRoom status changed to: $status") command.replyTo.tell(StatusChangeCompleted(status)) return this } } ``` In this updated code, the `InviteObservers` command is used to invite observer counselors separately from assigning the main counselor. The `onInviteObservers` method handles this new command.
InviteObservers 에서 한명씩 초청하고 싶습니다.
To invite observers one by one, you can modify the `InviteObservers` command to accept a single observer and update the `onInviteObservers` method accordingly. Here is the updated code: ```kotlin data class InviteObserver(val observer: ActorRef<CounselorCommand>) : CounselorRoomCommand() ``` ```kotlin private fun onInviteObserver(command: InviteObserver): Behavior<CounselorRoomCommand> { observerCounselors.add(command.observer) command.observer.tell(SendToCounselorSystemMessage("You are observing the room: $name")) return this } ``` Update the `createReceive` method to handle the new `InviteObserver` command: ```kotlin override fun createReceive(): Receive<CounselorRoomCommand> { return newReceiveBuilder() .onMessage(InvitePersonalRoomActor::class.java, this::onInvitePersonalRoomActor) .onMessage(ChangeStatus::class.java, this::onChangeStatus) .onMessage(AssignCounselor::class.java, this::onAssignCounselor) .onMessage(InviteObserver::class.java, this::onInviteObserver) .onMessage(SendMessageToPersonalRoom::class.java, this::onSendMessageToPersonalRoom) .onMessage(SendToCounselor::class.java, this::onSendToCounselor) .build() } ``` This allows you to invite observers one by one using the `InviteObserver` command.
CounselorManagerActor에 옵저버 상담원을 추가하는 이벤트및 작동구현 해
To add an event and implementation for adding observer counselors in `CounselorManagerActor`, follow these steps: 1. Define a new command `AddObserverCounselor`. 2. Implement the handler method `onAddObserverCounselor` in `CounselorManagerActor`. 3. Update the `createReceive` method to handle the new command. Here is the updated code: ### Define the new command ```kotlin sealed class CounselorManagerCommand data class AddObserverCounselor(val roomName: String, val observer: ActorRef<CounselorCommand>, val replyTo: ActorRef<CounselorManagerResponse>) : CounselorManagerCommand() ``` ### Implement the handler method ```kotlin private fun onAddObserverCounselor(command: AddObserverCounselor): Behavior<CounselorManagerCommand> { val counselorRoomActor = counselorRooms[command.roomName] return if (counselorRoomActor != null) { counselorRoomActor.tell(InviteObserver(command.observer)) command.replyTo.tell(CounselorManagerSystemResponse("Observer counselor added to room: ${command.roomName}")) this } else { command.replyTo.tell(ErrorResponse("Room ${command.roomName} not found.")) this } } ``` ### Update the `createReceive` method ```kotlin override fun createReceive(): Receive<CounselorManagerCommand> { return newReceiveBuilder() .onMessage(CreateCounselor::class.java, this::onCreateCounselor) .onMessage(CreateRoom::class.java, this::onCreateRoom) .onMessage(RequestCounseling::class.java, this::onRequestCounseling) .onMessage(GetCounselor::class.java, this::onGetCounselor) .onMessage(GetCounselorRoom::class.java, this::onGetCounselorRoom) .onMessage(UpdateRoutingRule::class.java, this::onUpdateRoutingRule) .onMessage(EvaluateRoutingRule::class.java, this::onEvaluateRoutingRule) .onMessage(AddObserverCounselor::class.java, this::onAddObserverCounselor) .build() } ``` This implementation allows you to add observer counselors to a specific room by sending the `AddObserverCounselor` command to the `CounselorManagerActor`.
Observer는 counselors[command.name] 중에 ..
즉 요청은 고유한 name로 가능
To ensure that observers are added based on a unique name, you can modify the `onAddObserverCounselor` method to look up the observer by name from the `counselors` map. Here is the updated code: 1. Update the `AddObserverCounselor` command to include the observer's name. 2. Modify the `onAddObserverCounselor` method to look up the observer by name. ### Update the `AddObserverCounselor` command ```kotlin data class AddObserverCounselor(val roomName: String, val observerName: String, val replyTo: ActorRef<CounselorManagerResponse>) : CounselorManagerCommand() ``` ### Modify the `onAddObserverCounselor` method ```kotlin private fun onAddObserverCounselor(command: AddObserverCounselor): Behavior<CounselorManagerCommand> { val counselorRoomActor = counselorRooms[command.roomName] val observer = counselors[command.observerName] return if (counselorRoomActor != null && observer != null) { counselorRoomActor.tell(InviteObserver(observer)) command.replyTo.tell(CounselorManagerSystemResponse("Observer counselor ${command.observerName} added to room: ${command.roomName}")) this } else { val errorMessage = when { counselorRoomActor == null -> "Room ${command.roomName} not found." observer == null -> "Observer counselor ${command.observerName} not found." else -> "Unknown error." } command.replyTo.tell(ErrorResponse(errorMessage)) this } } ``` This implementation ensures that the observer is added based on a unique name.
지금까지 완성한 기능을
CounselorManagerActorTest 에 유닛테스트를 추가
@Test fun testAddObserverCounselor() { val probe = testKit.createTestProbe<CounselorManagerResponse>() val counselorManager = testKit.spawn(CounselorManagerActor.create()) // Create a counselor and a room counselorManager.tell(CreateCounselor("observer1", probe.ref)) probe.receiveMessage() counselorManager.tell(CreateRoom("room1", probe.ref)) probe.receiveMessage() // Add observer counselor to the room counselorManager.tell(AddObserverCounselor("room1", "observer1", probe.ref)) val response = probe.receiveMessage() assertEquals(CounselorManagerSystemResponse("Observer counselor observer1 added to room: room1"), response) }
AI가 작성한 코드 유닛테스트로 검증
유닛테스트가 작작동함으로 개선사항 커밋
연구 저장소 : https://github.com/psmon/kopring-reactive-labs