본문 바로가기

Issue

순환 참조 문제 / Go언어(Golang) 게임서버

 

순환 참조의 개념

순환 참조란 두 모듈이 서로를 직접 또는 간접적으로 참조할 때 발생한다.
이는 컴파일 타임에 해결되지 않으면 런타임 에러로 이어지며,  Go에서는 패키지 간 잘못된 의존성 구조로 인해 이런 문제가 발생한다.
( Go에서는 컴파일 타임에 아래와 같은 import cycle not allowed 와 같은 컴파일 에러를 반환하여 알려주긴 한다)

순환 참조가 야기하는 문제점

  • 컴파일 에러 : 컴파일 에러가 난다면 그나마 다행이지 않을까 싶다
  • 단위 테스트가 어렵고 비용이 증가 : 의존적인 모듈들이 함께 테스트 되어야하기 때문에 단위적인 테스트가 어렵고 비용이 증가한다
  • 가독성이 저하되고 유지보수의 어려움 증가

문제 상황 및 배경

오.. import cycle not allowed

Go언어를 이용해 한글을 타이핑하여 스킬 사용 및 공격 등을 진행하는 게임을 구성하는 데 활용될 서버를 구축하고 있었다.
통신방법은 TCP, UDP.. 팀원들과 여러번의 회의를 거치고 테스트 코드를 작성하여 테스트 해본 결과 보안성 및 안정성이 높고 프로젝트의 규모가 그다지 크지 않음을 고려하여 WebSocket을 사용하기로 했다

Client에서 연결됨과 동시에 socketserver에 존재하는 WebSocketServer 객체를 생성하고 플레이어의 위치, 좌표, 상태 등.. 여러가지 정보를 관리할 수 있도록 하였다. 그 이후 WebSocketServer 내부에서 router의 HandleRouter를 통해 router -> handler 순으로 동작이 진행된다.

최종적으로 handler에선 플레이어의 움직임 혹은 채팅 등을 처리해야 하기에 WebSocketServer 내부에 존재하는 데이터를 변경해야 했고, socketserver에 선언된 WebSocketServer의 메서드를 이용해야 했다.

 

이러한 순환구조가 구성된다

그림으로 본다면 이러한 구성이 되는 것이다.
이러한 구성이 된 이유는 아무래도 WebSocketServer 객체에서 플레이어의 데이터를 관리하기 때문이라고 생각한다.
우선 테스팅 목적도 있을 것이고, 정확히 플레이어의 데이터를 어떤 방법을 통해 관리해야될지 정하지 못했기 때문이다.
별도의 Redis와 같은 메모리를 통해 관리하는 것이 아닌, 단순히 플레이어의 연결과 동시에 객체를 생성하고 메모리 내부에서 관리하는 인-메모리 방법을 사용하였는데 이 과정에서 handler 이후 store와 같이 별도의 메모리 혹은 데이터베이스를 관리할 수 있는 객체 및 함수에서 동작해야될 로직들이 socketserver 내부에서 동작하게 되며 WebSocketServer를 다시 참조하는 순환참조가 구성되었던 것이다.

아마 별도의 저장공간을 활용했다면 위와같은 구조가 구성되었을 것이다

 

순환 참조의 해결방법

  • 패키지 구조의 재설계 혹은 통합 : 의존성이 매우 높다면 분리된 패키지들을 합쳐 구성하거나 구조를 재설계 해야한다
  • 인터페이스 활용 : 의존성 역전을 통해 패키지 순환 구조를 개선
  • 공통 기능으로 분리

순환 참조를 해결하기 위해 보통 위와 같은 방법으로 접근한다고 한다.

 

현재 프로젝트에서의 해결방안

하지만, 현재 구조 상 패키지 구조의 통합은 적절하지 않았다. router, handler 등을 모두 합하여 하나의 패키지로 구성한다면 서버가 단일 코드 내에서 동작하게 되는 것이다. 오히려 유지보수가 더욱 어려워질 것이고, 역할의 분리가 이루어지지 않는 코드가 구성된다.

 

그래서 역할을 분리하여 구성하였다. WebSocketServer 객체의 구성은 model에 정의해두고 함수들도 객체에 정의된 함수가 아닌 일반적인 함수들로 분리, 각 데이터를 관리하는 함수들은 또다시 별도의 패키지로 구성하여 패키지별로 역할을 명확하게 나누었다.

이렇게 router, handler에서 socketserver의 WebSocketServer 메서드를 직접 사용하는 것이 아닌 해당 함수들에 인자로 객체를 전달하여 최종적으로 데이터를 관리하는 별도의 패키지에서 함수가 동작할 수 있도록 하였다

아마 이후 플레이어의 정보를 관리하는 별도의 방법(Redis 등)이 결정된다면 이러한 순환참조 문제는 발생하지 않을 것 같다.

 

느낀점

대학에서 배우는 기본적인 전공지식들을 통해 새롭게 알아가는 것들이 생기며, 프로젝트를 구성하는 데 있어 발생한 문제에 대해 더욱 깊게 생각해볼 수 있던 것 같다. 단순 컴파일 혹은 런타임에서 출력되는 에러를 눈에서 지웠다고해서 해결하고 끝이 아닌, 문제의 원인 및 현 상황에 맞추어 상황을 판단해보고, 그것을 해결하기 위한 방법에는 어떤 것들이 있는지 익혀보며 배웠던 지식들을 스스로 경험해볼 수 있는 기회가 된 것 같다.