# モジュール構造

> Understand the structure of a Cloud Code module project and how it exposes endpoints.

Cloud Code モジュールは、ゲームクライアントが呼び出すことのできるエンドポイントを公開する単純な .NET プロジェクトです。モジュールは、同様のパラダイムを .NET クラスライブラリとして採用します。例えば、標準の C# クラスは以下のようになる場合があります。

```csharp
namespace ModulesExample;

public class Example
{
    private static string[] GetCheatCodes()
    {
        return new string[]
        {
            "LEAVEMEALONE",
            "ASPIRINE",
            "BIGBANG"
        };
    }

    public void ChangeHaircut(string haircutName)
    {

    }
}
```

コンパイルされると、クラス `Example` への参照は `ChangeHaircut()` メソッドのみを使用できます。

```csharp
using ModulesExample;

class UsageExample
{
    public void GameLoop()
    {
        Example.ChangeHaircut("buzzcut");

        // Compile error "Cannot access private method 'GetCheatCodes()' here"
        var cheatCodes = Example.GetCheatCodes();
    }
}
```

これにより、開発者はアクセスモディファイアを通じてライブラリのインターフェースを選択できます。Cloud Code モジュールは同じ方法で動作しますが、代わりに `[CloudCodeFunction("ChangeHaircut")]` 属性を使用します。

Cloud Code モジュールでは、自己包含サーバー主導型ライブラリを作成できます。これにより、サーバーでゲームロジックを実行し、それをクライアントから呼び出すことができるため、ゲームロジックがクライアントからデタッチされ、セキュリティが向上します。モジュールは、コードの再利用を提供し、ゲームロジックの管理に使用できるインターフェースを提供します。

## C# プロジェクト##c-project

Cloud Code モジュールプロジェクトは、クラスライブラリ C# プロジェクトです。プロジェクトは、ソリューションの一部にすることもできます。最も単純なプロジェクト構造は以下のようになります。

```text
├─ Main.csproj
    └─ Dependencies
    └─ HelloWorld.cs
```

または、ソリューション内の場合は、以下のようになります。

```text
Sample.sln
├─ Main.csproj
    └─ Dependencies
    └─ HelloWorld.cs
```

ソリューションには、相互に参照する複数のプロジェクトを入れることもできます。これにより、テストプロジェクトを簡単に追加でき、コードの再利用が可能になります。以下の例は、有効なプロジェクト構造を示します。

```text
Sample.sln
├─ Main.csproj
    └─ Dependencies
    └─ HelloWorld.cs
├─ Tests.csproj
    └─ Dependencies
    └─ Tests.cs
├─ Other.csproj
    └─ Dependencies
    └─ Class1.cs
```

ただし、Cloud Code 関数を入れることができるのはメインプロジェクトだけです。

Cloud Code モジュールの単体テストの記述方法については、[単体テストの作成](./unit-testing.md) を参照してください。

適用されるストレージ制限を確認するには、[Cloud Code 制限](../reference/limits) を参照してください。

### メインプロジェクト##main-project

ソリューションには複数の C# プロジェクトを追加できますが、メインプロジェクトはモジュールごとに 1 つだけです。メインプロジェクトには、すべてのモジュールエントリーポイント (`CloudCodeFunction` 属性がある関数) が含まれます。

> **Warning:**
>
> **重要**: メインプロジェクトの外部で任意の関数に `CloudCodeFunction` 属性を設定した場合、Cloud Code サービスはそれらをエントリーポイントとして認識できません。

メインプロジェクトは、Cloud Code サービスにデプロイするプロジェクトでもあります。メインプロジェクトで参照されていないプロジェクトと依存関係は Cloud Code サービスにデプロイしません。

メインプロジェクトの名前は、Cloud Code サービスにデプロイするアーカイブの名前と一致する必要があります。

> **Note:**
>
> **ノート**: 複数のメイン Cloud Code モジュールプロジェクトを含むソリューションを作成できます。この場合、各メインプロジェクトは異なるモジュールに対応します。

### サンプルプロジェクト##sample-project

最も単純なモジュールプロジェクトは、以下の構造を持ちます。

```text
Sample.sln
├─ Main.csproj
    └─ Dependencies
    └─ HelloWorld.cs
```

以下のサンプルは、文字列 `Hello World!` を返す単一の関数 `Hello` で Cloud Code モジュールを定義します。

```csharp
using Unity.Services.CloudCode.Core;

namespace Sample;

public class HelloWorld
{
    [CloudCodeFunction("Hello")]
    public string PrintHello()
    {
        return "Hello World!";
    }
}
```

## Cloud Code NuGet パッケージ##cloud-code-nuget-packages

.NET プロジェクトとして、Cloud Code モジュールプロジェクトはパッケージ化されたコードを共有するために NuGet も使用します。

NuGet の詳細については、[Microsoft のドキュメント](https://learn.microsoft.com/en-us/nuget/what-is-nuget) を参照してください。

Cloud Code は、モジュールに追加できる 2 つのパッケージを提供します。

* `com.Unity.Services.CloudCode.Core`: このパッケージには、`CloudCodeFunction` 属性など、Cloud Code のコア機能が含まれています。すべての Cloud Code モジュールに、このパッケージが必要です。
* `com.Unity.Services.CloudCode.Apis`: これは、Cloud Code サービスを操作するために使用できる Unity Gaming Services API を含む任意のパッケージです。このパッケージを使用して、Cloud Save などの他の Cloud Code SDK を呼び出すことができます。

詳細については、[Jetbrains Rider](https://www.jetbrains.com/help/rider/Using_NuGet.html) または [Visual Studio](https://learn.microsoft.com/en-us/nuget/install-nuget-client-tools) でプロジェクト内の NuGet パッケージをインストールして使用する方法に関するドキュメントを参照してください。

> **Note:**
>
> **ノート**: モジュールプロジェクトで使用する外部 NuGet パッケージをインストールすることもできます。詳細については、[外部サービスとのインテグレーション](./external-services-integration.md) を参照してください。

## コード構造##code-structure

コード構造の問題を回避するには、以下のガイドラインに従います。

### CloudCodeFunction 属性##cloudcodefunction-attribute

メソッドを Cloud Code 関数としてマークし、実行可能な Cloud Code モジュールエンドポイントにするには、`CloudCodeFunction` 属性を使用します。Cloud Code 関数属性は、モジュールのメインクラスでのみ使用できます。

関数を呼び出すには、属性の名前を呼び出す必要があります。

### メソッドシグネチャ##method-signature

モジュール関数のメソッドシグネチャには以下の構造体を使用します。

```csharp
[CloudCodeFunction("FunctionName")]
public async Task<ReturnType> FunctionName(IGameApiClient gameApiClient, IExecutionContext ctx, string parameter1, string parameter2)
{
    ...
}
```

> **Warning:**
>
> **重要**:`async void` パターンはエラーの原因になります。常に `Task` または `Task<T>` を返します。非同期プログラミングのベストプラクティスについては、[Microsoft の公式ドキュメント](https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming) を参照してください。

> **Warning:**
>
> **重要**: Cloud Code は、Cloud Code 関数の静的メソッドをサポートしていません。

### モジュールでのシングルトンパターンの使用##use-singleton-pattern-for-modules

シングルトンパターンを使用して、Cloud Code C# パッケージと外部サービス呼び出しを統合できます。シングルトンパターンは、Cloud Code のパフォーマンスとスケーラビリティを改善し、同じオブジェクトの複数のインスタンスを作成しないようにします。

[依存性注入](./initialize-modules/dependency-injection.md) を使用して、モジュールに `IGameApiClient` および `IPushClient` インターフェースを挿入します。

> **Warning:**
>
> **重要**: 複数のワーカーが存在する場合、API 呼び出しが同じワーカー対象にする保証はないため、モジュール関数呼び出し間の共有状態に依存しないでください。

> **Warning:**
>
> **重要**: Cloud Code ワーカーには 256 MB のメモリ制限があります。この制限を超えたワーカーは、最終的にクラッシュします。

## パラメーターを渡す##passing-in-parameters

モジュール関数にパラメーターを渡すことができます。

### プリミティブな型と複雑な型##primitive-and-complex-types

これには、すべてのプリミティブな型 (`int`、`string`、`bool`、`float` など) と複雑な型 (`List<T>`、`Dictionary<T>`、`Dictionary<string, T>` など) が含まれます。

例えば、モジュール関数に `string` と `List<string>` を渡すことができます。

```json
{
  "params": {
    "parameter1": "a",
    "parameter2": ["b", "c"]
  }
}
```

同じパラメーターを使用してモジュール関数を定義します。

```csharp
[CloudCodeFunction("SimpleParams")]
public async Task SimpleParams(IExecutionContext ctx, string parameter1, List<string> parameter2)
{
  return parameter1 + parameter2[0] + parameter2[1];
}
```

### カスタム型##custom-types

モジュールプロジェクトで定義したカスタム型を渡すこともできます。

例えば、JSON オブジェクトをモジュールオブジェクトに渡すことができます。

```json
{
  "params": {
    "player": {
      "name": "player",
      "health": 50,
      "stamina": 20,
      "inventory": {
        "count": 5,
        "weight": 20
      }
    }
  }
}
```

モジュールプロジェクトに `Player` クラスと `Inventory` クラスおよび関数を定義します。

```csharp
public class Player
{
    public string Name { get; set; }
    public int Health { get; set; }
    public int Stamina { get; set; }

    public Inventory Inventory { get; set; }
}

public class Inventory
{
    public int Count { get; set; }
    public int Weight { get; set; }
}

[CloudCodeFunction("CustomParams")]
public async Task<string> CustomParams(IExecutionContext ctx, Player player)
{
    return $"{player.Name} has {player.Health} health and {player.Stamina} stamina and {player.Inventory.Count} items in their inventory with a total weight of {player.Inventory.Weight}";
}
```

## 使用可能なインターフェース##available-interfaces

Cloud Code モジュールは、`IGameApiClient`、`IAdminApiClient`、`IPushClient` および `IExecutionContext` インターフェースを使用して、モジュールの機能を拡張できます。

`ICloudCodeSetup` インターフェースを使用して、モジュールの依存関係を管理できます。これについては、[依存性注入](./initialize-modules/dependency-injection.md) のドキュメントを参照してください。

### IGameApiClient インターフェース##igameapiclient-interface

`IGameApiClient` インターフェースは、Cloud Code C# Client SDK にアクセスする方法を提供します。[依存性注入](./initialize-modules/dependency-injection.md) を使用して、モジュールに `IGameApiClient` を挿入します。

1. `ICloudCodeSetup` インターフェースを実装するクラスを定義し、`GameApiClient` をシングルトンとして追加します。
   ```csharp
    public class ModuleConfig : ICloudCodeSetup
    {
        public void Setup(ICloudCodeConfig config)
        {
            config.Dependencies.AddSingleton(GameApiClient.Create());
        }
    }
   ```
2. `IGameApiClient` インターフェースを関数パラメーターとしてモジュール関数に渡します。

### IAdminApiClient インターフェース##iadminapiclient-interface

`IAdminApiClient` インターフェースは、Cloud Code C# Admin SDK にアクセスする方法を提供します。[依存性注入](./initialize-modules/dependency-injection.md) を使用して、モジュールに `IAdminApiClient` を挿入します。

1. `ICloudCodeSetup` インターフェースを実装するクラスを定義し、`AdminApiClient` をシングルトンとして追加します。
   ```csharp
    public class ModuleConfig : ICloudCodeSetup
    {
        public void Setup(ICloudCodeConfig config)
        {
            config.Dependencies.AddSingleton(AdminApiClient.Create());
        }
    }
   ```
2. `IAdminApiClient` インターフェースを関数パラメーターとしてモジュール関数に渡します。

### IPushClient インターフェース##ipushclient-interface

`IPushClient` インターフェースは、プレイヤーにプッシュ通知を送信する方法を提供します。[依存性注入](./initialize-modules/dependency-injection.md) を使用して、モジュールに `IPushClient` を挿入します。

1. `ICloudCodeSetup` インターフェースを実装するクラスを定義し、`PushClient` をシングルトンとして追加します。
   ```csharp
    public class ModuleConfig : ICloudCodeSetup
    {
        public void Setup(ICloudCodeConfig config)
        {
            config.Dependencies.AddSingleton(PushClient.Create());
        }
    }
   ```
2. `PushClient` オブジェクトを関数パラメーターとしてモジュール関数に渡します。

Cloud Code からプッシュメッセージを送信する方法の詳細については、[プッシュメッセージ](./push-messages.md) を参照してください。

### IExecution コンテキスト##iexecution-context

モジュール内の他の UGS サービスを呼び出すには、`IExecutionContext` インターフェースを使用して、現在の呼び出しに関する追加の情報を取得できます。以下の値を使用して、[サービス SDK](./unity-services-integration.md) にアクセスし、どのプレイヤーがリクエストを行うかを確認できます。

* `ProjectId`: 呼び出し元が認証されたプロジェクト ID。
* `PlayerId`: スクリプトを実行したプレイヤー ID。**ノート**: サービスがスクリプトを呼び出す場合、プレイヤー ID は使用できません。
* `EnvironmentId`: モジュールの環境 ID。
* `EnvironmentName`: モジュールの環境名。
* `AccessToken`: プレイヤーが Cloud Code を認証するために使用した JSON Web Token (JWT) 認証情報。
* `UserId`: サービスアカウントのユーザー ID。(ノート: サービスがスクリプトを呼び出す場合、ユーザー ID は使用できません。)
* `Issuer`: 発行者またはサービスアカウントトークン。(ノート: プレイヤーがスクリプトを呼び出す場合、発行者は使用できません。) 例えば、Multiplay が呼び出す場合、発行者は `multiplay` です。
* `ServiceToken`: Cloud Code サービスアカウント JWT 認証情報。
* `AnalyticsUserId`: プレイヤーの Analytics ユーザー ID。(ノート: サービスがスクリプトを呼び出す場合、分析ユーザー ID は使用できません。)
* `UnityInstallationId`: プレイヤーの Unity デバイスインストール ID。(ノート: サービスがスクリプトを呼び出す場合、Unity インストール ID は使用できません。)

実行コンテキストを使用してプレイヤー ID を取得し、異なる Unity Gaming Services を認証できます。クラスのコンストラクタと、`CloudCodeFunction` 属性を指定したメソッドの両方を通じて、実行コンテキストを取得できます。

例えば、実行コンテキストを使用して Cloud Save を呼び出し、プレイヤーキーを取得できます。

```csharp
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Unity.Services.CloudCode.Apis;
using Unity.Services.CloudCode.Core;
using Unity.Services.CloudCode.Shared;

namespace Sample;

public class HelloWorld
{
    private static ILogger<HelloWorld> _logger;
    public CloudSaveSdkSample(ILogger<HelloWorld> logger)
    {
        _logger = logger;
    }

    [CloudCodeFunction("GetData")]
    public async Task<object> GetData(IExecutionContext context, IGameApiClient gameApiClient, string key)
    {
        try
        {
            var result = await gameApiClient.CloudSaveData.GetItemsAsync(context, context.AccessToken,
                context.ProjectId, context.PlayerId, new List<string> { key });
            return result.Data.Results.First().Value;
        }
        catch (ApiException ex)
        {
            _logger.LogError("Failed to get data from Cloud Save. Error: {Error}", ex.Message);
            throw new Exception($"Failed to get data for playerId {ctx.PlayerId}. Error: {e.Message}");
        }
    }

    public class ModuleConfig : ICloudCodeSetup
    {
        public void Setup(ICloudCodeConfig config)
        {
            config.Dependencies.AddSingleton(GameApiClient.Create());
        }
    }


}
```

#### トークン認証##token-authentication

`AccessToken` と `ServiceToken` は `IExecutionContext` インターフェースの [JWT](https://jwt.io/) とプロパティです。

これらのトークンを使用して、Cloud Code から他の Unity Gaming Services の呼び出しを認証できます。

| トークンタイプ        | 発生元                                                                    | データアクセス          | 使用方法                                                                                             |
| -------------- | ---------------------------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------ |
| `accessToken`  | [Authentication サービス](/authentication/use-anon-sign-in.md) によって生成されます。 | 認証されたプレイヤーのみ。    | `AccessToken` は、Cloud Code 呼び出しを認証するために使用する JWT です。他の UGS サービスにトークンを渡して、認証されたプレイヤーのデータにアクセスできます。 |
| `serviceToken` | Cloud Code によって生成されます。                                                 | クロスプレイヤーデータアクセス。 | `ServiceToken` は、他の UGS サービスを呼び出し、クロスプレイヤーデータを操作するために使用するトークンです。                                 |

設定する [アクセス制御](./access-control.md) ルールは `AccessToken` に影響します。

以下のサンプルは、`AccessToken` を使用して、Cloud Save で認証されたプレイヤーのデータにアクセスする方法を示します。

```csharp
using Microsoft.Extensions.DependencyInjection;
using Unity.Services.CloudCode.Apis;
using Unity.Services.CloudCode.Core;
using Unity.Services.CloudCode.Shared;
using Unity.Services.CloudSave.Model;

namespace TokenSample;

public class TokenSample
{
    [CloudCodeFunction("GetKeysForAuthenticatedPlayer")]
    public  Task<ApiResponse<GetKeysResponse>> GetPlayerKeys(IExecutionContext ctx, IGameApiClient gameApiClient)
    {
        try
        {
            // Using ctx.AccessToken to access the data for the player calling the function
            return gameApiClient.CloudSaveData.GetKeysAsync(ctx, ctx.AccessToken, ctx.ProjectId, ctx.PlayerId);
        }
        catch (ApiException e)
        {
            throw new Exception($"Failed to get keys for playerId {ctx.PlayerId}. Error: {e.Message}");

        }
    }

    public class ModuleConfig : ICloudCodeSetup
    {
        public void Setup(ICloudCodeConfig config)
        {
            config.Dependencies.AddSingleton(GameApiClient.Create());
        }
    }
}
```

すべてのプレイヤーのデータにアクセスするには、`ServiceToken` を使用し、`playerId` のパラメーターを渡すことができます。

```csharp
using Microsoft.Extensions.DependencyInjection;
using Unity.Services.CloudCode.Apis;
using Unity.Services.CloudCode.Core;
using Unity.Services.CloudCode.Shared;
using Unity.Services.CloudSave.Model;

namespace TokenSample;

public class TokenSample
{
    [CloudCodeFunction("GetKeysForAnyPlayer")]
    public  Task<ApiResponse<GetKeysResponse>> GetPlayerKeys(IExecutionContext ctx, IGameApiClient gameApiClient, string playerId)
    {
        try
        {
            // Using ctx.ServiceToken to access the data for any player
            return gameApiClient.CloudSaveData.GetKeysAsync(ctx, ctx.ServiceToken, ctx.ProjectId, playerId);
        } catch (ApiException e)
        {
            throw new Exception($"Failed to get keys for playerId {ctx.PlayerId}. Error: {e.Message}");
        }
    }

    public class ModuleConfig : ICloudCodeSetup
    {
        public void Setup(ICloudCodeConfig config)
        {
            config.Dependencies.AddSingleton(GameApiClient.Create());
        }
    }
}
```

2 つのトークンの違いの詳細な説明は、[サービスとアクセストークン](./token-support.md) のドキュメントを参照してください。

## Cloud Code モジュールメタデータ##cloud-code-module-metadata

Cloud Code モジュールとそのメタデータを取得するには、Cloud Code サービスに `GET` リクエストを送信します。レスポンスは、以下の構造を持つ JSON オブジェクトです。

```json
{
  "name": "string",
  "language": "CS",
  "tags": {
    "property1": "string",
    "property2": "string"
  },
  "signedDownloadURL": "string",
  "dateCreated": "2022-04-05T09:12:13Z",
  "dateModified": "2022-04-05T09:12:13Z"
}
```

### タグ##tags

タグは、Cloud Code モジュールにアタッチできるキーと値のペアです。モジュールのバージョン、作成者、バージョン管理を使用する場合のコミットハッシュなど、必要な任意の情報をタグに記録できます。

モジュールでは最大 10 個のタグを格納でき、タグキーとタグ値ごとに使用できる最大文字数は 128 です。

> **Note:**
>
> **ノート**: カスタムタグは、オーサリングに [Cloud Code API](./run-modules/rest-api.md) を使用する場合のみサポートされます。オーサリングに [Unity Gaming Services CLI](./write-modules/cli.md) や [Unity エディター](./write-modules/unity-editor.md) を使用する場合、タグは Cloud Code で無視されます。

### 署名付きダウンロード URL##signed-download-url

署名付きダウンロード URL は、モジュールアーカイブをダウンロードするために使用できる一時的な URL です。
