Node.js
TypeScript
Alexa
AlexaSkillsKit

TypeScript を使って Alexa Custom Skills を作ろう (4) 実装 - インテント

前回

大分間が空いてしまいましたが。。。
前回の投稿では、index.ts からハンドラ部分を切り出し、外部モジュール化を行いました。
今回の投稿では、ハンドラにあるインテントを外部モジュール化していきましょう。

インテントの外部モジュール化について、考える

スキルを作っていくと、インテントの数も増えていきます。
状態が増えていくとさらに増えていきます。

単純にハンドラ毎にインテントを作っていくと、同じ動きをするけど、各ハンドラのインテント毎にそれぞれ同じ処理(プログラム)を書いているなんてことになり保守が大変になります。

そのようなプログラムをレビューする側としては非常につらい。。。同じプログラムを二度チェックしないといけない。。。
インテントに限らず再利用性を考え、計画的に外部モジュール化しましょう。

そんなことを言いつつも個人的には、再利用性を考え、すべてを外部モジュール化しなければいけないのか?と問われると、Noです。
とりあえず動くものを作って、あとから再利用性を考えるでも全然問題無いと思います。
プロジェクト規模によっては敢えてそこを捨てることで、上手くチームが回る場合ももちろんあるでしょう。

さて、今回の投稿では、複数回呼ばれているヘルプインテントを外部モジュール化していきます。

さあ、はじめよう

new-session-handler.tsstart-handler.tsで呼び出されているAMAZON.HelpIntentを外部モジュール化します。

handlers/new-session-handler.ts(抜粋)
'AMAZON.HelpIntent': function (this: Alexa.Handler<any>) {
    this.emit(':ask', this.t('ASK_HELP_MESSAGE'));
  },
handlers/start-handler.ts(抜粋)
'AMAZON.HelpIntent': function (this: Alexa.Handler<any>) {
    this.emit(':ask', this.t('ASK_HELP_MESSAGE'));
  },

2つとも、同じ処理をしていますね。
再利用性が高いので、これを外部モジュール化しましょう。

外部モジュール化するにあたっては、単体テストのしやすい作りを考えましょう。

インテントで書かれている処理は、コンテキストに依存しています。
この依存をこれから作るインテントでは、コンテキストを外部から渡すことにより、クラスを疎結合にし、インテントの単体テストをしやすくします。

インテント処理をハンドラから外出しする

移動
$ cd ~/custom-skill-sample-to-convert/skill/lambda/custom/src
intentsディレクトリ作成
$ mkdir -p intents
ヘルプインテントファイル作成
$ touch ./intents/help-intent.ts

では、help-intent.tsファイルにクラスを定義していきましょう。

インテント基底クラスの作成

インテントの処理は、コンテキストに依存している為これを依存解決をしようと思います。
本スキルでは、他のインテントは外部モジュール化は行いませんが同様に依存した作りとなる為、依存解決部分は基底クラスに定義します。

インテント基底クラスファイルの作成
$ touch ./intents/intent-base.ts
intents/intent-base.ts
import * as Alexa from 'alexa-sdk';

/**
 * インテント基底クラス
 */
export class IntentBase {
  /**
   * プロパティ - Alexaハンドラコンテキスト
   */
  protected alexaContext: Alexa.Handler<any>;

  /**
   * コンストラクタ
   * @param context Alexaハンドラコンテキスト
   */
  constructor(context: Alexa.Handler<any>) {
    this.alexaContext = context;
  }
}

ヘルプインテントクラスの作成

基底クラスを基にヘルプインテントクラスを作ります。
先程作成した基底クラスを継承し、実装します。

ポイントは、コンテキストを外から依存解決しそれをexecuteメソッドで利用しているところです。

intents/help-intent.ts
import * as Alexa from 'alexa-sdk';
import { IntentBase } from './intent-base';

/**
* ヘルプインテントクラス
*/
export class HelpIntent extends IntentBase {

  /**
   * コンストラクタ
   * @param context Alexaハンドラコンテキスト
   */
  constructor(context: Alexa.Handler<any>) {
    super(context);
  }

  /**
   * アクション
   */
  public execute() {
    // スキルレスポンス設定
    this.alexaContext.emit(':ask', this.alexaContext.t('ASK_HELP_MESSAGE'));
  }
}

ハンドラのインテント呼出の変更

作成したクラスをハンドラから呼び出しましょう。
まずは、インポートですね。

./handlers/各ファイルのヘッダ部
import { HelpIntent } from '../intents/help-intent';

あとは、ヘルプインテントのインスタンスをコンテキストを渡して作って、実行するだけです。

余談ですがインテントクラスは、シングルトンでよいと思っています。
なので、今回の実装ではインテント毎にインスタンスを生成していますが、そこをどう作っていくかは今後の課題ですね。

handlers/new-session-handler.ts(抜粋)
'AMAZON.HelpIntent': function (this: Alexa.Handler<any>) {
    (new HelpIntent(this)).execute();
  },
handlers/start-handler.ts(抜粋)
'AMAZON.HelpIntent': function (this: Alexa.Handler<any>) {
    (new HelpIntent(this)).execute();
  },

first-handler.tsのヘルプインテントは、確認しやすいように本投稿で追加しました。

handlers/first-handler.ts(抜粋)
'AMAZON.HelpIntent': function (this: Alexa.Handler<any>) {
    (new HelpIntent(this)).execute();
  },

まとめ

この投稿では、ハンドラからインテント部分を切り出し、外部モジュール化を行いました。
ヘルプインテントは、処理が1行しか無いのでメリットが薄く感じるかもしれませんが、
コンテキストの依存解決を行うことで単体テストがしやすくなります。

今回の投稿のソースは以下にあります。
ヘルプインテント外部モジュール化

次回は、発話です!