이 장을 통해 추가적으로 알수 있는것

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


토너 액터설계

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

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


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

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

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

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

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

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

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

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

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


프린터 액터에 토너를 탑재하기

여기서 프린터와 액터는 어떠한 객체 디펜던시가 없기때문에 , 토너액터는 원격지로 분리도 가능합니다.

토너 액터 구현

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} 입니다.");
            }
        });
    }
}

//토너액터생성
AkkaLoad.RegisterActor(
	"toner",
	actorSystem.ActorOf(Props.Create<TonerActor>()
		.WithRouter(new RoundRobinPool(1)),
		"toner"
));

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

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

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


프린터 액터 수정

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

    //주소로 액터를 선택하기 : 장점 생성자에 참조객체 디펜던시를 가질필요가 없다. ( DI로 동적인 디펜던시를 알아내기 어려울때 사용가능하며 위치 투명성을 보장합니다.)
    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) - 전달만함, 교무실에 실제로 갔는지 알필요없음


작동결과




  • No labels