2008년 11월 09일
게임 서버 엔진의 세션과 엔티티 설계
게임 서버 프로그램의 엔진을 개발할 때 주로 고민하게 되는 부분 중 하나인
세션과 엔티티에 대한 이야기를 해볼 까 합니다.
우선, 단어에 대한 정의가 우선이겠군요.
[ SESSION ]
7【컴퓨터】 세션
a 컴퓨터 시스템의 사용자가 단말기 앞에 앉아 로그인하여 사용을 시작한 다음 작업을
끝마칠 때까지의 동안
b 원격 컴퓨터와 사용자 사이에서 교환되는 데이터들의 완전한 집합
[ ENTITY ]
en·ti·ty〔〕 n. (pl. -ties)
1 실재(實在), 존재
2 본체, 실체(實體), 실재물;자주 독립체
*출처 [네이버 단어사전]
위 단어의 뜻을 참고하여, 각각의 단어의 의미를 가지고 아래와 같은 네이밍으로 구성합니다.
[세션] 이란 네트워크 I/O 를 처리하는 클래스를 뜻합니다.
EventHandler, Session 등의 이름으로 구성되는 클래스들을 말하겠지요?
[엔티티]란 로직을 처리하기 위해 설계되는 클래스를 말합니다.
User, Player, Monster 등등 로직을 처리하기 위해 필요한 실재를 구성한 클래스 입니다.
일반적으로 게임 서버 라이브러리에서 세션과 엔티티는 아래와 같이 구성하는 것 같습니다.
그림 1) Socket[Session] 을 상속받은 Player[Entity] 구조

Socket 클래스는 실제 SOCKET 핸들을 속성으로 가지고 그에 대한 인터페이스를 마련한
세션 역할의 클래스입니다. 그리고 Player 는 실제 로직을 처리하기 위한 엔티티 역할이겠지요.
가장 쉽게 접근할 수 있는 구성이며, 또한 매우 단순한 형태의 구조라고 말할 수 있을 것입니다.
아무래도 소켓 자체에 대한 래퍼 클래스를 구성하면서, 매번 이 역할의 클래스에 접근해야 할 일이
많아져, 아예 Socket 클래스에 많은 역할을 가지게 하여 파생된 형태의 패턴이 아닐까 생각됩니다.
그리고 많은 서버 라이브러리가 하는 방식인 세션 역할의 클래스를 상속받은 유저 클래스 형태의
구성이 될 것 이고요. 이 구성의 장점과 단점은 무엇일까요 ?
이 부분에 대해서는 조금 있다가 언급하기로 하고, 이와 비슷한 또 다른 구성이 있습니다.
어느정도 네트워크 패턴에 대해 많이 생각하고 이미 많이 언급된 형태(Reactor, Proactor)의 영향으로
아래와 같은 네이밍으로 구성된 설계가 있을 것입니다.
그림 2) EventHandler[Session] 을 상속받은 Player[Entity] 구조

이 구조는 이미 많이 알려진 Reactor, Proactor 패턴에서 관련 이벤트를 처리하기 위한 EventHandler 를
구현하여 이루어진 형태입니다. [ACE] 가 가장 좋은 예가 되겠지요. ?
실제 구현적으로는 다른 부분이 있을 수 있겠습니다만, 이 글을 통해 이야기하고자 하는
세션과 엔티티 설계 부분에 대해서는 [그림 1] 의 구성과 크게는 다를 것이 없습니다.
위와 같은 형태를 가진 서버 엔진을 실제 구현도 해보고 업무에서 활용하여 서비스도 해보았습니다.
그렇게 하면서 느끼게 된 제 주관적인 장/단점은 아래와 같습니다.
장점
1. 구조가 비교적 단순하여 직관성이 향상된다.
2. 엔진이 알고 있는 세션을 직접 상속하므로 접근성이 확대된다.
3. 디버깅이 효율적이다.
단점
1. 서버 엔진의 구현에 참여된 세션이 그대로 로직에 노출된다.
2. 상속 구조를 가지므로 서버 엔진과 로직의 의존관계가 이루어진다.
3. 엔티티는 세션과 is-a 관계로 불필요한 책임을 가지게 된다. [단일 책임 원칙(SRP) 위반]
저는 위 장/단점을 토대로 고민 끝에 아래와 같은 구성으로 게임 서버 엔진을 개발하게 되었습니다.
그림 3)
세션[Session] 엔티티[Entity] 분할 구성EventHandler[Session] 은 위에서 소개한(그림 1, 2) 구성과 같은 방식으로 제작된 클래스입니다.
IO Coordinator 는 실제 서버 엔진 Network I/O Layer 에서 발생한 여러가지 이벤트를
UserLayer 즉, 로직에 알리는 역할을 가진 클래스입니다.
Entity 는 서버 엔진이 알고있는 주체와의 연관 관계를 위해 설계된 클래스입니다.
실제로 서버 엔진을 사용하게 되는 유저, 즉 로직은 EventHandler[Session] 을 알지 못하고
오직 Entity 클래스만을 알도록 설계적으로 노출시키도록 하였습니다.
그리고 서버 엔진에서 로직으로, 그리고 로직에서 엔진으로 요구하는 여러가지 명령에 대해
각각의 주체를 공유하기 위한 Unique ID 를 생성해 관리하며 처리합니다.
즉, 위 구성(그림 1, 2) 과는 다르게 세션과 엔티티 클래스의 상속 관계를 제거하였습니다.
따라서 로직이 알 필요가 없는 세션 클래스의 역할 자체를 노출할 필요가 없게 되었으며,
로직에서 엔진쪽으로 접근이 필요한 인터페이스에 대해서는 엔티티 클래스가 그 역할을
가지게 하여 프록시 패턴의 형태로 구성이 됩니다.
따라서, 의존관계가 사라지므로 각 레이어의 주체 클래스(엔진:Session, 로직:Entity) 의
영역을 제거하거나 혹은 직접적인 접근이 이루어지더라도 각각에게는 아무런 영향이
없는 것입니다.
엔티티 클래스 역시도 서버 엔진 구성에 포함되는 클래스이지만, 로직에 노출되는 클래스는
엔티티 클래스로 제한됩니다. 그리고 로직은 엔티티 클래스를 상속 받거나 혹은 이 역시도
불필요한 구성이므로 오직 Unique ID 를 통해 [연결] [종료] [수신] [송신]에 대한 요구와 통보 처리만
노출시키도록 해도 되겠지요.
이와 같은 설계로 서버 엔진을 개발하면서 느낀 장/단점을 정리해보면 다음과 같습니다.
장점
1. 서버 엔진과 로직 간의 의존관계가 사라진다.
2. 서버 엔진에 참여한 세션 클래스의 기능을 불필요하게 로직에 노출하지 않게 된다.
3. 세션은 세션의 역할, 엔티티는 엔티티의 역할만을 수행하므로 역할이 분명해진다.
단점
1. 구조가 복잡해지므로 구조를 이해하기 전 까지 직관성이 떨어질 수 있다.
2. 프록시 클래스가 추가되므로 개발 자체에 대한 비용은 증가한다.
3. 디버깅이 어렵다.
여기서, 위의 단점에 대한 제 의견을 덧붙여 보겠습니다.
1. 구조가 복잡해지므로 구조를 이해하기 전 까지 직관성이 떨어질 수 있다.
<< 오히려 역할 자체가 더 분명해졌고, 또한 서버 엔진과 로직 개발 책임자가 분리된 경우라면
불필요한 기능을 노출하지 않아 오히려 업무 집중력이 향상될 수 있습니다.
2. 프록시 클래스가 추가되므로 개발 자체에 대한 비용은 증가한다.
<< 개발 시 많은 추가 개발사항이 있고 또한 잦은 변동사항이 있는 경우에 개발의 비용이
증가되는 구조는 좋지 않은 선택일 것입니다.
하지만, 서버 엔진은 초기 개발 이 후에는 많은 변동사항이 발생하지 않으며
특별히 요구되는 추가 개발 사항도 비교적 많이 발생하지 않습니다.
무엇보다도 안정성이 생명인 서버 엔진을 개발하는 것이므로, 개발 비용이 증가하더라도
프록시를 구성하여 역할 자체에 대한 제한을 크게 두어 의존성을 분리하는 것이
더 나은 선택이라 생각합니다.
3. 디버깅이 어렵다.
<< 클래스가 많아지면서 디버깅 자체가 불리한 것은 사실입니다.
하지만 역할 자체에 대해서 분리된 구조이므로, 각각의 역할에 대해서 확실한 테스트가
이루어진 다음이라면 오히려 그 기초적인 테스트 기반이 있으므로 분리된 역할에 대한
테스트에 집중할 수 있다는 장점이 있습니다. 또한 분리되어 구현된 경우 테스트가된 부분은
재활용할 수 있다는 장점 또한 말할 수 있을 것입니다.
# by | 2008/11/09 02:12 | 죽은 프로그래머의 사회 | 트랙백 | 덧글(1)





☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
http://www.nettention.co.kr
(광고글입니다. 실례되면 자진삭제하겠습니다.)