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

2018.10.19

この記事は公開されてから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で眺めると、色々勉強になります。