[C# Actor] Akka 기초 1-2 | 메시지 정의 및 핸들링
- C#/Akka(Actor)
- 2021. 6. 13. 00:00
참조
소개
- 이번 시간에는 고유의 메시지 타입을 만들고, 커스텀 메시지를 기반으로 액터 내에서 처리 흐름을 제어하는 방법을 배워봅니다.
- 또한, 액터 시스템에서 메시지 및 이벤트 기반 방식의 커뮤니케이팅 기본을 알려드립니다.
- 지난 포스팅에서 작성했던 콘솔 액터 시스템을 계속 확장해가며 진행할 것입니다.
- 우리는 우리가 사용할 메시지를 정의하는 것 뿐만 아니라, 간단한 입력 검증과 검증의 결과에 따라 취해질 액션 또한 추가할 것입니다.
Key concpets / background
메시지(Message) 란?
- 모든 POCO는 메시지가 될 수 있습니다.
- 메시지는 string, int 와 같은 value, 인터페이스를 구현하는 오브젝트 등 당신이 원하는 뭐든지 될 수 있습니다.
- 그렇지만, 당신의 커스텀 메시지를 의미 있는 이름의 클래스로 만들고, 원하는 상태를 클래스 안에 캡슐화하는 방법을 추천합니다.
액터(actor)에게 메시지를 보내려면 어떻게 하죠?
- 액터에게 메시지를 보내려면 Tell() 을 이용해서 보낼 수 있습니다.
메시지를 어떻게 처리(handle) 하죠?
- 이것은 전적으로 프로그래머에게 달려있습니다.
- 사실 Akka.NET과 그다지 연관이 없습니다.
- 당신의 액터 내 선택에 따라 처리할 수도, 처리하지 않을 수도 있습니다.
내 액터가 처리하는 법을 모르는 메시지를 받으면 어떻게 되죠?
- 액터는 처리하는 법을 모르는 메시지는 무시 합니다.
- 무시된 메시지는 액터의 종류에 따라 로깅 되기도 합니다.
UntypedActor
에서는, 처리되지 않은 메시지를 수동으로Unhandled()
하지 않는 이상 로깅되지 않습니다.- 그렇지만, ReceiveActor 에서는 처리되지 않은 메시지는 자동으로 Unhandled로 보내고 로깅이 수행됩니다.
class MyActor : UntypedActor
{
protected override void OnReceive(object message)
{
if(message is Messages.InputError)
{
var msg = message as Messages.InputError;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(msg.Reason);
}
else
{
Unhandled(message);
}
}
}
내 액터가 메시지에 어떻게 반응해야 하죠?
- 전적으로 프로그래머에게 달려있습니다.
- 프로그래머는 간단히 메시지를 처리하거나,
Sender
에게 응답하거나, 다른 액터에게 전달하거나, 아무것도 안 하고 끝낼 수 있습니다.
NOTE : 액터가 메시지를 받았을 때는 항상 액터 내부의
Sender
속성을 통해 현재 메시지 발신자의 참조를 가지고 있습니다.
실습
- 이번 실습에서는 우리의 시스템에 유효성 검사를 추가할 것입니다.
- 사용자의 입력을 검사하고, 커스텀 메시지 타입을 사용해서 검증 결과를 다시 사용자에게 알려줄 것입니다.
1단계 : 메시지 타입 정의
- Messages.cs 파일을 추가하고, Messages 클래스를 만듭니다.
- 이 클래스는 이벤트를 알리는 것에 사용할 시스템 레벨 메시지(system-level-messages) 를 정의하는데 이용할 것입니다.
- 우리는 이벤트를 메시지로 바꾸는 패턴을 사용할 것입니다.
- 즉, 이벤트가 발생하면, 그것에 대해 알아야 할 필요가 있는 액터(들)에게 적절한 메시지 클래스를 보낼 것이고, 그리고 수신하는 액터측에서 필요에 따라 메시지를 듣고 응답할 것입니다.
- 각 메시지 타입에 따라 region 을 추가합니다.
- 파일에 3개의 각기 다른 메시지 타입을 위한 region 을 추가합니다.
- 다음으로, 우리는 이벤트를 알리는 것에 사용할 메시지 클래스를 만들 것입니다.
namespace Akka_Basic
{
public class Messages
{
#region Neutral/system messages
#endregion
#region Success messages
#endregion
#region Error messages
#endregion
}
}
우리는 regions 안에 다음과 같은 상황을 알리기 위한 커스텀 메시지 타입을 정의할 것입니다.
사용자가 비어있는(blank) 입력을 한 경우 - 사용자가 잘못된 입력을 한 경우 - 사용자가 올바른 입력을 한 경우
ContinueProcessing 메시지를 만듭니다.
Neutral/system message 안에 마커 메시지 클래스를 정의합니다.
이는 처리를 계속하라고 신호를 보내는 것에 사용합니다.
namespace Akka_Basic
{
public class Messages
{
#region Neutral/system messages
public class ContinueProcessing { }
#endregion
#region Success messages
#endregion
#region Error messages
#endregion
}
}
- InputSuccess 메시지를 만듭니다.
- Success messages 안에 InputSuccess 클래스를 정의합니다.
- 우리는 이것을 사용자의 입력이 올바르고 검증을 통과할 때 보낼 신호로 사용할 것입니다.
namespace Akka_Basic
{
public class Messages
{
#region Neutral/system messages
public class ContinueProcessing { }
#endregion
#region Success messages
public class InputSuccess
{
public string Reason { get; private set; }
public InputSuccess(string reason)
{
Reason = reason;
}
}
#endregion
#region Error messages
#endregion
}
}
- InputError 메시지를 만듭니다.
- Error messages 안에 InputError 클래스를 정의합니다.
- 우리는 이것을 잘못된 입력이 발생했을 때 보낼 신호로 사용할 것입니다.
namespace Akka_Basic
{
public class Messages
{
#region Neutral/system messages
public class ContinueProcessing { }
#endregion
#region Success messages
public class InputSuccess
{
public string Reason { get; private set; }
public InputSuccess(string reason)
{
Reason = reason;
}
}
#endregion
#region Error messages
public class InputError
{
public string Reason { get; private set; }
public InputError(string reason)
{
Reason = reason;
}
}
public class NullInputError : InputError
{
public NullInputError(string reason) : base(reason) { }
}
public class ValidationError : InputError
{
public ValidationError(string reason) : base(reason) { }
}
#endregion
}
}
2단계 : 이벤트를 메시지로 변환 및 전송
- 우리는 이벤트를 감싸고(wrap) 있는 메시지를 만들었습니다.
- 이제 ConsoleReaderActor와 ConsoleWriterActor 에서 사용해 봅니다.
- ConsoleReaderActor 에 내부 메시지 타입을 추가해주세요.
최종 소스코드
- Messages.cs
namespace Akka_Basic
{
public class Messages
{
#region Neutral/system messages
public class ContinueProcessing { }
#endregion
#region Success messages
public class InputSuccess
{
public string Reason { get; private set; }
public InputSuccess(string reason)
{
Reason = reason;
}
}
#endregion
#region Error messages
public class InputError
{
public string Reason { get; private set; }
public InputError(string reason)
{
Reason = reason;
}
}
public class NullInputError : InputError
{
public NullInputError(string reason) : base(reason) { }
}
public class ValidationError : InputError
{
public ValidationError(string reason) : base(reason) { }
}
#endregion
}
}
- ConsoleReaderActor.cs
using System;
using Akka.Actor;
namespace Akka_Basic
{
public class ConsoleReaderActor : UntypedActor
{
public const string StartCommand = "start";
public const string ExitCommand = "exit";
private IActorRef _consoleWriterActor;
public ConsoleReaderActor(IActorRef consoleWriterActor)
{
_consoleWriterActor = consoleWriterActor;
}
protected override void OnReceive(object message)
{
if(message.Equals(StartCommand))
{
DoPrintInstructions();
}
else if(message is Messages.InputError)
{
_consoleWriterActor.Tell(message as Messages.InputError);
}
GetAndValidateInput();
}
private void DoPrintInstructions()
{
Console.WriteLine("Write whatever you want into the console");
Console.WriteLine("Some entries will pass validation, and some won't...\n\n");
Console.WriteLine("Type 'exit' to quit this application at any time. \n");
}
private void GetAndValidateInput()
{
var message = Console.ReadLine();
if(string.IsNullOrEmpty(message))
{
//signal that the user needs to supply an input, as previously
//received input was blank
Self.Tell(new Messages.NullInputError("No input received."));
}
else if(String.Equals(message, ExitCommand, StringComparison.OrdinalIgnoreCase))
{
//shut down the entire actor system (allows the process to exit)
Context.System.Terminate();
}
else
{
var valid = IsValid(message);
if(valid)
{
_consoleWriterActor.Tell(new Messages.InputSuccess("Thank you! Message was valid."));
//continue reading messages from console
Self.Tell(new Messages.ContinueProcessing());
}
else
{
Self.Tell(new Messages.ValidationError("Invalid : input had odd number of characters."));
}
}
}
private static bool IsValid(string message)
{
var valid = message.Length % 2 == 0;
return valid;
}
}
}
- ConsoleWriterActor.cs
using System;
using Akka.Actor;
namespace Akka_Basic
{
public class ConsoleWriterActor : UntypedActor
{
protected override void OnReceive(object message)
{
if(message is Messages.NullInputError)
{
var msg = message as Messages.InputError;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(msg.Reason);
}
else if(message is Messages.ValidationError)
{
var msg = message as Messages.InputError;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(msg.Reason);
}
else if(message is Messages.InputSuccess)
{
var msg = message as Messages.InputSuccess;
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine(msg.Reason);
}
else
{
Console.WriteLine(message);
}
Console.ResetColor();
}
}
}
- Program.cs
using System;
using Akka.Actor;
namespace Akka_Basic
{
class Program
{
static void Main(string[] args)
{
//ActorSystem 생성
ActorSystem MyActorSystem = ActorSystem.Create("MyActorSystem");
//Actor 생성
var consoleWriterActor = MyActorSystem.ActorOf(Props.Create(() => new ConsoleWriterActor()));
var consoleReaderActor = MyActorSystem.ActorOf(Props.Create(() => new ConsoleReaderActor(consoleWriterActor)));
//메시지 보내기
consoleReaderActor.Tell(ConsoleReaderActor.StartCommand);
//액터시스템이 종료될 때 까지 메인 스레드가 종료 되는 것을 차단합니다.
MyActorSystem.WhenTerminated.Wait();
}
}
}
실행결과
728x90
'C# > Akka(Actor)' 카테고리의 다른 글
[C# Actor] Akka 기초 1-5 | ActorSelection과 함께 주소로 액터 찾기 (0) | 2021.06.16 |
---|---|
[C# Actor] Akka 기초 1-4 | 자식 액터, 액터 계층 구조, 그리고 감시(Supervision) (0) | 2021.06.15 |
[C# Actor] Akka 기초 1-3 | Props와 IActorRef (0) | 2021.06.14 |
[C# Actor] 액터와 액터시스템(ActorSystem) (0) | 2021.06.12 |
[C# Akka] VS 2019 .NET Framework NuGet 설치 경로 및 종속성 확인 (0) | 2021.06.11 |
이 글을 공유하기