ASK-SDK v2がリリースされ、少し期間も経ったので移行しようかなと思ったらかなり破壊的な変更が入っていました。
この記事は、ASK-SDK v1(以下,V1)で作ったスキルかつ、V1のSDKからDynamoDBの操作を行っているスキルを開発している開発者向けです。
ASK-SDK V1とV2では、DynamoDBに生成されるプライマリパーティションキーが異なる
従来のV1で、AlexaスキルからDynamoDBを操作する場合は、下記のようなコードをLambda側に用意していました。下記のコードでは、DynamoDBのテーブル名にAlexaUserData_V1を設定し、何らかのインテントを受け付けたときに**AlexaSDK V1のテストです。と返答します。返答時にuseridをプライマリパーティションキーとしてDynamoDBのテーブルが生成されます。今回は、msgというキーとしてV1からの書き込みデータです。**というStringの文字列を書き込みます。
var Alexa = require('alexa-sdk');
exports.handler = function(event, context, callback) {
var alexa = Alexa.handler(event, context);
alexa.dynamoDBTableName = 'AlexaUserData_V1';
alexa.registerHandlers(handlers);
alexa.execute();
};
var handlers = {
'Unhandled': function () {
var dataKey = "msg";
var userData = this.attributes[dataKey];
if (userData === undefined) {
userData = "V1からの書き込みデータです。"
}
this.attributes[dataKey] = userData;
this.emit(':tell', 'AlexaSDK V1のテストです。');
}
};
実際にスキルを実行するとDynamoDB側ではAlexaデバイスにログインしているuserIdをプライマリパーティションキーとして、mapAttrの項目内にmsgをKeyとして先程の文字列が登録されています。
簡単に既存プロジェクトをASK-SDK V2に移行する場合
破壊的な変更とは言ったものの、後方互換はちゃんと保たれています。SDKを alexa-sdk
からask-sdk-v1adapter
に切り替えることで、既存のv1のプロジェクトのままv2に移行することができます。この場合も下記のコードのまま、V1で生成したDynamoDBへのテーブルへのアクセスは可能です。
// const Alexa = require('alexa-sdk');
const Alexa = require('ask-sdk-v1adapter');
exports.handler = function(event, context, callback) {
var alexa = Alexa.handler(event, context);
alexa.dynamoDBTableName = 'AlexaUserData_V1';
alexa.registerHandlers(handlers);
alexa.execute();
};
var handlers = {
'LaunchRequest': function () {
var dataKey = "msg";
var userData = this.attributes[dataKey];
if (userData === undefined) {
userData = "V1Adapterからの書き込みデータです。"
}
this.attributes[dataKey] = userData;
this.emit(':tell', 'AlexaSDK V1Adapterのテストです。');
}
};
V2で、DynamoDBを操作した場合
では、今回の主題のV2でDynamoDBを操作した場合はどうなるか見ていきます。今回実行したコードは下記の通りです。比較のために、DynamoDBのテーブル名はV2に変更しています。
const Alexa = require('ask-sdk');
exports.handler = Alexa.SkillBuilders.standard()
.addRequestHandlers(LaunchRequestHandler)
.withTableName("AlexaUserData_V2")
.withAutoCreateTable(true)
.lambda();
const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
},
async handle(handlerInput) {
var dynamos = await handlerInput.attributesManager.getPersistentAttributes()
dynamos = {"msg":"V2からの書き込みデータです。"};
handlerInput.attributesManager.setPersistentAttributes(dynamos);
await handlerInput.attributesManager.savePersistentAttributes();
return handlerInput.responseBuilder
.speak('AlexaSDK V2のテストです。')
.getResponse();
}
};
上記のスクリプトを実際にスキルから呼び出し生成したDynamoDBのテーブルが下記になります。この時点で、おわかりだと思いますが、プライマリパーティションキーがV1のSDKを用いたスキルの場合、useridを項目として生成されていましたが、V2のSDKからDynamoDBを操作した場合、プライマリパーティションキーとして使われる項目名がidに変更になっています。そのため、上記のV2のスクリプトからV1のテーブルにアクセスを行おうとした場合、キーが見つからないためエラーとなりスキル終了してしまいます。
そのため、V1を利用したスキルをV2のSDKでマイグレーションを行おうとしてもDynamoDBの既存のuseridをソートするようなスキルを開発している場合、既存のテーブルのまま移行することが現状ではできません。 →実は出来ました。
では、V1からV2への移行をどうするか
1.ask-sdk-v1adapterを採用する
公式が推奨するのは、後方互換が担保されているv1adapterになるでしょう。v1adapterを利用すれば、DynamoDBのキー変更の問題は何ら問題なく対応できます。ただし、Handler部分はV2仕様に逐一マイグレーションすることが可能ですが、SDKのイニシャライズ部分などはV1のフォーマットに準拠することになります。また、Stateなどの扱いもV2から大きく変わっていることから結果として多少なりとも辛みは残ってしまうと私個人は考えます。
2.ask-sdk v2に完全移行する
後方互換も用意されていますが、個人的にはまだリリース前もしくは、規模の小さいスキルに関しては、v2へ早々に移行した方が良いとも感じています。また、Amazon側もAlexaBlogにて、今後の新規スキルはV2の利用を推奨しています。
今回はDynamoDBのソートするキーの名称がv1とv2で変更されていて単純にScanできませんというお話ですが、v2の破壊的に近い変更を見る限り、今後も大幅にSDKが進化する可能性が秘められていると思います。そういったことを踏まえても、早々に既存スキルのv2への完全移行を進めた方が良いかなと個人的には今回の件を通し感じています。(異論は認めますが、どのスキルも規模的にまだ大きくなっていないので、負債となる前に移行作業をしてしまったほうが幸せかと思います。)
完全移行する場合のDynamoDBの対応策
当初、V2向けのDynamoDBのテーブルを作らざるおえないとしていましたが、先日のAlexa dev daysで、ジェフレス・ジャスティン氏に質問したところask-sdk-dynamodb-persistence-adapterをV1の時と同じキーのConfigをもたせたインスタンスを生成、読み込みを行うことで、v2向けのテーブルを生成せずに対応することが可能とのことでした。
この際、**SkillBuilders.standard()**でSkill Builderのインスタンス生成を行うと、V2のデフォルト値でConfig登録されますので、**SkillBuilders.custom()**で、Skill Builderのインスタンス生成を行ってください。
日本語ドキュメントばかり探していたので、実は英語の方にはテーブル名などの指定方法が明記されてました。公式の日本語ドキュメントは、一部Option項目などが抜け落ちているので、あまり信用せず、英語版もしっかり読みましょう。(ジャスティンさんありがとうございました!)
const Alexa = require('ask-sdk');
//別途Adapterの読み込みが必要
const DynamoDBAdapter = require('ask-sdk-dynamodb-persistence-adapter');
//V1と同じキーをConfigに設定
const dbConfig = {
tableName: 'AlexaUserData_V1',
partitionKeyName: 'userId', //本項目がv2のデフォルトでは、「id」になっています
attributesName: 'mapAttr', //本項目がv2のデフォルトでは、「attribute」になっています
createTable: true
}
//V1のキーを指定したConfig設定のDynamoDbPersistenceAdapterを生成
const dbAdapter = new DynamoDBAdapter.DynamoDbPersistenceAdapter(dbConfig);
exports.handler = Alexa.SkillBuilders.custom() //customでインスタンス生成
.addRequestHandlers(LaunchRequestHandler)
.withPersistenceAdapter(dbAdapter) //DynamoDbPersistenceAdapterをSkillBuilderに登録
.lambda();
const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
},
async handle(handlerInput) {
var dynamos = await handlerInput.attributesManager.getPersistentAttributes()
dynamos = {"msg":"V2からV1への書き込みデータです。"};
handlerInput.attributesManager.setPersistentAttributes(dynamos);
await handlerInput.attributesManager.savePersistentAttributes();
return handlerInput.responseBuilder
.speak('AlexaSDK V2のテストです。')
.getResponse();
}
};
現状の方法としては、V1を使用したスクリプトからアクセスしているDynamoDBのテーブルからV2用のテーブルへ、項目名をV2用に変更してコピーする形になると思います。
作業手順としては、既存スキルのV2版の開発を行い、完了後にDynamoDBのV1用テーブルからV2用のテーブルに項目名をv2用にリネームしつつコピーを行います。
コピーを完了した段階で、スキルのエンドポイントに設定しているLambda functionのスクリプトをV2版に差し替えることで、スキルの申請なしでV2版のスクリプトに変更することができます。
現状個人的に考え出せた解がこれなので、アドバイスなどいただけると嬉しいです。
#追記
一応Custom Skill Builderでゴニョゴニョすると任意のキーで叩けますが、Standard Skill Bulderを利用する際はテーブルの移し替えになります。(移し替えするならCustomでなんとかしたほうがいいと思います。)
その際は、下記スクリプトを叩けば新しいテーブルにデータ移管が可能です。
ConverterScript書きました。
Alexa_DynamoDB_KeyConverter