[日本語Alexa] Alexa-SDK Ver2 DynamoDB利用時のTips 〜リージョンを指定したり、プライマリーキーを変えて、スキルで共通のデータを保存したり〜

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

1 はじめに

本日、Alexa Blogに、SDK V1で自動生成されたDynamoDBテーブルをV2で引き継ぐための手法が公開されていました。
ASK SDK V1で作られたDynamoDBテーブルをそのままV2で使うためのコツ

上記ブログでは、DynamoDbPersistenceAdapterを自前で作成することで、プライマリキーやアトリビュート名をデフォルトのものから置き換える手法が紹介されています。

const dynamoDbPersistenceAdapter = new DynamoDbPersistenceAdapter({  
    tableName : 'persistence-demo',  
    partitionKeyName: 'userId',  
    attributesName: 'mapAttr'  
})

Alexa SDK V2では、かなりの勢いでラッパーされているため、デフォルト値を使用するのであれば、非常にシンプルにスキルを作成することができます。しかし、一部でも、デフォルト値を変更したいと考えると、そのドキュメントがまだ少ないため、ソースコードを確認する作業になります。

今回は、私がソースコードから得た情報で、DynamoDBのデフォルト値を変更する手法を2つ紹介させて下さい。 なお、本記事の内容は、私が手元で試しただけのものであることを、予めご了承下さい。

2 リージョン

以下のサンプルは、DynamoDBにテーブルを作成し、counterをインクリメントするだけのものです。

const Alexa = require('ask-sdk');

const tableName = 'test-sdk-v2-dynamodb';
let skill;
exports.handler = async function (event, context) {
    if (!skill) {
      skill = Alexa.SkillBuilders.standard() 
        .addRequestHandlers(TestHandler)
        .withTableName(tableName) // テーブル名指定
        .withAutoCreateTable(true) //テーブル作成もスキルから行う
        .create();
    }
    return skill.invoke(event);
}

const TestHandler = {
    canHandle(handlerInput) {
        return true;
    },
    async handle(handlerInput) {

      // 永続化情報の取得
      let attributes = await handlerInput.attributesManager.getPersistentAttributes()
      // 初期化
      if(!attributes.counter){ 
          attributes.counter = 0; 
      }
      attributes.counter++;
      // 永続化情報の保存
      handlerInput.attributesManager.setPersistentAttributes(attributes);
      await handlerInput.attributesManager.savePersistentAttributes();

      return handlerInput.responseBuilder
          .speak('こんにちは')
          .getResponse();
    }
};

呼び出される度に、counterがインクリメントされます。

しかし、このスキルは、Lambdaがデプロイされたリージョンと同じリージョンのDynamoDBにテーブルを作成します。

DynamoDBのリージョンを自由に指定したい場合は、下記のように自前でDynamoDBクライアントを生成します。下記の例では、バージニアにテーブルが作成されることになります。

const Alexa = require('ask-sdk');
const AWS = require('aws-sdk');
 
const tableName = 'test-sdk-v2-dynamodb';
let skill;
exports.handler = async function (event, context) {
    if (!skill) {
      skill = Alexa.SkillBuilders.standard() 
        .addRequestHandlers(TestHandler)
        .withTableName(tableName) // テーブル名指定
        .withAutoCreateTable(true) //テーブル作成もスキルから行う
        .withDynamoDbClient(
          new AWS.DynamoDB({ apiVersion: "latest", region: "us-east-1" })
        )
        .create();
    }
    return skill.invoke(event);
}

3 プライマリーキー

(1) userId

DynamoDBを利用した永続化では、Alexaから送られてきたUserIdを元にプライマリーキーを生成しています。

UserIDは、スキルが「有効」になっていから「無効」にされるまで、同一ユーザーであれば、利用するデバイスに関係なく同じとなるため、利用者の固有データを保存するというニーズでは、非常に理にかなった仕様です。

参考:Alexaからスキルに送られてくる各種IDの一意性については、下記のブログをご参照下さい。

(2) キー値の設定

ユーザーごとの情報を保存するのであれば、デフォルトの動作で全く問題ありませんが、ちょっとニーズが変わって、例えば、このスキルを使用した際の情報(全ユーザーをまたがった情報)や、日毎の情報(日毎の最高値や、トータルなど)を保存したい場合も、プライマリーキーを差し替えることで、AttributesManagerで同じように利用できるようになります。

ここで、StandardSkillBuilderを確認すると、DynamoDB関連のメソッドにwithPartitionKeyGeneratorというのがあり、PartitionKeyGeneratorというオブジェクトで初期化されている事がわかります。

StandardSkillBuilder.d.ts

export interface StandardSkillBuilder extends BaseSkillBuilder {
    withTableName(tableName: string): this;
    withAutoCreateTable(autoCreateTable: boolean): this;
    withPartitionKeyGenerator(partitionKeyGenerator: PartitionKeyGenerator): this;
    withDynamoDbClient(customDynamoDBClient: DynamoDB): this;
}

そして、PartitionKeyGeneratorsの実装を確認すると下記のようになっていました。userIdがAlexaのリクエストからuserIdを抽出しているのが分かります。

PartitionKeyGenerators.ts

export const PartitionKeyGenerators = {
    /**
     * Gets attributes id using user id.
     * @param {RequestEnvelope} requestEnvelope
     * @returns {string}
     */
    userId(requestEnvelope : RequestEnvelope) : string {
        if (!(requestEnvelope
              && requestEnvelope.context
              && requestEnvelope.context.System
              && requestEnvelope.context.System.user
              && requestEnvelope.context.System.user.userId)) {
            throw createAskSdkError(
                'PartitionKeyGenerators',
                'Cannot retrieve user id from request envelope!',
            );
        }

        return requestEnvelope.context.System.user.userId;
    },

    /**
     * Gets attributes id using device id.
     * @param {RequestEnvelope} requestEnvelope
     * @returns {string}
     */
    deviceId(requestEnvelope : RequestEnvelope) : string {
        if (!(requestEnvelope
              && requestEnvelope.context
              && requestEnvelope.context.System
              && requestEnvelope.context.System.device
              && requestEnvelope.context.System.device.deviceId)) {
            throw createAskSdkError(
                'PartitionKeyGenerators',
                'Cannot retrieve device id from request envelope!',
            );
        }

        return requestEnvelope.context.System.device.deviceId;
    },
};

withPartitionKeyGeneratorを使用して、キーのジェネレーターを差し替えるコードは、下記のようになります。

const tableName = 'test-sdk-v2-dynamodb';
let skill;
exports.handler = async function (event, context) {
    if (!skill) {
      skill = Alexa.SkillBuilders.standard() 
        .addRequestHandlers(TestHandler)
        .withTableName(tableName) // テーブル名指定
        .withAutoCreateTable(true) //テーブル作成もスキルから行う
        .withPartitionKeyGenerator(OriginKey)
        .create();
    }
    return skill.invoke(event);
}

function OriginKey() {
  return 'original-key';
}

このスキルを実行すると、全てのユーザーの情報が、origin-keyに保存されます。

OriginKey()を日付に応じた文字列を返すようにすれば、日毎のデータもが保存できることになります。

4 最後に

今回は、DynamoDBのデフォルト値を変更することで、自由にリージョンを変えたり、プライマリーキーを変更して、AttributesManagerを使用する要領を紹介させて頂きました。

デフォルト値の変更は、ドキュメントがない場合、どうしてもソースコードの確認が必要ですが、幸い、Alea SDK V2はTypeScriptで書かれているため、VSCodeで簡単に追跡することができます。

GutHubからコードをダウンロードして、VSCodeで眺めると、色々勉強になります。

コメントは受け付けていません。