[C# Actor] Akka 기초 1-3 | Props와 IActorRef

참조

소개

  • 이번 레슨은 액터를 만들고 메시지를 보내는 여러 방법에 대해 복습 및 보강을 해보도록 합니다.
  • 이번 강의는 코딩이 별로 없고, 더욱 개념적인 내용이지만 앞으로 만날 코드를 이해하기 위한 핵심요소 이자 필수 기반입니다.
  • 또한 코드가 조금 변경되었습니다.
  • 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() 호출을 포함하고 있습니다.

    1. typeof 문법
Props props1 = Props.Create(typeof(MyACtor));
  • 단순해 보이지만, 이 방법을 추천하지 않습니다.

  • 이것은 타입 안정성이 없으며, 컴파일을 통과하고 런타임에 터뜨리는 버그를 쉽게 만들게 합니다.

    1. lambda 문법
Props props2 = Props.Create(() => new MyActor(...), "...");
  • 이것은 훌륭한 문법이며, 우리가 좋아하는 방법입니다.

  • 당신은 액터 클래스의 생성자에 필요한 인자들을 이름과 함께 전달할 수 있습니다.

    1. 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);
        }
    }
}
728x90

이 글을 공유하기

댓글

Designed by JB FACTORY