Alexaスキルを Microsoft Azure Functions（Node.js）環境で開発する方法

# 1. はじめに
通常、Alexaスキルを開発する際のシステム構成とし、「Amazon Alexa ＋ AWS Lambda」の構成がベストプラクティスだとは思いますが、諸事情により「AWS Lambda」を用いることができない場合も稀に存在します。
ただ、2019年1月現在、AWS Lambda以外のクラウド環境（Node.js）を用いた参考サイトは、ほとんど見つけることができません。
今回は、自分への備忘録も兼ねて、「AWS Lambda」の代わりに「Microsoft Azure Functions（Node.js）」を用いたAlexaスキルの開発方法をお伝えします。

## 参考文献
- [AlexaアプリをAWS lambdaではなくAzure functionでつくった話](https://qiita.com/kino3/items/ede16f3468ce4101b40f)

## 1-1. この記事を読んでできること
- **Alexaスキルを Microsoft Azure Functions（Node.js）環境で開発できるようになる。**

## 1-2. 前提条件
- Alexaスキルの知識があること。
- Microsoft Azure Functionsの知識があること。

## 1-3. 必要なアカウント
- Amazon.co.jpアカウント [Amazon Developer Portal](https://developer.amazon.com/ja)ログイン用
- [Microsoft Azure](https://azure.microsoft.com/ja-jp/)の開発アカウント

## 1-4. 必要なソフトウェア
### ブラウザ
- ブラウザ：Chrome（推奨）
- Chrome拡張機能：Advanced REST client（ローカル環境でのPOSTテスト用）

### エディタ
- エディタ：[Visual Studio Code（推奨）](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-create-first-function-vs-code)
- Visual Studio Code 用 Azure Functions 拡張機能：[azure functions](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-create-first-function-vs-code)

### ツール
- [.NET Core SDK 2.2](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-run-local)
- [Node.js（npmコマンド含む）](https://nodejs.org/ja/)
- Azure Functions Core Tools：[azure-functions-core-tools](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-run-local)

## 1-5. 注意事項
- Nodeモジュールに[alexa-skill-sdk-for-azure-function](https://www.npmjs.com/package/alexa-skill-sdk-for-azure-function)を用います。
- alexa-skill-sdk-for-azure-functionモジュールは、Alexa SDK V1 [alexa-sdk](https://www.npmjs.com/package/alexa-sdk)にのみ対応しています。
- Alexa SDK V1 [alexa-sdk](https://www.npmjs.com/package/alexa-sdk)は、2019年1月現在、開発が終了しており、Alexa SDK V2 [ask-sdk](https://www.npmjs.com/package/ask-sdk)の利用が推奨されています。
- Alexa SDK V2 ask-sdkを用いたMicrosoft Azure Functions環境での開発方法をご存知の方がいらっしゃいましたら、コメントをお待ちしております。

# 2. index.jsサンプルソース
- Microsoft Azure Functions環境版のFACTスキル（宇宙の豆知識）です。

```javascript:index.js（FACTスキル：宇宙の豆知識スキルAzureFunctions版）
'use strict';

// コメントアウト
// var Alexa = require('alexa-sdk');

// var APP_ID = "amzn1.ask.skill.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
var APP_ID = undefined;

// 定数定義
var SKILL_NAME = "豆知識";
var GET_FACT_MESSAGE = "知ってましたか？";
var HELP_MESSAGE = "豆知識を聞きたい時は「豆知識」と、終わりたい時は「おしまい」と言ってください。どうしますか？";
var HELP_REPROMPT = "どうしますか？";
var STOP_MESSAGE = "さようなら";

// 音声応答定義
var data = [
    "水星の一年はたった88日です。",
    "金星は水星と比べて太陽より遠くにありますが、気温は水星よりも高いです。",
    "金星は反時計回りに自転しています。過去に起こった隕石の衝突が原因と言われています。",
    "火星上から見ると、太陽の大きさは地球から見た場合の約半分に見えます。",
    "木星の<sub alias='いちにち'>1日</sub>は全惑星の中で一番短いです。",
    "天の川銀河は約50億年後にアンドロメダ星雲と衝突します。",
    "太陽の質量は全太陽系の質量の99.86%を占めます。",
    "太陽はほぼ完璧な円形です。",
    "皆既日食は一年から二年に一度しか発生しない珍しい出来事です。",
    "土星は自身が太陽から受けるエネルギーの2.5倍のエネルギーを宇宙に放出しています。",
    "太陽の内部温度は摂氏1500万度にも達します。",
    "月は毎年3.8cm地球から離れていっています。"
];

// AWS Lambda Node.js V6.10 + alexa-sdk V1の場合の表記
var handlers = {
    'LaunchRequest': function () {
        this.emit('GetNewFactIntent');
    },
    'GetNewFactIntent': function () {
        var factArr = data;
        var factIndex = Math.floor(Math.random() * factArr.length);
        var randomFact = factArr[factIndex];
        var speechOutput = GET_FACT_MESSAGE + randomFact;
        this.emit(':tellWithCard', speechOutput, SKILL_NAME, randomFact)
    },
    'AMAZON.HelpIntent': function () {
        var speechOutput = HELP_MESSAGE;
        var reprompt = HELP_REPROMPT;
        this.emit(':ask', speechOutput, reprompt);
    },
    'AMAZON.CancelIntent': function () {
        this.emit(':tell', STOP_MESSAGE);
    },
    'AMAZON.StopIntent': function () {
        this.emit(':tell', STOP_MESSAGE);
    }
};

// ーーーー 追記ここから AzureFunctions対応 ーーーー
module.exports = function (context, req) {
    var alexa = require('alexa-skill-sdk-for-azure-function');
    alexa.setup({
        azureCtx: context,
        azureReq: req,
        handlers: [handlers],
        trackInvokedIntents: true,
        enforceVerifier: false,
        // i18nSettings: i18nSettings
    });

    alexa.execute(avsCallback(context, req));
}

var avsCallback = function (azureCtx, req) {
    return function (err, obj) {

        if (err) {
            azureCtx.res = {
                status: 400,
                body: err
            };
        } else {
            azureCtx.res = {
                body: obj
            };
        }
        azureCtx.done();
    };
};
// ーーーー 追記ここまで AzureFunctions対応 ーーーー
```

# 3. package.json
- package.jsonは自分で作る必要がある。

```text:Ubuntuコマンド
// プロジェクトフォルダ直下（host.jsonファイルがある同一フォルダ内で実行する）
// package.jsonの作成
$ npm init

// alexa-skill-sdk-for-azure-function のインストール
$ npm i alexa-skill-sdk-for-azure-function
```

```json:package.json
{
  "name": "functions-project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "dependencies": {
    "alexa-skill-sdk-for-azure-function": "^1.0.8"
  },
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

```

# 4. 関数のURLの取得
- Azure Functions の関数ページから、関数のURLを取得する。

![関数のURL.PNG](https://qiita-image-store.s3.amazonaws.com/0/240522/e2543d4f-bdd2-cc90-42f0-4b0527d070ad.png)

# 5. Alexaスキルのエンドポイント設定
1. 左メニューから、「エンドポイント」を選択する。
2. サービスのエンドポイントの種類に、「HTTPS」を選択する。
3. デフォルトの地域に、Azure Functions（クラウド）の「関数のURL」を設定する。
4. ドロップダウン選択肢から、「開発用のエンドポイントは、証明機関が発行したワイルドカード証明書をもつドメインのサブドメインです」を選択する。
5. 「エンドポイントを保存」をクリックする。

https://プロジェクト名.azurewebsites.net/api/関数名

![エンドポイント設定.PNG](https://qiita-image-store.s3.amazonaws.com/0/240522/95f722cf-7807-3840-c997-a19c15f93ced.png)

# 6. Amazon Developer Portalでのテスト実施（クラウド環境テスト）
- 「＜スキル名＞ を開いて」と発話し、テストする。
- スキル名が「宇宙の豆知識」の場合、Amazon公式のスキルが動作するため、スキル名呼び出しは重複しないように注意すること。（例では、「宇宙豆知識」としています。）

![テスト.PNG](https://qiita-image-store.s3.amazonaws.com/0/240522/918bd11c-2e6c-f875-e453-4189bb59fc00.png)

# 6. Visual Studio Codeでのテスト実施（ローカル環境テスト）
1. メニューから、「デバッグ」→「デバッグの開始」をクリックする。
2. テストURL（ローカル環境）をコピーする。

![ローカルテスト.PNG](https://qiita-image-store.s3.amazonaws.com/0/240522/ec572939-f2c9-b450-d0a0-1f97e3306ffe.png)

# 7. ローカル環境テスト用ツールの用意
- Chrome拡張機能に「Advanced REST client」を追加し、アプリを起動する。

![ローカルツール.PNG](https://qiita-image-store.s3.amazonaws.com/0/240522/f35d0bcf-d7fd-4fcc-5068-814af17abf25.png)

# 8. テスト用 Request JSON（ローカル環境テスト用）
- テスト用 Request JSON（LaunchRequest）は以下のとおり。

```
{
  "session": {
    "new": true,
    "sessionId": "amzn1.echo-api.session.[unique-value-here]",
    "attributes": {},
    "user": {
      "userId": "amzn1.ask.account.[unique-value-here]"
    },
    "application": {
      "applicationId": "amzn1.ask.skill.[unique-value-here]"
    }
  },
  "version": "1.0",
  "request": {
    "locale": "ja-JP",
    "timestamp": "2016-10-27T18:21:44Z",
    "type": "LaunchRequest",
    "requestId": "amzn1.echo-api.request.[unique-value-here]"
  },
  "context": {
    "AudioPlayer": {
      "playerActivity": "IDLE"
    },
    "System": {
      "device": {
        "supportedInterfaces": {
          "AudioPlayer": {}
        }
      },
      "application": {
        "applicationId": "amzn1.ask.skill.[unique-value-here]"
      },
      "user": {
        "userId": "amzn1.ask.account.[unique-value-here]"
      }
    }
  }
}
```

# 9. ローカル環境テスト用ツールの設定＆実行
1. Methodに、「POST」を選択する。
2. Request URLに、先ほどコピーした「テストURL（ローカル環境）」をペーストする。
3. 「Body」を選択する。
4. Body contenxt typeに、「application/json」を選択する。
5. Editor viewに、「Raw Input」を選択する。
6. テキストエリアに、「テスト用 Request JSON（ローカル環境テスト用）」をペーストする。
7. 「SEND」ボタンをクリックする。

![ローカルツール2.PNG](https://qiita-image-store.s3.amazonaws.com/0/240522/e4607b38-7426-e429-f35a-22e3bb82ef28.png)

実行結果として、画面下部に「200 OK」と「Response JSON」が出力される。

![実行結果.PNG](https://qiita-image-store.s3.amazonaws.com/0/240522/dc14d48e-3964-1a03-039b-ca6ff10c5ac7.png)

# 10. おまけ（Azure Function クラウド環境での単体テスト）
1. 「テスト」を、クリックする。
2. HTTPメソッドに、「POST」を選択する。
3. 要求本文に、「テスト用 Request JSON（ローカル環境テスト用）」をペーストする。
4. 「実行ボタン」をクリックする。

実行結果として、画面下部に「進捗状況：200 OK」と「Response JSON」が出力される。

![Azureテスト.PNG](https://qiita-image-store.s3.amazonaws.com/0/240522/78935a2d-57d7-f034-22e0-fa321fb4589b.png)

# 11. おわりに
いかがだったでしょうか？ご参考になりましたでしょうか？
わたしじしんが、Microsoft Azure Functionsを始めて2日目で知識がとぼしいため、Azure Functionsの関数作成手順などは、今回割愛させていただきました。
Azure Functionsの関数作成については、公式サイトの[Azure Functions のドキュメント](https://docs.microsoft.com/ja-jp/azure/azure-functions/)が参考になるかと思います。

また、もしかしたら間違ってるよ、とかありましたら、コメント頂けると幸いです。

そして、Alexa SDK V1 alexa-sdk ではなく、Alexa SDK V2 ask-sdk でも Microsoft Azure Functions（Node.js）環境で開発できる方法をご存知の方がいらっしゃいましたら、ご教示のほどお待ちしております。

2019/01/17 TAKAHIRO NISHIZONO


# 追記：2019/01/20
Alexa SDK V2 ask-sdkを用いた、Microsoft Azure Functions（Node.js）環境での開発方法を、以下の記事に書き起こしました。あわせてご参照ください。

[Microsoft Azure Functions ではじめる Alexaスキル開発チュートリアル](https://qiita.com/zono_0/items/d53f9890bcfa7a43f4ec)