Go 런타임 #

Nakama 서버를 통해 Go에 작성된 신뢰할 수 있는 게임 서버 코드를 실행하여 구매, 일간 보상 등과 같은 민감한 코드를 클라이언트 실행과 분리할 수 있습니다.

Go를 사용하여 게임 서버 사용자 지정 로직을 작성하면 Go 런타임 코드를 통해 서버와 서버 환경 전체에 액세스할 수 있습니다.

Go에서 서버 런타임 코드를 작성할 경우, 종속성 버전 불일치와 해결 방법에 대한 공통적인 문제를 다루는 사후조치 관련 문서를 참조하십시오.

필수 조건 #

Nakama Go 서버 런타임을 사용하려면 해당 도구를 설치해야 합니다:

Nakama 공통 버전 #

프로젝트의 go.mod 파일이 Nakama 릴리스에 올바른 Nakama 공통 버전을 참조하는지 확인합니다:

Nakama VersionNakama Common Version
3.22.01.32.0
3.21.11.31.0
3.21.01.31.0
3.20.11.30.1
3.20.01.30.1
3.19.01.30.0
3.18.01.29.0
3.17.11.28.1
3.17.01.28.0
3.16.01.27.0
3.15.01.26.0
3.14.01.25.0
3.13.11.24.0
3.12.01.23.0
3.11.01.22.0
3.10.01.21.0
3.9.01.20.0
3.8.01.19.0
3.7.01.18.0
3.6.01.17.0
3.5.01.16.0
3.4.01.15.0
3.3.01.14.0
3.2.11.13.1
3.2.01.13.0
3.1.21.12.1
3.1.11.12.1
3.1.01.12.0
3.0.01.11.0

프로젝트 초기화 #

이 단계에서는 워크스페이스를 설정하여 모든 프로젝트 코드가 게임 서버에서 실행될 수 있도록 작성합니다.

프로젝트에서 워크스페이스로 사용하는 폴더 이름을 정의합니다.

1
2
mkdir go-project
cd go-project

Go를 사용하여 유효한 Go 모듈 경로를 제공하는 프로젝트를 초기화하고 Nakama 런타임 패키지를 설치합니다.

1
2
go mod init example.com/go-project
go get github.com/heroiclabs/nakama-common/runtime

개발 코드 #

모든 코드는 게임 서버 시작 시 글로벌 범위에서 찾는 함수에서 실행을 시작해야 합니다. 이 함수는 InitModule으(로) 불리며 RPC, 사전/사후 후크를 등록하는 방법이고, 다른 이벤트 함수는 서버에 의해서 관리됩니다.

아래의 코드는 Logger을(를) 사용하여 메시지를 작성하는 Hello World에 대한 간단한 예시입니다. 소스 파일 main.go의 이름을 지정합니다. 즐겨찾기 편집기 또는 IDE에 작성할 수 있습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import (
    "context"
    "database/sql"
    "github.com/heroiclabs/nakama-common/runtime"
)

func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error {
    logger.Info("Hello World!")
    return nil
}

이 코드를 추가한 상태에서 단자/명령 프롬프트로 이동하여 Go 패키지 종속성을 판매하기 위해서 다음 명령을 실행합니다.

1
go mod vendor

Go 패키지 종속성을 판매하면 프로젝트의 vendor/ 폴더와 go.sum 파일에 복사본이 생깁니다. 두 항목 모두 소스 제어 리포지토리에서 확인해야 합니다.

다음으로, local.yml Nakama 서버 구성 파일을 추가합니다. 사용 가능한 구성 옵션에 대한 세부 내용을 확인합니다.

1
2
logger:
    level: DEBUG

오류 처리 #

Go 함수는 일반적으로 오류 발생 시 오류값을 반환합니다. 사용자 지정 함수 또는 런타임에서 제공된 항목에서 발생한 오류를 처리하기 위해서 오류 반환값을 검사해야 합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func willError() (string, error) {
	return "", errors.New("i'm an error")
}

response, err := willError()

// Handle error.
if err != nil {
  logger.Error("an error occurred: %v", err)
}

이 패턴을 사용하여 오류 처리 및 검사에 대한 모든 런타임 API 호출을 적용하는 것이 좋습니다.

1
2
3
4
5
// Will throw an error because this function expects a valid user ID.
account, err := nk.AccountGetId(ctx, "invalid_id")
if err != nil {
  logger.Error("account not found: %v", err)
}

클라이언트로 오류 반환 #

자체적으로 사용자 지정 런타임 코드를 작성하는 경우, 요청을 처리하는 과정에서 발생하는 오류가 클라이언트로 적절하게 전달되는지 확인해야 합니다. 클라이언트로 반환되는 오류에 명확한 정보를 전달하는 오류 메시지와 정확한 HTTP 상태 코드가 포함되어야 한다는 의미입니다.

내부적으로 Nakama 런타임은 클라이언트로 오류를 반환할 때 gRPC 오류 코드를 사용하여 정확한 HTTP 상태 코드로 전환합니다.

아래와 같이 gRPC 오류 코드를 Go 모듈에서 상수로 정의할 수 있습니다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
const (
	OK                  = 0
	CANCELED            = 1
	UNKNOWN             = 2
	INVALID_ARGUMENT    = 3
	DEADLINE_EXCEEDED   = 4
	NOT_FOUND           = 5
	ALREADY_EXISTS      = 6
	PERMISSION_DENIED   = 7
	RESOURCE_EXHAUSTED  = 8
	FAILED_PRECONDITION = 9
	ABORTED             = 10
	OUT_OF_RANGE        = 11
	UNIMPLEMENTED       = 12
	INTERNAL            = 13
	UNAVAILABLE         = 14
	DATA_LOSS           = 15
	UNAUTHENTICATED     = 16
)

오류 코드 상수를 정의한 다음, runtime.NewError("error message", GRPC_CODE) 함수를 사용하여 error 개체를 정의할 수 있습니다. 다음은 모듈에서 정의할 수 있는 오류의 예시입니다.

1
2
3
4
5
6
7
8
var (
	errBadInput           = runtime.NewError("input contained invalid data", INVALID_ARGUMENT)
	errInternalError      = runtime.NewError("internal server error", INTERNAL)
	errGuildAlreadyExists = runtime.NewError("guild name is in use", ALREADY_EXISTS)
	errFullGuild          = runtime.NewError("guild is full", RESOURCE_EXHAUSTED)
	errNotAllowed         = runtime.NewError("operation not allowed", PERMISSION_DENIED)
	errNoGuildFound       = runtime.NewError("guild not found", NOT_FOUND)
)

아래는 RPC 호출과 후크 이전에서 정확한 오류를 반환하는 방법에 대한 예시입니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
func CreateGuildRpc(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
	// ... check if a guild already exists and set value of `alreadyExists` accordingly
	var alreadyExists bool = true

	if alreadyExists {
		return "", errGuildAlreadyExists
	}

	return "", nil
}

func BeforeAuthenticateCustom(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, in *api.AuthenticateCustomRequest) (*api.AuthenticateCustomRequest, error) {
  // Only match custom Id in the format "cid-000000"
  pattern := regexp.MustCompile("^cid-([0-9]{6})$")

  if !pattern.MatchString(in.Account.Id) {
    return nil, errBadInput
  }

  return in, nil
}

Go에서 공유되는 개체 빌드하기 #

Nakama 서버에서 사용자 지정 로직을 사용하려면 공유되는 개체로 컴파일해야 합니다.

1
go build --trimpath --mod=vendor --buildmode=plugin -o ./backend.so

Windows를 사용하는 경우, 현재 Go 플러그인 빌드를 지원하지 않기 때문에 이 명령을 실행할 수 없습니다. 아래의 Dockerfile 예시를 통해 Docker를 사용하여 서버를 실행할 수 있습니다.

아래와 같이 Docker 메서드를 통해 Nakama 서버를 실행할 경우, Dockerfile이(가) 사용되기 때문에 Go에서 공유되는 개체를 별도로 빌드해야 합니다.

제한 사항 #

호환성 #

Go 런타임 코드는 전체 범위의 표준 라이브러리 함수 및 패키지를 사용할 수 있습니다.

Go 런타임을 사용할 수 있는 기능은 Nakama 릴리스가 컴파일되는 Go의 버전에 의존합니다. 일반적으로, 출시된 시점에 가장 안정적인 버전을 사용합니다. Nakama 설치에 사용되는 정확한 Go 버전에 대한 서버 시작 로그를 확인합니다.

단일 스레드 #

런타임 코드에서 다중 스레드 처리(goroutines)는 다중 노드 환경에서 실행이 어렵기 때문에 권장되지 않습니다.

글로벌 상태 #

Go 런타임은 글로벌 변수를 사용하여 메모리에 상태를 저장하고 필요 시 데이터를 저장하고 공유할 수 있지만, 동시성과 액세스 제어는 개발자의 책임입니다.

상태 공유는 다중 노드 환경에서 지원되지 않기 때문에 런타임 코드에서 사용하지 않는 것이 좋습니다.

샌드박싱 #

런타임 코드에서 Go를 사용할 경우 샌드박싱이 없습니다. Go 런타임 코드를 사용하면 서버와 서버 환경에 전체 하위 수준 액세스가 제공됩니다.

이렇게 하면 완전한 유연성과 제어를 통해 강력한 기능을 포함하고 높은 성능을 제공하지만, 오류에 대한 안전성은 보장되지 않습니다. 서버는 Go 런타임 코드에서 부분 오류 또는 포인터 참조 해제 실패와 같은 치명적인 오류를 방지하지 않습니다.

프로젝트 실행 #

Docker 사용 #

서버를 로컬로 실행할 수 있는 가장 쉬운 방법은 Docker를 사용하는 것입니다. Nakama에서 Go 모듈을 사용하려면 Nakama 바이너리를 컴파일할 때 사용한 것과 동일한 버전의 Go를 사용해야 합니다. 이를 보장하기 위해서 아래와 같이 동일한 버전의 nakama-pluginbuilder 태그와 nakama 이미지를 사용하면 됩니다.

Dockerfile(이)라는 파일을 생성합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
FROM heroiclabs/nakama-pluginbuilder:3.22.0 AS go-builder

ENV GO111MODULE on
ENV CGO_ENABLED 1

WORKDIR /backend

COPY go.mod .
COPY main.go .
COPY vendor/ vendor/

RUN go build --trimpath --mod=vendor --buildmode=plugin -o ./backend.so

FROM registry.heroiclabs.com/heroiclabs/nakama:3.22.0

COPY --from=go-builder /backend/backend.so /nakama/data/modules/
COPY local.yml /nakama/data/

다음으로, docker-compose.yml 파일을 생성합니다. 자세한 내용은 Docker Compose로 Nakama 설치 문서를 참조하십시오.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
version: '3'
services:
  postgres:
    command: postgres -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all
    environment:
      - POSTGRES_DB=nakama
      - POSTGRES_PASSWORD=localdb
    expose:
      - "8080"
      - "5432"
    image: postgres:12.2-alpine
    ports:
      - "5432:5432"
      - "8080:8080"
    volumes:
      - data:/var/lib/postgresql/data

  nakama:
    build: .
    depends_on:
      - postgres
    entrypoint:
      - "/bin/sh"
      - "-ecx"
      - >
        /nakama/nakama migrate up --database.address postgres:localdb@postgres:5432/nakama &&
        exec /nakama/nakama --config /nakama/data/local.yml --database.address postgres:localdb@postgres:5432/nakama        
    expose:
      - "7349"
      - "7350"
      - "7351"
    healthcheck:
      test: ["CMD", "/nakama/nakama", "healthcheck"]
      interval: 10s
      timeout: 5s
      retries: 5
    links:
      - "postgres:db"
    ports:
      - "7349:7349"
      - "7350:7350"
      - "7351:7351"
    restart: unless-stopped

volumes:
  data:

이제 명령을 사용하여 서버를 실행합니다.

1
docker compose up --build

Docker 사용하지 않음 #

Linux, Windows, macOS에 대한 Nakama 바이너리 스택을 설치합니다. 이 작업을 완료하고 나면 게임 서버를 실행하여 코드를 로드할 수 있습니다:

1
nakama --config local.yml --database.address <DATABASE ADDRESS>

서버가 실행 중인지 확인 #

서버 로그는 위에서 작성한 코드가 시작 시 로드되고 실행되었는지 나타내는 출력이나 유사한 항목으로 표시됩니다.

1
2
3
4
5
6
7
{
  "level": "info",
  "ts": "....",
  "caller": "go-project/main.go:10",
  "msg": "Hello World!",
  "runtime": "go"
}

다음 단계 #

다음의 Nakama 기능을 포함하는 Nakama 프로젝트 템플릿을 살펴보십시오:

Related Pages