You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 10 Next »

이 장을 통해 알수 있는것

  • 액터를 선택하고 통신을 할수 있는 방법 ( actor ref vs actor selection)
  • 결과처리를 기달려야하는 경우와 아닌 경우 ( tell or ask)


토너 액터설계

프린터의 기능중, 토너의 용량을 관리하고 알려주는 기능부분을 분리하고 다음과 같이 설계해보자

  • 프린팅시 토너를 강제로 소모한다 ( 물론 페이지와 글자수를 계산하여 소모하는 알고리즘은 없다) - tell
  • 토너가 모두 소진되면 오너 프린터에게 토너소모를 푸시메시지로 알린다. - tell
  • 프린터가 토너량을 물어보면 현재 용량을 알려준다. - ask


액터간 데이터 전송은 우리가 하는 대화방법과 유사하다.

상대편의 나이를 알기위해, 상대방이 가진 신분증을 접근하여 상대편의 나이를 알아내는 방법을 사용하지 않는다. ( 일반적인 객체접근 방식)

나이를 알고싶은 상대를 선택한후 (ActorSelection), 나이가 어떻게 되나요? ( A ASK B)  - 25세입니다. (B Tell A)  란 방식을 사용합니다.  ( ASK )

절차적인 접근에서 함수의 리턴되는 값을 사용하는것에 익숙하다라고 하면 다소 어려운 방식일수도 있습니다. 

액터모델이 항상 모든경우에 최상의 방법은 아니며, 특정 도메인 모델이 액터모델로 구현됨으로 장점을 가질수 있습니다. ( 개발자가 알아서~)

OOP 모델에서는 프린터가, 토너를 포함하고 있고 토너의 객체에 직접 접근하여 토너의량을 접근할수 있으며 프린터 모델에서는 그것이 더 심플하고 적합할수 있다. 

여기서는 프린터 모델을 설명하는 베스트 방법을 설명하려는것이 아니고, 액터 모델을 설명하기 위해 비교적 설명이 쉬울것같은 프린터를 대상으로 하였다.

토너 관리자가 단순하게 프린터 한대당 도킹되어 토너관리를 하느것이 아닌, 네트워크로 연결된 여러대의 프린터의 토너량을 관리해야하는 모델이라고가정해보자

OOP로 작성된 모델은 객체접근 밖에 되지 않기때문에 다시 작성되어야 할것이다.  


토너 액터 구현

액터 생성

public class ActorProviders
{
    public delegate IActorRef PrinterActorProvider();

    public delegate IActorRef TonerActorProvider();
}			


services.AddAkkaActor<PrinterActorProvider>((provider, actorFactory) =>
{
    var printerActor = actorFactory.ActorOf(Props.Create(() => new PrinterActor()).WithRouter(new RoundRobinPool(1))
        ,"printer");  //여기서 printer라는 이름을 지정한것은 중요하며 나중에 이름만가지고 액터 참조를 얻을수 있습니다.
    return () => printerActor;
});

services.AddAkkaActor<TonerActorProvider>((provider, actorFactory) =>
{
    var tonerActor = actorFactory.ActorOf(Props.Create(() => new TonerActor()).WithRouter(new RoundRobinPool(1))
        , "toner");
    return () => tonerActor;
});

프린터 액터는 앞장에서 이미 생성을 하였고, 똑같은 방식으로 토너액터를 생성합니다.

모든 액터는 이름지정이 가능하며 (없을시 랜덤),  이름을 통해 액터 참조를 쉽게 얻어 낼수 있습니다.


프린터 액터 수정

ReceiveAsync<PrintPage>(async page =>
{
    logger.Debug($"프린터 요청 들어옴:{page}");
    await Task.Delay(page.DelayForPrint);

    //주소로 액터를 선택하기 : 장점 생성자에 참조객체를 가질필요가 없다.
    ActorSelection tonerActor = Context.System.ActorSelection("user/toner");                
    //토너를 비동기로 소모시킴
    tonerActor.Tell(1);

    //남은 토너 용량 물어봄
    var msg =await tonerActor.Ask("남은용량?");
    logger.Debug($"ASK결과:{msg}");

    logger.Debug($"페이지 출력 완료:{page}");
});

프린터에 토너를 소모시키는 기능과, 토너 용량을 물어보는 기능을 탑재하였습니다.

여기서 토너를 알아내기위해, 토너의 이름만 으로 참조를 획득 하였습니다.

ActorSelection을 한후 Tell Or Ask는 다음과 같이 대화기법과 유사함을 알수 있습니다.

  • 민수야 너의 나이가 몇이니? :  민수야(액터선택)  너의 나이가 몇이니?( ASK )  - 응답을 기달림
  • 영희야 선생님이 찾으셔 교무실에 가봐 :  영희야(액터선택) 선생님이 찾으셔 교무실에 가봐( Tell) - 전달만함, 교무실에 실제로 갔는지 알필요없음


토너 액터 구현

public class TonerActor : ReceiveActor
{
    private readonly ILoggingAdapter logger = Context.GetLogger();
    private readonly string id;
    private int tonerAmount = 5;

    public TonerActor()
    {
        id = Guid.NewGuid().ToString();
        logger.Info($"토너 액터 생성:{id}");
        ReceiveAsync<int>(async usageAmount =>
        {
            logger.Info($"토너 소모 :{usageAmount}");

            if (tonerAmount < 1)
            {
                //송신자 에게 이야기하기
                Sender.Tell("토너가 모두 소모되었습니다.");
            }
            tonerAmount -= usageAmount;                
        });

        ReceiveAsync<string>(async msg =>
        {
            if (msg == "남은용량?")
            {
                Sender.Tell($"남은 용량은 {tonerAmount} 입니다.");
            }
        });
    }
}

통신을 위한 객체 정의를 하지 않았기때문에 여기서는 int,string type만 가지고 송수신하는 방법을 사용하였습니다.

실제 액터 내부는 더 다양한 메시지를 주고 받을수 있기때문에 Type을 정의하는것이 좋습니다.

  • 토너소모를 하기위해 int를 전송하는것이아닌 → 토너소모객체(TonerConsumption)를 정의하여 Tell 하는것이 권장됩니다.
  • Sender : 민수→영희 에게 질문을 하였다고 해봅시다. 최초 대화시작에서 민수는 영희를 지정할 필요가 있지만(액터선택) , 그것에 대한 영희의 응답은 대상지정이 아닌 나에게 질문을 한 대상자 즉 Sender이며 민수를 찾을 필요가 없습니다.
  • Foward : 민수→ 영희에게 길동의 나이를 알아봐죠 한경우 영희가 민수의 질문을 길동에게 전달하고 길동→민수 에게 답해야하는 상황으로 이 경우 Forward라고 합니다. 이것에 대한 로직은 이후 장에서 시도해보겠습니다.


작동결과


git : 액터를 추가하고 상호 메지시 교환을 추가한 변경코드








  • No labels