Page History
...
UserCase 구상이 완료되면, 실제 메시지를 처리할 인스턴스를 작성하고
구조적으로 배치해야합니다.
구조적으로 배치해야합니다.
AKKA System을 선택한 이유는, 액터를 잘 설계한다고하면, 코드의 큰 수정없이 클러스터로 확장이 용이합니다. - https://doc.akka.io/docs/akka/2.5/cluster-usage.html#a-simple-cluster-example
당장은 클러스터를 크게 신경쓰지 않고 진행을 하겠습니다.
아직은 아무기능이 없으며 서버 기능을 하는 액터를 옹기종기 잘 모아 놓습니다. 실제 구현 단계가 될것입니다.
메시지 흐름 설계
그 다음 해야할일은 메시지 흐름을 조금더 구체화 해보겠습니다구체화하는것입니다.
액터에 실제 메시지흐름을 정의하는 단계입니다정의하고 이 베이스로 액터의 메시지 흐름을 처리하는 코드를 작성할것입니다.
게임에 접속하고, 멀티플레이를 위한 테이블 공간에 참여시키는 메시지 시퀀스 다이어 그램
메시지 처리기 구현
액터의 메시지는 오브젝트의 패턴매칭을 통해 분기처리가 가능합니다.
조금더 진보된 방식의 switch 구문이라고 보면 되겠습니다.
최근 모던 언어들은 대부분 패턴매칭을 언어 자체에서 지원하는 경우가 많습니다.
| Code Block | ||||
|---|---|---|---|---|
| ||||
@Override
public AbstractActor.Receive createReceive() {
return receiveBuilder()
.match(ConnectInfo.class, c -> {
if(c.getCmd()== ConnectInfo.Cmd.CONNECT){
sessionMgr.put(c.getSessionId(),c.getWsSender());
log.info("user connected:"+c.getSessionId());
}else if(c.getCmd()== ConnectInfo.Cmd.DISCONET){
sessionMgr.remove(c.getSessionId());
Player removeUser = new Player();
removeUser.setSession(c.getSessionId());
if(c.getTableNo()>0){
findTableByID(c.getTableNo()).tell(new SeatOut(removeUser),ActorRef.noSender());
}else{
findTableALL().tell(new SeatOut(removeUser),ActorRef.noSender());
}
log.info("user disconnected:"+c.getSessionId());
}
sessionMgr.put(c.getSessionId(),c.getWsSender());
})
.match(TableCreate.class, t->{
// Create a table under the lobby, if you have an Actor named TableManagement, you can move easily.
String tableUID = "table-" + t.getTableId();
if(t.getCmd() == TableCreate.Cmd.CREATE){
ActorRef tableActor = getContext().actorOf( TableActor.props(t,this.getSelf() ), tableUID);
tableActor.tell(t,ActorRef.noSender());
}
})
.match(JoinGame.class, j->{
joinGameTable(j.getTableId(),j.getName(),j.getSession());
})
.match(MessageWS.class, m->{
send(m.getSession(),m.getGameMessage());
})
.build();
} |
메시지 흐름을 다이어그램과 일치시키는것은 쉬운일은 아닙니다. 보통 메시지 전송 툴(Akka/Netty/Kafka등등)들이 사용하기 어렵고
코드가 복잡해진다고 하면 이것을 일치하는것은 더 어려운일이나, AKKA의 Actor은 UML툴과 비교적 쉽게 일치가 됩니다.
또한 자신의방식 혹은 팀과 공유할수 있는 방식으로 코드와 다이어그램을 일치시키려는 노력은 중요합니다.
게임로직은 API가 단순하게 DB를 저장시키고 조회를 하는것이 아니기 때문에 문서 자동화가 어렵기때문입니다.
...
ActorPath를 통한 메시지 전송
액터의 중요한 속성중 하나인데, 액터는 그 어떤 액터와 상태 공유를 하지 않습니다. ( 할수없습니다.)
그래서 익숙한 도트접근을 통해 로컬에서 개발되었다고 해도 그어떤 값도 얻어 낼수 없습니다.(메모리 공유를 원천 차단합니다.)
| OOP | ACTOR |
|---|---|
Lobby a; a.getTable(1).getTableName(); | LobbyActor a; TableActor b; b.tell("some ask",a) |
로컬에서 구현이 복잡해지는 단점이 있으나, 리모트로 확장시 구현의 차이가 없어진다는 장점이 있습니다.
주소를 통한 Ask패턴사용
| Code Block | ||||
|---|---|---|---|---|
| ||||
private ActorRef findTableByID(int tableID) throws Exception {
String tableActorPath = "/user/lobby/table-"+tableID;
ActorSelection tableSelect = this.getContext().actorSelection(tableActorPath);
FiniteDuration duration = FiniteDuration.create(1, TimeUnit.SECONDS);
Future<ActorRef> fut = tableSelect.resolveOne(duration);
ActorRef tableActor = Await.result(fut, duration);
return tableActor;
} |
객체를 통한 메시지 전송의 문제는 , 메시지를 보낼려는 대상의 객체를 포함해야한다는 점이며
이렇게 포함되고 상속된 객체들은 나중에 심각한 디펜던시를 가질수 있습니다.
액터 메시지 전송은, 절대 주소, 상대주소, 객체참조 전송등 다양한 방법을 제공하며 상대의 주소만 알면 됩니다.
...
상대의 액터주소만 알고 있다면 , 원격지라 할지라도 상대의 액터 객체를 알지못해도 통신이 가능합니다.
객체를 통한 전송
| Code Block | ||||
|---|---|---|---|---|
| ||||
ActorRef someActor = system.ActorOf(........);
someActor.tell("some message",null); |
주로 로컬에서 활용되며, 액터는 생성시점 액터참조를 바로 얻을수 있습니다. 하지만 상대 액터가 가진
멤버접근을 통한 정보 획득은 불가하며 로컬이라 할지라도 tell 로 질의를 해야합니다.
주소선택을 통한 전송
| Code Block | ||||
|---|---|---|---|---|
| ||||
ActorSelection lobbyActor = system.ActorSelection("user/lobby");
lobbyActor.tell("some message",null);
// 자식의 모든 요소 선택이 가능합니다.
ActorSelection tableAllActor = system.ActorSelection("user/lobby/table/*");
tableAllActor.tell("some message",null); |
ActorPath를 통한 여러 지점에으로의 우아한 메시지 전송을 다음 링크를 통해 살펴볼수 있습니다. 이것은 복잡하게 구성된 요소로의 메시지 전송을 단순하게 해줍니다
Actor를 심화학습하고 싶다고 하면, 아래를 조금더 살펴보면 됩니다.
- https://doc.akka.io/docs/akka/2.5/general/addressing.html - Actor Path
- https://www.baeldung.com/akka-with-spring
...
