Alexa Skills Kit (ASK) Software Development Kit (ask-sdk-core) v2使ってみた

# 概要
* alexa-sdk for Node.jsがバージョン１からバージョン２になっていたので使ってみた
    * https://developer.amazon.com/blogs/alexa/post/decb3931-2c81-497d-85e4-8fbb5ffb1114/now-available-version-2-of-the-ask-software-development-kit-for-node-js
* バージョン１とバージョン２の主たる相違点をまとめた

# 相違点の検証実施
## 開発環境
* node: v8.10.0
* npm: 5.6.0
* ask-sdk-core: 2.0.1
* ask-sdk-model: 1.0.1

## 主たる相違点
### ハンドラーの定義方法
* バージョン１
    * セッションの状態を起点にハンドラーを定義
    * ハンドラー内ではインテントごとに処理を定義

    ```javascript:バージョン1
    exports.handler = (event, context) => {
        alexa.registerHandlers(handlers);
    };

    let handlers = {
        'LaunchRequest': function () {
            this.response.speak('ようこそ');
            this.emit(':responseReady');
        },
        'SessionEndedRequest': function() {
        },
        ・・・
    }
```

* バージョン２
    * インテントを起点にハンドラーを定義
    * ハンドラー内ではセッションの状態ごとに処理を定義

    ```javascript:バージョン2
    const LaunchRequestHandler = {
        canHandle(handlerInput) {
            return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
        },
        handle(handlerInput) {
            const speechText = 'ようこそ';

            return handlerInput.responseBuilder
                .speak(speechText)
                .reprompt(speechText)
                .getResponse();
        },
    };
```

### エラーハンドリングを専用のハンドラーに集約
* バージョン１
    * ハンドラーごとに定義（正常処理が含まれているハンドラー）
    * ハンドラー内ではUnhandledとして処理を定義

    ```javascript:バージョン1
    exports.handler = (event, context) => {
        alexa.registerHandlers(handlers);
    };

    let handlers = {
        'LaunchRequest': function () {
            this.response.speak('ようこそ');
            this.emit(':responseReady');
        },
        'Unhandled': function() {
            this.response.speak('もう一度おねがいします');
            this.emit(':responseReady');
        }
    }
```

* バージョン２
    * エラー専用のハンドラーとして登録
    * ハンドラー内ではセッションの状態ごとに処理を定義

    ```javascript:バージョン2
    exports.handler = skillBuilder
        .addErrorHandlers(ErrorHandler)
        .lambda()

    const ErrorHandler = {
        canHandle() {
            return true;
        },
        handle(handlerInput, error) {
            return handlerInput.responseBuilder
                .speak('もう一度おねがいします')
                .getResponse();
        },
    };
```

### リクエストとレスポンスにインタセプターを設定可能
インターセプターとはある処理の実行前後になにか別の処理を挿入（インターセプト）させるためのコンポーネントです。

* バージョン１
    * リクエストインタセプター
        * 各インテントの先頭に挿入したい処理を定義
    * レスポンスインタセプター
        * 各インテントの末尾に挿入したい処理を定義
        * セッション終了インテントの処理であればSessionEndedRequestに実装してもよい

    ```javascript:バージョン1
    let handlers = {
        'LaunchRequest': function () {
            console.log('リクエストインタセプターの処理はここ');
            this.response.speak('ようこそ');
            this.emit(':responseReady');
            console.log('レスポンスインタセプターの処理はここ');
        },
        'Unhandled': function() {
            console.log('リクエストインタセプターの処理はここ');
            this.response.speak('もう一度おねがいします');
            this.emit(':responseReady');
            console.log('レスポンスインタセプターの処理はここ');
        }
    }
```

* バージョン２
    * リクエストインタセプター
        * addRequestInterceptorsをCustomSkillBuilderに定義
    * レスポンスインタセプター
        * addResponseInterceptorsをCustomSkillBuilderに定義

    ```javascript:バージョン2
    exports.handler = skillBuilder
        .addRequestInterceptors(requestInterceptor)
        .addResponseInterceptors(responseInterceptor)
        .lambda()

    const requestInterceptor = {
        process(handlerInput) {
             console.log('リクエストインタセプターの処理はここ');
       },
    };

    const responseInterceptor = {
        process(handlerInput) {
             console.log('レスポンスインタセプターの処理はここ');
       },
    };
```

### リクエストのJSONオブジェクトの取得
AlexaからLambdaに渡ってくるJSONオブジェクトの取得方法が変わってます。

* バージョン１

```javascript:バージョン1
let handlers = {
    'LaunchRequest': function () {
        const request = this.event.request;
        ・・・
    }
```

* バージョン２

```javascript:バージョン2
const LaunchRequestHandler = {
    canHandle(handlerInput) {
        ・・・
    },
    handle(handlerInput) {
        const request = handlerInput.requestEnvelope; 
        ・・・
    }
```

### セッション情報、永続情報の取得・保存
* バージョン１
    * セッション情報
        * 取得
            * this.event.session.attributesオブジェクトから値を取得
        * 保存
            * this.sessionオブジェクトに値を代入
    * 永続情報
        * alexa.dynamoDBTableName = 'MySkillSampleTable'を定義。
        * 取得
            * this.attributes['キー名']から値を取得
        * 保存
            * this.attributes['キー名']に値を代入
* バージョン２
    * AttributesManagerを用いる　

    ```javascript:セッション情報
    const attributesManager = handlerInput.attributesManager;
    // 取得
    const sessionAttributes = attributesManager.getSessionAttributes();
    // 保存
    sessionAttributes.保存したい名称 = '保存したい値';
    ```

    ```javascript:永続情報
return new Promise((resolve, reject) => {
        handlerInput.attributesManager.getPersistentAttributes()
        .then((attributes) => {
            // 取得
            const value = attributes['キー名'];
 
            // 保存
            attributes['キー名'] = '保存したい値';
            handlerInput.attributesManager
                .setPersistentAttributes(attributes);
            return handlerInput.attributesManager.savePersistentAttributes();
        })
        .then(() => {
            resolve(handlerInput.responseBuilder.getResponse());
        })
        .catch((error) => {
            reject(error);
        });
});
    ```


## 注意点
githubにあがっているサンプル上ではSkillIDによる制御が入っていないためいれておくとよいです。

```javascript:バージョン1
exports.handler = (event, context) => {
    alexa.appId = process.env.APP_ID;  //スキルID
};
```
```javascript:バージョン2
exports.handler = skillBuilder
    .withSkillId(process.env.APP_ID) //スキルID
```

（2018/05/04追記）
スキルIDの検証がLambda側でできるようになっていました。
Lambda中のロジックで検証ではなく、フロントで検証できるので、こちらでスキルIDを設定する方が便利のようです。

![Screen Shot 2018-05-04 at 12.14.34.png](https://qiita-image-store.s3.amazonaws.com/0/123578/2b184ddd-8bc5-56f1-a027-a95b8349ec52.png)


# まとめ
* ハンドラーの扱いが変わったのが最も大きな違いですがVUI設計の原則である、インテントごとにやりたい処理・機能を分離するという原則にのっとった実装になっている気がします
    * そのかわりに状態管理が複雑化しているようにも見えます。
* ４月中公開するスキルはこれで実装してみます。
    * 公開したら追記しますので是非つかってみてくださいｗ