Amazon Echo(Alexa)でのデータ保存は簡単？　冗談でしょう？

# 概要

Amazon Echo(Alexa)でのスキル開発、お疲れ様です。
Alexa-Skills-Kit SDK の使用は、一見すると簡単そうに見える。```alexa.dynamoDBTableName```の行を追記して、```this.emit```を呼び出せばまるで全てが解決するかのように書かれている記事が多いという印象だ。
だが、実際はサンプルをそのまま使ってハイOKとはいかなかったのでメモを残す。

# 日付について

全然関係ないように思わせておいて、JavaScriptの日付はトラップが多いことで有名だ。まず、lambda関数のURLにnortheastが含まれているからといって、```new Date();```が東京時間になっているという思い込みを捨てよう。

```js
    process.env.TZ = "Asia/Tokyo";
```

日本時間を処理するスキルには、この行が必要だ。
（もしくは君が海外展開を視野に入れているのなら、もっと複雑なコードが必要だろう。それについては君の目で確かめてみてくれ！）

# Web上のサンプルに潜む問題

まず、改善の余地があるほう。

```js
    exports.handler = function(event, context, callback) {
        var alexa = Alexa.handler(event, context);
        alexa.APP_ID = APP_ID;
        alexa.dynamoDBTableName = 'ExampleTable';
        alexa.registerHandlers(handlers);
        alexa.execute();
    };
```

こっちが、改善したほう。

```js
    exports.handler = function(event, context, callback) {
        var alexa = Alexa.handler(event, context, callback); // callbackを渡している
        alexa.appId = APP_ID; // appIdが正
        alexa.dynamoDBTableName = 'ExampleTable';
        alexa.registerHandlers(handlers);
        alexa.execute();
    };
```

お分かりいただけただろうか？

```callback```は、渡しているサンプルと渡していないサンプルがあって、どっちが正しいか混乱するが、```this.emit(':tell', "保存しています");```等で保存に行く際には、即時に終了されると困るので、```alexa.dynamoDBTableName```の指定のついでに、コールバックを引数として渡すことが必要。

また、地味にAlexa-Skills-Kit SDKは更新されており、2018/02/24 現時点では```alexa.APP_ID```ではなく```alexa.appId```にアプリケーションIDを保存するのが正しい（ワーニングが出なくなる）。

# DynamoDBへの保存に行くときのバリデーション

まず、バグっているコード

```js
    //死ぬときの処理
    var petName = this.attributes['petName'];
    this.attributes['prePetName'] = undefined;
    this.attributes['petName'] = "";
    this.emit(':saveState', true); // ここで全ての属性を完璧に保存したいが、なぜか上手く行かない
    this.emit(':tell', petName + "は死にました。"); // この行での保存も、なぜか上手く行かない
```

修正後のコード

```js
    //死ぬときの処理
    var petName = this.attributes['petName'];
    delete this.attributes['prePetName']; //キーごと削除
    delete this.attributes['petName']; //キーごと削除
    this.emit(':tell', petName + "は死にました。"); // この行で全ての属性が完璧に保存される
```

お分かりいただけただろうか？

DynamoDBはKEY-VALUE型のDBだが、保存時にバリデーションがかかり、undefinedおよび""（空文字）が含まれるレコードは拒否され、保存は行われない。従って、意図どおりデータを削除した形で保存したい場合、```delete```を使ってキーごと削除してやる必要がある。
これは良く読めばエラーとして出力されている。

    Error during DynamoDB put:ValidationException: One or more parameter values were invalid: An AttributeValue may not contain an empty string
    Error saving state: ValidationException: One or more parameter values were invalid: An AttributeValue may not contain an empty string

保存が上手くいっているかどうかは、DynamoDB側のテーブルの項目から確認できる。上手く行っている場合はほぼ即時に反映されるので、「DBへの書き込みには少しタイムラグがあるのかな？　ちょっとコーヒーでも飲んで待ってみるか」などと考えず、すぐに上記のバグを疑ってテストケースを追加することをオススメする。

もしくは、以下のデバッグコードを任意の場所に挿入するのも有効だろう。

```js
    console.log("session attributes: " + JSON.stringify(this.attributes));
```

# ユーザが黙った場合

ユーザが黙った場合でも、上記に注意して作られたコードならば、以下のコードを追加することで対応できる。
エラーが返ってきた場合、もう一度この記事を頭から読み直そう。

```js
    'SessionEndedRequest': function () {
        this.emit(':saveState', true); // DynamoDBへの保存を行う
    },
```

# まとめ

Amazon Echo(Alexa)、Alexa-Skills-Kit SDKでのデータ保存について書いた。これを読んだ君は、データ保存で躓（つまず）かずに、サクっとスキルを作れるようになったと思う。何かデータ保存に関して、別の気づきがあったら、この記事にコメントするか、別途Qiita等でノウハウをシェアしてもらえると嬉しい。

# 参考

- JavaScript の Date は罠が多すぎる https://qiita.com/labocho/items/5fbaa0491b67221419b4
- Alexa Skills Kit SDK for Node.js ざっくり訳 https://qiita.com/HeRo/items/8475505a55cc9e737687
- How to use emit ':saveState' with ':tell' on Alexa? I use a Promise library or an alexa's magic?
 https://forums.developer.amazon.com/questions/113390/how-to-use-emit-savestate-with-tell-on-alexa-i-use.html