[C# Actor] Akka 기초 1-3 | Props와 IActorRef
- C#/Akka(Actor)
- 2021. 6. 14. 00:00
참조
소개
- 이번 레슨은 액터를 만들고 메시지를 보내는 여러 방법에 대해 복습 및 보강을 해보도록 합니다.
- 이번 강의는 코딩이 별로 없고, 더욱 개념적인 내용이지만 앞으로 만날 코드를 이해하기 위한 핵심요소 이자 필수 기반입니다.
- 또한 코드가 조금 변경되었습니다.
- ConsoleReaderActor에선 더는 유효성 검사를 하지 않습니다.
- 대신 콘솔에서 받은 메시지를 유효성 검사를 담당할 액터 ValidationActor에게 전달합니다.
Key concepts / background
IActorRef 무엇인가요?
- IActorRef는 액터에 대한 참조(reference) 또는 핸들(handle) 입니다.
- IActorRef의 목적은 ActorSystem을 통해 액터에게 메시지를 전송하는 것 을 지원하는 것 입니다.
- 당신은 액터에게 직접적으로 말을 걸지 않습니다.
- IActorRef 에게 메시지를 보내고, ActorSystem은 당신을 위해 메시지를 전달합니다.
내 액터와 실제로 대화하지 않아요? 왜 안돼요?
- 직접적이지 않을 뿐, 액터와 대화를 나누고 있습니다.
- 액터들과 대화를 할 때는 ActorSystem의 중개를 통해 대화해야 합니다.
- IActorRef로 메시지를 보내고, ActorSystem이 실제 액터에게 메시지를 전달하는 것이 왜 더 좋은지에 대한 두 가지 이유가 여기에 있습니다.
- 메시지 의미론적으로 당신이 작업하기에 더 좋은 정보를 제공합니다. ActorSystem은 모든 메시지를 각 메시지에 대한 메타 데이터를 포함하여 Envelope 으로 포장합니다.
- 위치 투명성 을 허용합니다. 이것은 액터가 어느 프로세스나 머신에 올라와 있는지 걱정할 필요가 없다는 멋진말입니다. 모든 추적 잡업은 시스템이 할 일입니다. 이것은 원격 액터를 허용하는 데 필수적이고, 방대한 양의 데이터를 처리하기 위해 액터의 시스템을 확장하는 방법입니다.
내 메시지가 액터에게 전달되었음을 어떻게 알 수 있나요?
- 일단 지금은, 이것에 대해 고려하지 않아도 됩니다.
- Akka.NET의 ActorSystem 에서 이를 보장하는 메커니즘을 제공하지만, GuaranteedDeliveryActors 는 고급주제입니다.
- 지금은 단지 메시지 전달 작업이 당신이 아닌 ActorSystem이 할 이라고 생각합니다.
그래서 IActorRef 는 어떻게 얻을 수 있나요?
- IActorRef를 얻는 방법은 2가지가 있습니다.
1) 액터 생성
- 액터들은 본질적인 감독(supervision) 계층을 형성합니다.
- 이것은 ActorSystem에 직접 보고하는 최상위 액터와 다른 액터에게 보고하는 "자식(child)" 액터가 있음을 의미합니다.
- 액터를 만들려면, 이것의 컨텍스트에서 만들어야 합니다. 그리고 우리는 이미 이것을 끝냈습니다.
//"MyActorSystem" 이라는 액터 시스템을 가지고 있다고 가정합니다.
IActorRef myFirstActor = MyActorSystem.ActorOf(Props.Create(() => new MyActorClass()), "myFirstActor");
- 위의 예제에서 볼 수 있듯이, 항상 이것을 감독할 액터의 컨텍스트에서 액터를 만듭니다.
- 위와 같이 ActorSystem 위에서 직접 액터를 만들면 최상위 액터가 됩니다.
- 자식 액터 또한 똑같은 방법으로 만듭니다.
//MyActorClass 내부에서 child actor를 만들어야 할 때도 있습니다.
//보편적으로 OnReceive 나 PreStart 안에서 일어납니다.
class MyActorClass : UntypedActor
{
protected override void Prestart()
{
IActorRef myFirstChildActor = Context.ActorOf(Props.Create(() => new MyChildActorClass()), "myFirstChildActor");
}
}
- 주의 : 액터를 만들기 위해 Props와 ActorSystem의 외부에서 new MyActorClass()를 호출하지 마십시오. ActorSystem의 컨텍스트 외부에서 액터를 만든다면 완전히 쓸모없고 바람직하지 못한 오브젝트가 만들어질 것입니다.
2) 액터찾기
- 모든 액터는 시스템 계층 속에서 어디에 있는지를 나타내는 기술적인(ActorPath 라는 이름의) 주로를 가지고 있습니다.
- 당신은 주소로 액터를 찾아서(IActorRef를 입수하여) 핸들을 얻을 수도 있습니다.
액터들에게 꼭 이름을 지어야 하나요?
- 위에서 액터를 만들 때 ActorSystem에 이름을 같이 전달한 것을 알아차리셨을 수도 있습니다.
//ActorOf()를 호출할 때의 마지막 Argument가 이름입니다.
IActorRef myFirstActor = MyActorSystem.ActorOf(Props.Create(() => new MyActorClass()), "myFirstActor");
- 이름은 필수가 아닙니다. 아래와 같이 이름 없이 액터를 만들 수도 있습니다.
//ActorOf() 함수를 호출할 때, 마지막 argument로 이름을 주지 않았습니다.
IActorRef myFirstActor = MyActorSystem.ActorOf(Props.Create(() => new MyActorClass()));
- 이름을 지어 주는 것이 가장 좋은 방법입니다.
- 당신은 액터 이름은 로그 메시지와 액터의 식별에 사용되기 때문입니다.
- 습관으로 만드는걸 권장합니다.
- 디버깅을 해야 하는 상황에서 훌륭한 라벨이 붙어 있을 때, 미래의 당신은 자기 자신에게 고마워할 것입니다.
컨텍스트(Context) 는 어디에 사용되나요?
- 모든 액터들은 컨텍스트가 존재하며, 모든 액터들에 내장된 Context 속성으로 접근할 수 있습니다.
- Context에는 현재 메시지의 Sender, 현재 액터의 Parent, Children 과 같은 액터의 현재 상태에 관련된 메타 데이터가 있습니다.
- Parent, Children, 그리고 Sender 모두 IActorRef를 제공하고 사용할 수 있습니다.
Props 란?
- Props는 액터를 만들기 위한 레시피로 생각합니다.
- 전문적으로 Props는 주어진 액터 타입의 인스턴스를 만들기 위해 필요한 정보를 캡슐화하는 configuration class 입니다.
- Props 오브젝트는 액터의 인스턴스를 생성하는 것에 사용하는 공유 가능한 레시피입니다.
- Props는 ActorSystem으로 전달되어 당신이 사용할 액터를 생성합니다.
- 지금은 아마 Props가 조금 버겁게 느껴질 수 있ㅅ브니다.
- 우리가 봐왔던 것과 같은 대부분의 기본적인 Props는 액터를 만들기 위해 필요한 구성 요소들만 포함하는 것처럼 보입니다.
- 클래스와 필수 인자들, 생성자 처럼요.
- 하지만, 아직 보지 못한 점은 Props는 원격 작업을 수행하는 데 필요한 배포 정보와 다른 세부적인 설정을 포함하도록 확장된다는 것입니다.
- 예를 들어, Props는 직렬화 할 수 있으므로(serializable), 네트워크 어딘가의 다른 머신에서 전체 액터 그룹을 원격으로 생성하고 배포할 수 있습니다.
- 이것은 진도보다 앞서가는 내용이지만, 짧게 정리하면 우리는 Akka.NET에서 제공하는 흥미를 끌고 강한 힘을 가진 다양한 응용 기능들을 지원하기 위해 Props 를 필요로합니다.
Props를 어떻게 만들 수 있나요?
Props를 만드는 법을 말하기 전에, 하면 안되는 것을 말하겠습니다.
Props를 만들기 위해 new Props(...) 를 호출하지 않습니다.
액터를 만들기 위해 new MyActorClass()를 호출하는 것처럼, 이것은 프레임워크와 싸우고 Akka의 ActorSystem에게 액터의 재시작, 라이프사이클 관리와 관련한 안전 보장을 제공을 시키지 못합니다.
여기에 올바르게 Props를 만드는 3가지 방법이 있으며, 전부 Props.Create() 호출을 포함하고 있습니다.
- typeof 문법
Props props1 = Props.Create(typeof(MyACtor));
단순해 보이지만, 이 방법을 추천하지 않습니다.
이것은 타입 안정성이 없으며, 컴파일을 통과하고 런타임에 터뜨리는 버그를 쉽게 만들게 합니다.
- lambda 문법
Props props2 = Props.Create(() => new MyActor(...), "...");
이것은 훌륭한 문법이며, 우리가 좋아하는 방법입니다.
당신은 액터 클래스의 생성자에 필요한 인자들을 이름과 함께 전달할 수 있습니다.
- generic 문법
Props props3 = Props.Create<MyActor>();
- 이것 또한 우리가 전적으로 권하는 또 다른 훌륭한 문법입니다.
Props는 어떻게 사용하나요?
- Props - 액터 레시피 - Context.ActorOf() 호출에 전달하고, ActorSystem은 이 레시피를 읽습니다.
실습
1단계 : 유효성 검사를 별도의 액터로 만들기
모든 유효성 검사를 별도의 액터로 옮길 것입니다.
유효성 검사 코드들은 ConsoleReaderActor에 속하지 않습니다.
유효성 검사는 자체 액터를 가질 가치가 있습니다.(OOP에서 단일목적 - single-purpose-오브젝트를 지향하는 것과 비슷합니다.)
ValidationActor 라는 이름의 새로운 클래스와 파일을 만듭ㄴ디ㅏ. ConsoleReaderActor에 있는 유효성 검사 로직을 모두 옮깁니다.
using Akka.Actor;
namespace Akka_Basic
{
//사용자의 입력을 유효성 검사하고 다른 액터로 결과 신호를 전달하는 액터
public class ValidationActor : UntypedActor
{
private readonly IActorRef _consoleWriterActor;
public ValidationActor(IActorRef consoleWriterActor)
{
_consoleWriterActor = consoleWriterActor;
}
protected override void OnReceive(object message)
{
var msg = message as string;
if(string.IsNullOrEmpty(msg))
{
//signal that the user needs to supply an input
_consoleWriterActor.Tell(new Messages.NullInputError("No input received."));
}
else
{
var valid = IsValid(msg);
if(valid)
{
//send success to console writer
_consoleWriterActor.Tell(new Messages.InputSuccess("Thank you! Message was valid"));
}
else
{
//send that input was bad
_consoleWriterActor.Tell(new Messages.ValidationError("Invalid : input had odd number of characters."));
}
}
}
private bool IsValid(string message)
{
var valid = message.Length % 2 == 0;
return valid;
}
}
}
2단계: 우리의 액터 레시피, Props 만들기
이제 우리는 좋은 물건을 엎을 수 있습니다.
우리가 배운 Props를 사용할 것이고, 액터들을 만드는 방법을 바꿀 것입니다.
다시 말하지만, typeof 문법의 사용을 권하지 않습니다. 연습삼아 lambda와 generic 문법을 사용해주세요.
이 섹션에서는 Props 오브젝트를 읽기 쉽게 만들기 위해 줄을 나눌 겁니다. 실제로는 우리는 대게 ActorOf 호출 안에 인라인으로 사용합니다.
다음은 전체 실습 소스코드 입니다.
Program.cs
using Akka.Actor;
namespace Akka_Basic
{
class Program
{
static void Main(string[] args)
{
//initialize MyActorSystem
ActorSystem MyActorSystem = ActorSystem.Create("MyActorSystem");
IActorRef consoleWriterActor = MyActorSystem.ActorOf(Props.Create(() => new ConsoleWriterActor()), "consoleWriterActor");
IActorRef validationActor = MyActorSystem.ActorOf(Props.Create(() => new ValidationActor(consoleWriterActor)), "validationActor");
IActorRef consoleReaderActor = MyActorSystem.ActorOf(Props.Create(() => new ConsoleReaderActor(validationActor)), "consoleReaderActor");
//tell console reader to begin
consoleReaderActor.Tell(ConsoleReaderActor.StartCommand);
//blocks the main thread from exiting until the actor system is shut down
MyActorSystem.WhenTerminated.Wait();
}
}
}
- 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
}
}
- ValidationActor.cs
using Akka.Actor;
namespace Akka_Basic
{
//사용자의 입력을 유효성 검사하고 다른 액터로 결과 신호를 전달하는 액터
public class ValidationActor : UntypedActor
{
private readonly IActorRef _consoleWriterActor;
public ValidationActor(IActorRef consoleWriterActor)
{
_consoleWriterActor = consoleWriterActor;
}
protected override void OnReceive(object message)
{
var msg = message as string;
if(string.IsNullOrEmpty(msg))
{
//signal that the user needs to supply an input
_consoleWriterActor.Tell(new Messages.NullInputError("No input received."));
}
else
{
var valid = IsValid(msg);
if(valid)
{
//send success to console writer
_consoleWriterActor.Tell(new Messages.InputSuccess("Thank you! Message was valid"));
}
else
{
//send that input was bad
_consoleWriterActor.Tell(new Messages.ValidationError("Invalid : input had odd number of characters."));
}
}
// tell sender to continue doing its thing
// (whatever that may be, this actor doesn't care)
Sender.Tell(new Messages.ContinueProcessing());
}
private 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.InputError)
{
var msg = message as Messages.InputError;
Console.ForegroundColor = ConsoleColor.Red;
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();
}
}
}
- 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 _validationActor;
public ConsoleReaderActor(IActorRef validationActor)
{
_validationActor = validationActor;
}
protected override void OnReceive(object message)
{
if(message.Equals(StartCommand))
{
DoPrintInstructions();
}
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) &&
string.Equals(message, ExitCommand, StringComparison.OrdinalIgnoreCase))
{
//if user typed ExitCommand, shut down the entire actor
//system (allows the process to exit)
Context.System.Terminate();
return;
}
//otherwise, just hand message off to validation actor
_validationActor.Tell(message);
}
}
}
'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-2 | 메시지 정의 및 핸들링 (0) | 2021.06.13 |
[C# Actor] 액터와 액터시스템(ActorSystem) (0) | 2021.06.12 |
[C# Akka] VS 2019 .NET Framework NuGet 설치 경로 및 종속성 확인 (0) | 2021.06.11 |
이 글을 공유하기