simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

Unity:Amazon DynamoDB を C# から利用する

高速読み書きがスケーラブルなデータベース Dynamo DB を Unity から利用する

現在のルームメンバー一覧を取得したいとか、これからジョインするルームを決めるときに、ルームに何人入っているのかなどを確認したいときがあるのです。
S3 にファイルとして情報を置く?…いえいえ、ここはデータベースの出番だと思います。

今回は DynamoDB というキーと値のセットでデータを格納するタイプのデータベースを Unity から利用してデータを取得する例を示します。

前知識

前回の記事では、Amazon S3 というオンラインストレージを Unity から利用して
ファイルパスを渡してS3の指定バケットにアップロードするとオブジェクトキーを取得する機能
S3バケットのオブジェクトキーを指定するとダウンロードしたbyte配列を受け取る機能
いずれも非同期で実行する方法を示し、動作確認も行いました。
simplestar-tech.hatenablog.com

ここからは Unity から AWS を利用するための Cognito による認証・認可のハードルを越えている読者に向けて説明を続けます。
理解できずに振り落とされてしまった場合は、前回の記事を参照していただければと思います。

AWS SDK DynamoDB を使うための準備

Assets 以下に次の URL から net45 フォルダの dll を配置して、DynamoDB 関係の実装を利用できるようにします。
AWSSDK.DynamoDBv2

そして S3 のクライアントのときと同じく、Cognito の SignIn イベントにハンドラを登録して DynamoDBContext を作成します。
あとは以下のロジックを組みます。

AmazonDynamoDBClient.cs

using Amazon;
using Amazon.CognitoIdentity;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DataModel;
using Amazon.DynamoDBv2.DocumentModel;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Events;

namespace AWS
{
    public class AmazonDynamoDBClient : MonoBehaviour
    {
        #region Scene Components
        [SerializeField] AmazonCognitoSignInGUI cognitoSignInGUI;
        #endregion        

        void Start()
        {
            this.cognitoSignInGUI.onSignInSuccess += OnSignIn;            
        }

        void OnSignIn(CognitoAWSCredentials credentials)
        {
            this.dynamoDBClient = new Amazon.DynamoDBv2.AmazonDynamoDBClient(credentials, this.resourceRegion);
            this.dynamoDBContext = new DynamoDBContext(this.dynamoDBClient);
        }

        public async Task QueryAsync<T>(string partitionKey, QueryOperator queryOperator, IEnumerable<string> sortKeys, UnityAction<List<T>> onQuerySuccess)
        {
            var appQuery = dynamoDBContext.QueryAsync<T>(partitionKey, queryOperator, sortKeys);
            onQuerySuccess?.Invoke(await appQuery.GetRemainingAsync());
        }

        public async Task LoadAttributes<T>(string partitionKey, string sortKey, UnityAction<T> onLoadSuccess)
        {
            onLoadSuccess?.Invoke(await this.dynamoDBContext.LoadAsync<T>(partitionKey, sortKey));
        }

        public async Task SaveAttributes<T>(T attributes, UnityAction onSaveSuccess)
        {
            await this.dynamoDBContext.SaveAsync<T>(attributes);
            onSaveSuccess?.Invoke();
        }

        int bookID = 1001;

        RegionEndpoint resourceRegion = RegionEndpoint.APNortheast1;
        IAmazonDynamoDB dynamoDBClient = null;
        IDynamoDBContext dynamoDBContext = null;
    }
}

AmazonDynamoDBAttributes.cs

using Amazon.DynamoDBv2.DataModel;
using System.Collections.Generic;

namespace AWS.DynamoDBAttributes
{
    [DynamoDBTable("CubeWalkC1")]
    public class Rooms
    {
        [DynamoDBHashKey] public string PartitionKey { get; set; } = "Rooms";
        [DynamoDBRangeKey] public string SortKey { get; set; } = "XXX#XXX#XXXXXXX";
        [DynamoDBProperty("Players")] public List<string> DatePlayerIDs { get; set; } = new List<string>();
    }

    [DynamoDBTable("CubeWalkC1")]
    public class Players
    {
        [DynamoDBHashKey] public string PartitionKey { get; set; } = "Players";
        [DynamoDBRangeKey] public string SortKey { get; set; } = "20XX-XX-XXTXX:XX:XX#xxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
        [DynamoDBProperty] public string PlayerName { get; set; }
    }
}

Scene Component との接続は次の通り

f:id:simplestar_tech:20190602164129p:plain
Scene Component 接続

認証済みユーザーに割り当てる IAM ロールにて DynamoDB の GetItem x 特定テーブル ARN の指定を加えます。
以下のロール編集画面の通り

これけっこう重要なナレッジかな?DynamoDBContext には DescribeTable は必須みたい(ドキュメントのどこかに書かれているかな?)

f:id:simplestar_tech:20190602165421j:plain
DynamoDB のインラインポリシーの設定例

これで認証されたユーザーから CubeWalkC1 テーブルからアイテム情報を受け取ったり、配置できたりするようになります。

テスト

テストに使ったコードがこちら
TestAWSGUI.cs

using UnityEngine;
using UnityEngine.UI;
using AWS;
using AWS.DynamoDBAttributes;
using System.IO;
using Amazon.DynamoDBv2.DocumentModel;

public class TestAWSGUI : MonoBehaviour
{
    #region UI Connection
    [SerializeField] Button buttonPut;
    [SerializeField] Button buttonGet;
    #endregion

    #region Scene Components
    [SerializeField] AmazonS3Client s3Client;
    [SerializeField] AmazonDynamoDBClient dynamoDBClient;
    #endregion

    void Start()
    {
        this.buttonPut.onClick.AddListener(this.OnPut);
        this.buttonGet.onClick.AddListener(this.OnGet);
    }

    async void OnGet()
    {
        await this.s3Client.GetObjectDataAsync("ap-northeast-1:xxxxxxxxx-xxxxxx-xxxxxxx-xxxxx-xxxxxxxxxxxxxxx/Miraikomachi.vrm", (bytes) =>
        {
            File.WriteAllBytes(@"C:\Users\simpl\Downloads\VRM\MiraikomachiVRM-master\Miraikomachi2.vrm", bytes);
        });

        await this.dynamoDBClient.QueryAsync<Rooms>("Rooms", QueryOperator.BeginsWith, new string[] { "XXX" }, (list) => {
            foreach (var item in list)
            {
                foreach (var playerID in item.DatePlayerIDs)
                {
                    Debug.Log(playerID);
                }
                Debug.Log(item.SortKey);
            }
        });
    }

    async void OnPut()
    {
        await this.s3Client.PutObjectFileAsync(@"C:\Users\simpl\Downloads\VRM\MiraikomachiVRM-master\Miraikomachi.vrm", (objectKey) =>
        {
            Debug.Log(objectKey);
        });

        await this.dynamoDBClient.SaveAttributes(new Rooms { DatePlayerIDs = new System.Collections.Generic.List<string> { "player1", "player2" } }, () =>
        {
            Debug.Log("Save Rooms");
        });

        await this.dynamoDBClient.SaveAttributes(new Players { PlayerName = "player1" }, () =>
        {
            Debug.Log("Save Players");
        });
    }
}

実行すると以下のログが流れました。
期待通り、DynamoDB への書き込み、読み取りが機能している様子(クライアントからルーム情報いじれるのはセキュリティ的にどうかと考えてみたけど、ウソのジョインをいっぱい送ることも可能なのでどこで更新しても壊せるかな)

f:id:simplestar_tech:20190602170652j:plain
上記テストコードの実行結果

まとめ

DynamoDB を Unity から操作できました!