지난 글 에서는 gRPC 통신의 특징과 메세지의 역직렬화 과정에서의 .proto
파일의 역할에 간단히 다루었다. 그 외에도 protobuf 파일은 개발 영역 전반의 공통된 타입 저장소 역할을 할 수 있다. code-gen 라이브러리로 .proto
스키마에 대응하는 .d.ts
파일과 .js
파일을 생성할 수 있다. 게임 서버, 게임 클라이언트, 게임 어드민 등 게임 개발에 필요한 모든 서비스가 공통적인 스키마를 사용할 수 있기 때문에 체계적인 서비스 개발이 가능하다.
예를들어 아래와 같이 작성한 protobuf 파일은
syntax = "proto3"; // 사용될 Protocol Buffers의 버전을 지정
package user; // 패키지 이름을 정의 (생성된 코드에서 모듈/네임스페이스로 사용됨)
message User { // 'User'라는 메시지(데이터 구조)를 정의
string id = 1; // id' 필드는 string 타입이며, 필드 번호는 1 (번호는 메시지 직렬화 시 사용)
string name = 2;
int32 age = 3;
}
각 언어의 타입으로 변환을 도와주는 도구를 거치면 아래와 같이 변환된다.
export interface User {
id: string;
name: string;
age: number;
}
이처럼 타입생성의 수고로움을 덜어주는 protobuf 파일 기반의 타입생성을 게임 개발 초기 단계에서 어떻게 활용하였는지에 대한 이야기를 기록으로 남겨보려고 한다.
본격적으로 기능 개발을 위해 크런치모드가 시작되면서 곤란한 상황이 발생하였다. 당시에 서버와 클라이언트 개발자 분들은 일정을 맞추기 위해 평일 새벽이나 주말 등 시간대를 가리지 않고 업무를 진행할 정도로 바쁜 상황이었다. 그리고 많은 경우에 하나의 기능이 개발되면 어드민을 통해 다양한 케이스를 시도해보며 체크를 하는 것이 일반적이었다.
그런데 문제는 서버의 스키마가 바뀌는 경우도 자주 있었다는 것이다. 기존에 존재하던 필드가 없어지거나 이름이 수정되고, 혹은 새로운 필드가 생기는 경우도 있었다. 어드민 개발자는 나 혼자이고 서버 개발자분은 총 4분이다보 니 필요한 페이지도 많고 시간대를 가리지않고 들어오는 요청에 대응하기가 어려웠다.
gRPC는 protobuf 기반으로 메세지를 전송하기 때문에, 스키마가 바뀌면 반드시 어드민에서도 타입을 재생성해야만 했는데 여기에는 두 가지 경우가 있었다.
1번의 경우에는 따로 구현 된 페이지가 없더라도 클라이언트 개발자를 위해 유저 데이터 객체를 JSON Editor를 이용해 수정하는 페이지가 있어서 protobuf 업데이트는 반드시 필요했다. 이 경우 단순히 submodule로 관리되고 있는 protobuf에 대한 업데이트만 필요해서 비교적 간단하였다.
server 레포의 변경사항을 감지할 수 있는 방법을 찾던 중 workflow dispatch에 대해 알게되었다
workflow dispatch는 GitHub Actions을 수동으로 트리거(Trigger)할 수 있는 기능으로 이벤트가 없어도 개발자가 직접 실행하거나, 다른 저장소(A 레포)에서 API 요청으로 실행할 수 있다. 검색을 해보니 많지는 않지만 action을 수동으로 실행하는 작업을 하는 것에 대한 자료가 종종 있었다.
대부분 인자를 받아 github에서 action을 manual로 실행하는 용도로 사용하고 있었다.
하지만 내가 필요한 기능은 아래처럼 api.github으로 HTTP 요청을 통해 다른 레포에서의 작업을 트리거 하는 것이었다. 만약, repo A에서 설정해 둔 트리거가 발생한다면 workflow를 통해 repo B로 트리거를 보내게 된다. 그러면 repo B의 workflow가 실행되면서 서로 다른 repo A와 repo B가 통신을 할 수 있게 되는 것이다.
트리거를 보내는 repo에서의 작성예시는 다음과 같다. 인증 및 인가를 위한 token
, 타겟를 지정하기위한 owner
, repo
, workflow
, branch
정보가필요하다.
- name: Trigger from Repo-A
run: |
curl -X POST \
-H "Authorization:
트리거를 할 때 inputs
를 이용해 워크플로우 실행 시 필요한 입력값을 특정해 전달할 수도 있다.
run: |
--data '{"ref":"main",
트리거를 실행할 수 있는 워크플로우를 서버 레포에 넣어두고 새로운 PR이 머지되면 실행되도록하여 첫번째 문제를 해결할 수 있었다.
이미 구현 된 페이지가 있는 경우에는 마땅한 대안이 없어 어떤 식으로 해결할지 고민하던 중 PR을 생성해주는 워크플로우를 보면서 힌트를 얻게 되었다. 이미 이전의 타입을 기반으로 코드가 작성되어 있는 경우 코드는 반드시 수정할 수 밖에 없었다. 그래서 타입체크를 진행해서 오류가 있다면 PR을 생성해주는 로직으로 워크플로우를 분기처리해보았다.
이런식으로 워크플로우 내부에서 타입체크를 하고결과를 파일에 저장하면
npx tsc --noEmit > type-check.log
파일의 크기를 기준으로 에러여부를 체크할 수 있었다.
# type-check.log 파일에 내용이 있으면(타입 오류가 있으면)
if [ -s type-check.log ]; then # -s type-check.log는 파일이 존재 + 크기가 0보다 큼
# PR생성
else # 타입 오류가 없으면
# develop 브랜치에 커밋 후 push
fi
이렇게 되면 반드시 프론트엔드 엔지니어의 작업이 필요하게 되지만 알림 겸 빠른 작업을 위해 PR이 생성되면 다음날 아침에 곧바로 수정을 진행하기 수월하였다. 타입 수정의 경우 아예 새로운 형태가 아니고 대부분이 필드명 변경 정도였기 때문에 수정에 걸리는 시간은 많지 않았다.
처음에는 막막했지만 워크플로우 덕분에 수월하게 초기 개발 단계를 마무리 할 수 있었다. 이후에는 어느정도 기획이 안정되고 개발의 체계가 잡히면서 상황은 개선되었다. 킹덤 어드민을 개발하면서 나름 기술에 익숙해졌다고 생각했지만 체계적으로 스프린트 단위로 개발이 이루어지는 단계가 아니었기 때문에 해결책을 적용하는데에 어려움이 있었다. 특정적인 문제에 대한 해결을 위해서 사용하는 기술에 대한 이해도가 기반이 되고, 힌트를 얻을 수 있도록 개발 전반에 대한 지식이 중요하다는 것을 알 수 있었다.