# 원격 구성

**URL:** https://heroiclabs.com/docs/kr/nakama/guides/deployment/remote-configuration/
**Summary:** 원격 구성을 통해 인앱 매개변수를 관리하고 가져오는 방법을 보여주는 튜토리얼입니다. 개발자는 원격 구성을 사용하여 긴 검토 프로세스나 게임 또는 앱을 수정한 다음 사용자의 업데이트를 대기하는 번거로움을 없앨 수 있습니다. 이는 모바일 프로젝트에서 특히 유용합니다.

---


# 원격 구성

원격 구성은 원격 서버에 저장된 인앱 매개변수를 사용하여 앱 또는 게임의 동작을 사용자 지정하는 방법입니다. 이것은 다음 구현에 사용할 수 있습니다 <a href="https://en.wikipedia.org/wiki/Feature_toggle" target="\_blank">기능 플래그</a> 또는 앱 또는 게임의 모양이나 동작을 변경하는 설정을 조정합니다.

개발자는 원격 구성을 사용하여 긴 검토 프로세스나 게임 또는 앱을 수정한 다음 사용자의 업데이트를 대기하는 번거로움을 없앨 수 있습니다. 이는 모바일 프로젝트에서 특히 유용합니다.

## 인앱 매개변수 관리

앱이나 게임으로 전송된 구성 설정은 서버에 저장해야 합니다. 정보를 저장하는 가장 좋은 방법은 데이터의 변경 빈도에 따라 다릅니다.

대부분 정적 데이터의 경우 서버 측 코드에 데이터 구조로 포함하는 것이 가장 효율적이며 더 동적인 데이터의 경우 읽기 전용 [저장소 레코드](../../../concepts/storage/collections/)를 사용하는 것이 좋습니다.

이 두 가지 접근 방식을 모두 사용하는 경우 [등록/로그인](../../../concepts/authentication/)을 완료하거나 [사용자 세션](../../../concepts/authentication/#sessions)으로 연결하기 전에 원격 구성에 액세스할 수 있습니다. 구성하는 인앱 매개변수는 애플리케이션 시작 초기에 초기화할 수 있습니다.

### 정적 매개변수

가장 간단한 접근 방식의 경우 서버 측 코드를 사용하여 인앱 매개변수를 정적 변수로 나타냅니다. 서버가 시작된 후 매개변수를 변경하려면 Lua 코드를 업데이트하고 서버를 다시 시작해야 합니다.

{{< code type="server" >}}
```lua
-- The code could be stored in a module named `"rc.lua"` and placed in the runtime path for the server.

local nk = require("nakama")

-- In-app parameters stored in a static variable.
local parameters = {
  reachable_levels = 10,
  max_player_level = 90,
  min_version = 12
}

local function remote_configuration(_context, _payload)
  return nk.json_encode({ rc = parameters })
end

nk.register_rpc(remote_configuration, "rc")
```
{{< / code >}}

{{< code type="server" >}}
```go
var parameters = map[string]interface{}{
  "reachable_levels": 10,
  "max_player_level": 90,
  "min_version":      12,
}

func RemoteConfig(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
  responseBytes, err := json.Marshal(map[string]interface{}{"rc": parameters})
  if err != nil {
    return "", err
  }

  return string(responseBytes), nil
}

// Register as RPC function, this call should be in InitModule.
if err := initializer.RegisterRpc("rc", RemoteConfig); err != nil {
  logger.Error("Unable to register: %v", err)
  return err
}
```
{{< / code >}}

{{< code type="server" >}}
```typescript
const parameters = {
  reachableLevels: 10,
  maxPlayerLevel: 90,
  minVersion: 12
};

const RemoteConfigRpc : nkruntime.RpcFunction = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string): string | void {
  return JSON.stringify(parameters);
}

// Register as RPC function, this should be in InitModule.
initializer.registerRpc('rc', RemoteConfigRpc);
```
{{< / code>}}

### Dynamic parameters

For in-app parameters which may be changed via Analytics or with a LiveOps dashboard it's more flexible to store the configuration settings in the [storage engine](../../../concepts/storage/collections/) as a read-only record.

{{< code type="server" >}}
```lua
--Same as above we'll use server-side code with a module named `"rc.lua"` and placed in the runtime path for the server.

local nk = require("nakama")

local parameters = {
  reachable_levels = 10,
  max_player_level = 90,
  min_version = 12
}

local object = {
  collection = "configuration",
  key = "rc",
  value = parameters,
  permission_read = 1,
  permission_write = 0,
  version = "*" -- Only write object if it does not already exist.
}
pcall(nk.storage_write, { object }) -- Write object, ignore errors.

local function remote_configuration(_context, _payload)
  local rc = {
    collection = object.collection,
    key = object.key
  }
  local objects = nk.storage_read({ rc })
  return objects[1].value
end

nk.register_rpc(remote_configuration, "rc")
```
{{< / code >}}

{{< code type="server" >}}
```go
const (
  configCollection = "configuration"
  configKey = "rc"
)

func SaveConfig(ctx context.Context, nk runtime.NakamaModule, logger runtime.NakamaModule) error {
  parameters := map[string]interface{}{
    "reachable_levels": 10,
    "max_player_level": 90,
    "min_version":      12,
  }

  b, err := json.Marshal(parameters)
  if err != nil {
    return err
  }

  objects := []*runtime.StorageWrite{
    &runtime.StorageWrite{
      Collection:      configCollection,
      Key:             configKey,
      Value:           string(b),
      Version:         "*", // Only write if object does not exist already.
      PermissionRead:  1,
      PermissionWrite: 0,
    },
  }

  if _, err := nk.StorageWrite(ctx, objects); err != nil {
    return err
  }
  return nil
}

func RemoteConfig(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
  objectIds := []*runtime.StorageRead{
    &runtime.StorageRead{
      Collection: configCollection,
      Key:        configKey,
    },
  }

  records, err := nk.StorageRead(ctx, objectIds)
  if err != nil {
    return "", err
  }
  if len(records) == 0 {
    return "", errors.New("No config found.")
  }
  return records[0].Value, nil
}

// Ensure the configuration object is stored, this call should be in InitModule.
SaveConfig(ctx, nk, logger)
// Register as RPC function, this call should be in InitModule.
if err := initializer.RegisterRpc("rc", RemoteConfig); err != nil {
  logger.Error("Unable to register: %v", err)
  return err
}
```
{{< / code >}}

{{< code type="server" >}}
```typescript
const configCollection = 'configuration';
const configKey = 'rc';

const SaveConfig = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama) {
  let parameters = {
    reachableLevels: 10,
    maxPlayerLevel: 90,
    minVersion: 12
  };

  const storageWrite : nkruntime.StorageWriteRequest = {
    collection: configCollection,
    key: configKey,
    value: parameters,
    userId: '00000000-0000-0000-0000-000000000000',
    version: '*', // Only write if object doesn't exist already,
    permissionRead: 1,
    permissionWrite: 0
  };

  nk.storageWrite([ storageWrite ]);
}

const RemoteConfigRpc : nkruntime.RpcFunction = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string): string | void {
  const readRequest : nkruntime.StorageReadRequest = {
    collection: configCollection,
    key: configKey,
    userId: '00000000-0000-0000-0000-000000000000'
  };
  
  const records = nk.storageRead([readRequest]);
  if (records.length == 0) {
    throw new Error('No config found')
  }
  
  return JSON.stringify(records[0].value);
}

// Ensure the configuration object is stored, this call should be in InitModule.
SaveConfig(ctx, logger, nk)

// Register as RPC function, this call should be in InitModule.
initializer.registerRpc('rc', RemoteConfigRpc);
```
{{< / code >}}


## 인앱 매개변수 가져오기

인앱 매개변수 저장에 사용하는 두 가지 접근 방식 모두에서 HTTP 요청으로 구성을 가져올 수 있습니다.

**.Net/Unity:**

```csharp
// Remember to change the host, port, and auth values for how you've setup your server
var host = "127.0.0.1";
var port = 7350;
var path = "rc";
var auth = "defaultkey";

var format = "http://{0}:{1}/v2/rpc/{2}?http_key={3}";
var url = string.Format(format, Host, Port, Path, Auth);
var headers = new Dictionary<string, string>();
headers.Add("Content-Type", "application/json");
headers.Add("Accept", "application/json");

WWW www = new WWW(url, null, headers);
yield return www;
if (!string.IsNullOrEmpty(www.error)) {
    Debug.LogErrorFormat("Error occurred: {0}", www.error);
} else {
    var response = Encoding.UTF8.GetString(www.bytes);
    Debug.Log(response);
    // output
    // {"rc":{"max_player_level":90,"min_version":12,"reachable_levels":10}}
}
```

**Curl:**

```bash
curl -X POST "http://127.0.0.1:7350/v2/rpc/rc?http_key=defaultkey" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json'
# output
# {"rc":{"max_player_level":90,"min_version":12,"reachable_levels":10}}
```
