Alexaスキル（C#）からDynamoDBにアクセスするには？（テーブル作成まで）

####2018年11月9日追記

Waitメソッドによって非同期メソッド（CreateTableAsync）の完了を待機することでDynamoDBへテーブル作成処理をちゃんと完了させるようにしました。
が、[こちらの方の非同期処理に関する記事](https://qiita.com/takutoy/items/d45aa736ced25a8158b3)を読んで、Resultプロパティを使って非同期メソッドの結果も取得した方が汎用的と思い、Waitメソッドではなく、Resultプロパティを使うように修正しました。
ありがとうございました。

####2018年11月9日追記2
CreateTableしたときに、同名のテーブルが存在すると例外が発生してしまいます。
これを回避するために、CreateTableの前にテーブルの存在確認をするようにしました。
テーブルの存在確認は、`ListTablesAsync`メソッドによってテーブルの一覧を取得し、指定した名前のテーブルが存在するかをLinqのExistsメソッドを使って確認しているだけのシンプルなものです。

#はじめに
[この記事](https://qiita.com/shuhey/items/b777117713c0c5e15a17)で、[公式Alexaチュートリアル第3回](https://developer.amazon.com/ja/blogs/alexa/post/cddf2434-42ee-422f-886d-5be9c36dba14/jp-training-chapter3)の内容をC#で実装しました。

次は第4回になるわけですが、第4回は永続的なデータの保持ということでデータをDynamoDBに保存する、という内容になっています。

そこでまずはC#からDynamoDBにアクセスする方法を学ばなければなりません。
今回はそういう内容です。

#準備
[公式Alexaチュートリアル第4回](https://developer.amazon.com/ja/blogs/alexa/post/4144a8ea-7549-4c44-a4bd-e94cb93807ea/chapter4-jp)を完了しておいてください。Node.jsでそのままやってくださってOKです。
なぜかというと、チュートリアルではSDK for Node.jsが提供している`setPersistentAttributes`メソッドを使ってDynamoDBにデータを保存しているのですが、どのような情報がどのような形で保存されるのかを確認するためです。

SDK for Node.jsのメソッドを使えばDynamoDBにデータを保存するときのあれやこれやをよろしくやってくれるので、開発者はメソッド一発でデータをDynamoDBに保存することができます。

しかし、C#の場合、それを自分でやる必要があるため、こうして確認し、そのとおりにデータを保存してみようという考えです。

#DynamoDB上でのデータの形式を確認
公式チュートリアル第4回を完了して、スキルをひととおり動かすとDynamoDBにはユーザーの星座データが保存されます。
AWSのウェブコンソールでDynamoDB内のデータを見てみましょう。
DynamoDBを選択して、左側の一覧から「テーブル」を選択すると、おそらく「HoroscopeSkillTable」かもしくはスキル名を冠したテーブルが表示されると思います。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/245438/0437d617-dc33-660a-2dd7-43041a17fbfc.png)

このテーブルをクリックして開き、右側のタブから「項目」を選択すると、以下のようにテーブルの内容が表示されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/245438/8e1c10a0-1f3a-82d9-cc36-236250d6d620.png)

`id`と`attributes`という2つの列を持つテーブルです。
 
##idって何？
このidというのはリクエストのJSONでいうところの`userId`項目になります。この`userId`は何かというと、[こちら](https://developer.amazon.com/ja/docs/custom-skills/request-and-response-json-reference.html#request-body-syntax)に説明のあるとおり、利用者のユニークなIDです。
そのスキルが有効（インストール）にされたときに割り当てられ、そのスキルがいつか無効にされるまではそのスキルの利用において同じIDが使われます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/245438/34008477-442f-e99f-efd5-e45403b48fe5.png)

Alexaスキルのテストを行うと毎回違うIDになってしまいますが、実際には上記のとおりになるということです。

##attributesは？
テストのために、公式チュートリアル第4回のコードに、「test」というキーで整数「5」を追加しています。

```
        //永続アトリビュートの取得
        const persistentAttributes = await attributesManager.getPersistentAttributes();//
        persistentAttributes.sign = sign;
        ///////////テスト用・追加////////////
        persistentAttributes.test=5;//整数を入れてみる
```

DynamoDBにはこんな感じでデータが保存されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/245438/ce4aeeb7-0070-6b20-09e4-a2cb5d5ad235.png)

中はこんな感じと。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/245438/99e51a41-bcfc-4547-755c-a97f22a5d571.png)


#C#でDynamoDBにアクセスしよう
##準備
###AWSSDK.DynamoDBv2のインストール（Nugetで）
C#からDynamoDBにアクセスするにはどうしたら良いのか調べてみると、AmazonさんがそのためのパッケージをNugetで配布してくれていました。
それが「AWSSDK.DynamoDBv2」です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/245438/12b0aaaa-6e62-5ee7-3fc3-99a95301089d.png)

これをインストールします。

###テスト用のAWS Lambdaプロジェクトを作成
次はAlexaスキルから呼び出してDynamoDBにテーブルを作るテストをするためだけのAWS Lambdaプロジェクトを作りましょう。
テストするなら実際と同じ環境でテストしたいですからね。
[こちら](https://qiita.com/shuhey/items/30ade9a86262450feeee)の記事を読んでいただいて、プロジェクトの作成と「Alexa.NET」のインストールをしてください。
そして、Function.csの内容は以下のようなリクエストを受け取ってレスポンスを返すだけのシンプルな状態にしておきます。

```csharp:Function.cs
using System.Threading.Tasks;
using Amazon.Lambda.Core;
using Alexa.NET.Request;
using Alexa.NET.Response;


// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]

namespace DynamoDBTest
{
    public class Function
    {

        /// <summary>
        /// A simple function that takes a string and does a ToUpper
        /// </summary>
        /// <param name="input"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        public SkillResponse FunctionHandler(SkillRequest input, ILambdaContext context)
        {
            //ここにDynamoDBにアクセスするコードを追加していく

            return new SkillResponse
            {
                Version = "1.0",
                Response = new ResponseBody()
            };
        }
    }
}
```

##まずはテーブル作成
###テーブルを作成するコードを追加します
[こちら](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/LowLevelDotNetTableOperationsExample.html)にC#からDynamoDBへアクセスするサンプルコードが掲載されています
ので、これを参考にします。
これはコマンドラインアプリケーションからDynamoDBにアクセスするサンプルコードなので、AWS Lambdaから利用する場合はいくらか調整する必要があります。

調整の結果が以下です。

```csharp:Function.cs
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Amazon.Lambda.Core;
using Alexa.NET.Request;
using Alexa.NET.Response;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;


// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]

namespace DynamoDBTest
{
    public class Function
    {
        /// <summary>
        /// A simple function that takes a string and does a ToUpper
        /// </summary>
        /// <param name="input"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        public SkillResponse FunctionHandler(SkillRequest input, ILambdaContext context)
        {
            //ここにDynamoDBにアクセスするコードを追加していく
            string tableName = "TestTable";//テーブル名
            AmazonDynamoDBClient client = new AmazonDynamoDBClient();//主役。これを介してDynamoDBにアクセスする

            //同名テーブルの存在確認
            if (!IsTableExist(client, tableName))
            {
                //テーブルを作成
                CreateTable(client, tableName);
            }

            return new SkillResponse
            {
                Version = "1.0",
                Response = new ResponseBody()
            };
        }


        /// <summary>
        /// DynamoDBにテーブルを作成する
        /// </summary>
        /// <param name="client"></param>
        /// <param name="tableName"></param>
        private void CreateTable(IAmazonDynamoDB client,string tableName)
        {
            //リクエストを構築
            var request = new CreateTableRequest
            {
                //テーブルの列情報を設定
                //「ThisIsId」と「ThisIsSomthing」という2つの列を持つテーブルを作る
                AttributeDefinitions = new List<AttributeDefinition>()
                {
                    new AttributeDefinition
                    {
                        AttributeName = "ThisIsId",//カラム名
                        AttributeType = "N"
                    },
                    new AttributeDefinition
                    {
                        AttributeName = "ThisIsSomething",//カラム名
                        AttributeType = "N"
                    }
                },
                //勉強中
                KeySchema = new List<KeySchemaElement>
                {
                    new KeySchemaElement
                    {
                        AttributeName = "ThisIsId",
                        KeyType = "HASH" //Partition key
                    },
                    new KeySchemaElement
                    {
                        AttributeName = "ThisIsSomething",
                        KeyType = "RANGE" //Sort key
                    }
                },
                //勉強中
                ProvisionedThroughput = new ProvisionedThroughput
                {
                    ReadCapacityUnits = 5,
                    WriteCapacityUnits = 5
                },
                //テーブル名
                TableName = tableName
            };

            //テーブル作成リクエストを投げる！
            //ただし、非同期メソッドの返りを待たねければならない。
            //待たないと先にこのLambdaが終了して、DynamoDBのテーブル作成処理を完了せずに終わる。
            //でもメソッドにasyncつけたら、最終的にスキルのレスポンスの型もTask<SkillResponse>にしなきゃいけなくなってだめ。
            //.Wait()メソッドで非同期メソッドを同期メソッドにしちゃえば、返り値も変えなくていいし、テーブル作成完了まで待つことができる。
            //一回完結のサーバー側の処理で非同期でなきゃいけない理由ないしね。
            //client.CreateTableAsync(request).Wait();
            
            //Waitメソッドではただ非同期メソッドの完了を待つだけでしたが、非同期メソッドの返り値を取得したい場合は、Resultプロパティを使いましょう。
            //Resultプロパティにアクセスすることで、非同期メソッドの完了を待った上で結果を取得することができます。
            //結果を使って何か処理を行いたい場合はこちらが良いのではないでしょうか。
            var result=client.CreateTableAsync(request).Result;
        }


        /// <summary>
        /// 同名のテーブルが存在するかをチェックします。
        /// </summary>
        /// <param name="client"></param>
        /// <param name="tableName"></param>
        /// <returns></returns>
        private bool IsTableExist(IAmazonDynamoDB client,string tableName)
        {
            //テーブル一覧を取得
            var tableList=client.ListTablesAsync().Result;
            //TableNamesプロパティをチェック
            return tableList.TableNames.Exists(s => s.Equals(tableName));
        }
    }
}
```

公式ページのサンプルコードでは同期メソッドの`CreateTable`メソッドが使用されていましたが、今は`CreateTableAsync`しか提供されていないんですかね？
ともかく、スキルとしての返り値の制限からasync、awaitを使った非同期メソッドの利用はできないので、非同期メソッドを同期メソッドとして使えば問題はないということです。

###AWS Lambdaにデプロイします
デプロイの方法はこれまた[こちら](https://qiita.com/shuhey/items/30ade9a86262450feeee)を参考にしていただくとして、一つ注意があります。
それは「Role」についてです。
今回はDynamoDBにアクセスするので、このプログラムにその権限を与える必要があります。
AWSコンソールでIAMの設定から必要な権限（ポリシー）を持ったロールを作成するとよいのですが、とりあえずここではデプロイ時に選択できるロールとして「**AWSLambdaFullAccess**」というとても力強いポリシーのものを選びます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/245438/95ce7c6b-8be7-b14d-04c1-a769b8b54aa5.png)

###動作確認
デプロイが完了したら動作確認しましょう。
サンプルリクエストとして「Alexa Start Session」を選択します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/245438/fe979e96-e221-8755-9735-93171e36599d.png)

レスポンスが返ってきたら、AWSコンソールでDynamoDBにテーブルが追加されているか確認します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/245438/51a6cbc2-bd12-9b4e-62a7-399e204ea0bf.png)

「TestTable」が追加されています。やったね！
ということで、これでC#を使ってAWS_LambdaからDynamoDBにアクセスする第一歩が踏み出せました。

#課題
DynamoDBに送るリクエストの構築部分がよくわかっていません。
カラム名の指定のところは見てわかるのですが、`KeyScheme`と`ProvisionedThroughput`がわかっていません。
調べてわかったら追記します。