ASK-SDK(v2)の変更点メモ

# はじめに
スマートスピーカーのスキル作成方法などまとめていて、そろそろLambda部分かなーと思っていたらそういえばv2出てたんですよね・・・。私の知識はv1止まりなので先にv2の勉強も兼ねてプログラムの書き方をメモっておきます。

# どんな人向け？

- alexa-sdkを使用してスキル開発をしたことがある人

# 本家リンク

- https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs
- https://developer.amazon.com/ja/blogs/alexa/post/51f6f6eb-a6cb-4d2e-82ca-166bdfee61be/ask-sdk-v2-release-japanese

# 使用するパッケージ
パッケージ名が変更されるとともに、幾つかに分解されたようですね。

- v1
    - alexa-sdk
- v2
    - ask-sdk
    - ask-sdk-core
    - ask-sdk-model
    - ask-sdk-dynamodb-persistence-adapter
    - ask-sdk-v1adapter

実際に使うところまで検証できていないのですが、Dynamo使わないならcoreで大丈夫みたい。
また、v1時代のプログラムからv2に移行する際には下位バージョン互換をサポートするv1adapterが使えるようですね。大きなプログラムを移行するのはシンドイですもんね・・・。

# 私が基本的に使ってたSDKたちと変更後の書き方
今回の調査対象とする部分です。スキルを作成する上でこれくらいを把握できていれば簡単なスキルは作成できるかな〜と思ってます。ダイアログ関係は次のステップにします。

- ハンドラーの作り方
- this.emit()を使用したask/tell
- state管理
- セッション管理（と言うか値の持ち回し）
- スロットとシノニムからの値の取得方法

## ハンドラーの作り方とstate管理

入り口から大きく変わりましたね。
旧バージョンはこのような感じでした。

```javascript
exports.handler = function (event, context, callback) {

    var alexa = Alexa.handler(event, context);
    alexa.appId = APP_ID;
    // ここで、ステート毎に作成しておいたハンドラーを列挙していく
    alexa.registerHandlers(handlers, handlers1, handlers2, ...);
    alexa.execute();

};
```

新バージョンはこんな感じ

```javascript
exports.handler = Alexa.SkillBuilders.custom()
    .addRequestHandlers(  // ここから登録された順にハンドラーが評価される
        handlers,
        handlers1,
        handlers2,
        SessionEndedRequestHandler)
    .addErrorHandlers(ErrorHandler)
    .withSkillId(APP_ID)
    .lambda();
```

書き方が変わっただけで、やってることは変わらないかな〜と見えるのですが実はハンドラーの書き方が激変しています。

### v1のハンドラーの書き方
v1ではステート毎にハンドラーを作成して、ハンドラーの中で各インテントの処理を記載していました。

```javascript
const handlers = {
    'LaunchRequest': function () {
        this.handler.state = 'STATE1';
        this.emit(':ask', 'こんにちは。って言ってね！');
    },
    'HelloIntent': function () {
        this.handler.state = 'STATE1';
        this.emit(':ask', 'こんにちは。って言ってね！');
    },
    'Unhandled': function () {
        this.emit(':tell', '終了しますね！');
    }
};

const handlers1 = Alexa.CreateStateHandler('STATE1', {
    'HelloIntent': function () {
        this.handler.state = 'STATE2';
        this.emit(':ask', '次は、こんばんわ。って言ってね！');
    },
    'Unhandled': function () {
        this.emit(':tell', '終了しますね！');
    }
});

const handlers2 = Alexa.CreateStateHandler('STATE2', {
    'GoodEveningIntent': function () {
        this.emit(':tell', 'ありがとう！');
    },
    'Unhandled': function () {
        this.emit(':tell', '終了しますね！');
    }
});
```

処理内容はともかくとして・・・STATEが設定されていない時と設定されている時のハンドラーをそれぞれ準備して、各インテント用のファンクションを用意するイメージでした。

### v2のハンドラーの書き方
コードを見てみましょう。

```javascript
const handlers = {
    // 今の状況下で、このハンドラーを処理するかどうかを判断する
    canHandle (handlerInput) {
        const request = handlerInput.requestEnvelope.request;
        const state = handlerInput.attributesManager.getSessionAttributes().state;
        return request.type === 'LaunchRequest' ||
              (request.type === 'IntentRequest' &&
                request.intent.name === 'HelloIntent' &&
                !state);
    },
    // 上記の判断でtrueとなる場合に実行される
    handle (handlerInput) {
        const speechText = 'こんにちわ。って言ってね！';
        handlerInput.attributesManager.setSessionAttributes({'state': 'STATE1'});
        return handlerInput.responseBuilder
            .speak(speechText)
            .reprompt(speechText)
            .getResponse();
    }
};

const handlers1 = {
    canHandle (handlerInput) {
        const request = handlerInput.requestEnvelope.request;
        const state = handlerInput.attributesManager.getSessionAttributes().state;
        return request.type === 'IntentRequest' &&
                request.intent.name === 'HelloIntent' &&
                state === 'STATE1';
    },
    handle (handlerInput) {
        const speechText = '次は、こんばんわ。って言ってね！';
        handlerInput.attributesManager.setSessionAttributes({'state': 'STATE2'});
        return handlerInput.responseBuilder
            .speak(speechText)
            .reprompt(speechText)
            .getResponse();
    }
};

const handlers2 = {
    canHandle (handlerInput) {
        const request = handlerInput.requestEnvelope.request;
        const state = handlerInput.attributesManager.getSessionAttributes().state;
        return request.type === 'IntentRequest' &&
                request.intent.name === 'GoodEveningIntent' &&
                state === 'STATE2';
    },
    handle (handlerInput) {
        const speechText = 'ありがとう！';
        return handlerInput.responseBuilder
            .speak(speechText)
            .getResponse();
    }
};

const ErrorHandler = {
    canHandle (handlerInput) {
        return true;
    },
    handle (handlerInput) {
        return handlerInput.responseBuilder
            .speak('終了しますね！')
            .getResponse();
    }
};
```

**canHandle**という部分で、そのハンドラーの実処理部分を実行するかを判断します。
そして**handle**という部分で実処理を記載します。
このハンドラーが、Alexa.SkillBuilders.custom().addRequestHandlers()に列挙した順に実行されるので、順にcanHandleで判断されて一番最初に実処理を行うと判断されたところでストップします。

なるほどーと思いますが、これ難しくないですか・・・？

## ask / tell

v1でもあったと思うのですが、**Response**を使う形式で統一された？ようです。
[WikiのInterface](https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs/wiki/Response-Building)がとても見やすいです。

### tellの書き方
上記のハンドラーの例にも既に書いちゃっていますがこんな感じです。

```javascript
return handlerInput.responseBuilder
    .speak('通常応答メッセージ！')
    .getResponse();
```

### askの書き方
repromptを付けると**ask**になります。

```javascript
return handlerInput.responseBuilder
    .speak('通常応答メッセージ！')
    .reprompt('応答が無かった時の再確認メッセージ！')
    .getResponse();
```

Wikiを見る限りだとwithShouldEndSessionメソッドが存在するので、reprompt無しでも制御できそうですが、askする限りはrepromptがあった方がいいと思うので、この形で良さそうです。

他にもカードを使ったりなど色々とメソッドが用意されていますね。

## セッション管理
AttributesManagerというものを使用します。
[こちらもWikiが](https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs/wiki/Skill-Attributes#attributesmanager)とても見やすいです。

生存期間が、１リクエスト、１セッション、永続の３種があるようなので用途に応じて使い分けるのが楽しそうです。
上記のハンドラーの例では従来通りのセッション管理をしています。

### 保持
```javascript
handlerInput.attributesManager.setSessionAttributes({'Key': 'Value'});
```

### 取得
```javascript
const val = handlerInput.attributesManager.getSessionAttributes().key;
```

## スロットとシノニムからの値取得
v1の時と同じようですね。

```javascript
// スロット値（発話した内容。同義語を言った場合は同義語値）
handlerInput.requestEnvelope.request.intent.(スロット名).value
// スロット値（値を言っても同義語を言っても、こちらは「値」として設定したもの）
handlerInput.requestEnvelope.request.intent.(スロット名).resolutions.resolutionsPerAuthority[0].values[0].value
```

この深い方の配列が複数になるのってどういう条件だろう・・・？

# 終わりに
Alexaスキルはドキュメントもサンプルも充実しているので、とりあえず何か作ってみるのが簡単でいいですよね！
v2についてはまだ調べ始めたばかりなので間違っている点などあるかもしれません。何かあればご指摘いただけるとありがたいです。

ここにメモした事以外にも色々ありそうなので、有用な情報などあれば書いていきたいと思います。
読んでいただいてありがとうございました！