Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Deep Dive Development Alexa Skill by Node.js

Deep Dive Development Alexa Skill by Node.js

Alexa day 2019

Hidetaka Okamoto

April 06, 2019
Tweet

More Decks by Hidetaka Okamoto

Other Decks in Programming

Transcript

  1. D e e p D i v e D e

    v e l o p m e n t A l e x a S k i l l b y N o d e . j s A l e x a D a y 2 0 1 9 #alexaday2019
  2. L e a r n a b o u t

    … • ASK SDK • Developing Alexa Skills by TypeScript • Difference of S3 / DynamoDB • Testing & Deployment #alexaday2019
  3. H i d e t a k a O k

    a m o t o • Digitalcube Co. Ltd. • Alexa Campions • AWS Samurai 2017 in Japan
  4. E c h o ΁ ͷ ൃ ࿩ ͕ L

    a m b d a ʹ ಧ ͘ · Ͱ #alexaday2019
  5. Ի ੠ - > จ ࣈ - > J S

    O N ͷ 2 ε ς ο ϓ • ASR (Auto Speech Recognition)ͱNLU (Natural Language Understanging) • Echo͕ฉ͖औͬͨԻ੠ΛASR͕จࣈʹม׵͢Δ • ม׵͞ΕͨจࣈྻΛNLU͕JSONʹม׵͢Δ • Lambda͸JSONΛड͚औΓɺJSONΛฦ͚ͩ͢ #alexaday2019
  6. ฉ ͖ औ Γ ɾ ೝ ࣝ ෦ ෼ ͷ

    ϙΠ ϯ τ • NLU͕JSON΁ͷม׵ʹʮର࿩ϞσϧʯΛར༻͢Δ • ςΩετΛਖ਼͍͠JSONʹม׵Ͱ͖͍ͯΔ͔ -> ର࿩Ϟσϧ • ड͚औͬͨJSONΛਖ਼͘͠ॲཧͰ͖͍ͯΔ͔ -> Lambdaͷ࣮૷ • ʮೝࣝͰ͖ͯͳ͍ʯͱʮॲཧͰ͖ͯͳ͍ʯͷ੾Γ෼͚Λ #alexaday2019
  7. A S K S D K ͕ ΍ ͬͯ ͍

    Δ ͜ ͱ #alexaday2019
  8. Ұ ൠ త ͳ ॻ ͖ ํ export handler =

    Ask.SkillBuilders.custom() .addRequestHandlers(...) .lambda() #alexaday2019
  9. L a m b d a ϥ ΠΫ ʹ ॻ

    ͖ ௚ ͢ͱ ͜ ͏ ͳ Δ export handler = Ask.SkillBuilders.custom() .addRequestHandlers(...) .lambda() export const handler= async (event) => { return Ask.SkillBuilders.custom() .addRequestHandlers(...) .create().invoke(event) } #alexaday2019
  10. l a m b d a ( ) ͕ ಺

    ෦ Ͱ . c re a t e ( ) . i n v o k e ( e v e n t ) Λ ࣮ ߦ export handler = Ask.SkillBuilders.custom() .addRequestHandlers(...) .lambda() export const handler= async (event) => { return Ask.SkillBuilders.custom() .addRequestHandlers(...) .create().invoke(event) } #alexaday2019
  11. i n v o k e ( ) ಺ Ͱ

    e v e n t Λ h a n d l e r I n p u t ʹ ม ׵ const input : HandlerInput = { requestEnvelope, //͜Ε͕event context, attributesManager : AttributesManagerFactory.init({ requestEnvelope, persistenceAdapter : this.persistenceAdapter, }), responseBuilder : ResponseFactory.init(), serviceClientFactory : this.apiClient ? new ServiceClientFactory({ apiClient : this.apiClient, apiEndpoint : requestEnvelope.context.System.apiEndpoint, authorizationValue : requestEnvelope.context.System.apiAccessToken, }) : undefined, }; https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs/blob/2.0.x/ask-sdk-core/lib/skill/CustomSkill.ts#L65 #alexaday2019
  12. h a n d l e r I n p

    u t ͷ த ਎ ֓ આ handerInput: { requestEnvelope: Alexa͔ΒͷϦΫΤετ಺༰(=event), context: LambdaͷίϯςΩετ(=context), attributesManager: attributeૢ࡞ܥʹ͔ͭ͏, responseBuilder: Ϩεϙϯεੜ੒ʹ͔ͭ͏, serviceClientFactory: ֤छAPIαʔϏεʹ઀ଓ͢ΔͨΊʹ͔ͭ͏ } #alexaday2019
  13. Ϧ Ϋ Τ ε τ ಺ ༰ ( I n

    t e n t / S l o t / e t c . . ) Λ औ ಘ ͢ Δ handerInput: { requestEnvelope: Alexa͔ΒͷϦΫΤετ಺༰(=event), context: LambdaͷίϯςΩετ(=context), attributesManager: attributeૢ࡞ܥʹ͔ͭ͏, responseBuilder: Ϩεϙϯεੜ੒ʹ͔ͭ͏, serviceClientFactory: ֤छAPIαʔϏεʹ઀ଓ͢ΔͨΊʹ͔ͭ͏ } #alexaday2019
  14. L a m b d a ͷ ֤ छ ৘

    ใ ͕ ೖ ͬͯ ͍ Δ handerInput: { requestEnvelope: Alexa͔ΒͷϦΫΤετ಺༰(=event), context: LambdaͷίϯςΩετ(=context), attributesManager: attributeૢ࡞ܥʹ͔ͭ͏, responseBuilder: Ϩεϙϯεੜ੒ʹ͔ͭ͏, serviceClientFactory: ֤छAPIαʔϏεʹ઀ଓ͢ΔͨΊʹ͔ͭ͏ } #alexaday2019
  15. η ο γ ϣ ϯ ΍ D B σ ʔ

    λ ͳ Ͳ ͷ ॲ ཧ ʹ ࢖ ͏ handerInput: { requestEnvelope: Alexa͔ΒͷϦΫΤετ಺༰(=event), context: LambdaͷίϯςΩετ(=context), attributesManager: attributeૢ࡞ܥʹ͔ͭ͏, responseBuilder: Ϩεϙϯεੜ੒ʹ͔ͭ͏, serviceClientFactory: ֤छAPIαʔϏεʹ઀ଓ͢ΔͨΊʹ͔ͭ͏ } #alexaday2019
  16. ฦ ౴ ಺ ༰ ͷ ࡞ ੒ ʹ ࢖ ͏

    handerInput: { requestEnvelope: Alexa͔ΒͷϦΫΤετ಺༰(=event), context: LambdaͷίϯςΩετ(=context), attributesManager: attributeૢ࡞ܥʹ͔ͭ͏, responseBuilder: Ϩεϙϯεੜ੒ʹ͔ͭ͏, serviceClientFactory: ֤छAPIαʔϏεʹ઀ଓ͢ΔͨΊʹ͔ͭ͏ } #alexaday2019
  17. C u s t o m e r P ro

    f i l e A P I ͳ Ͳ ͷ ઀ ଓ ʹ ࢖ ͏ handerInput: { requestEnvelope: Alexa͔ΒͷϦΫΤετ಺༰(=event), context: LambdaͷίϯςΩετ(=context), attributesManager: attributeૢ࡞ܥʹ͔ͭ͏, responseBuilder: Ϩεϙϯεੜ੒ʹ͔ͭ͏, serviceClientFactory: ֤छAPIαʔϏεʹ઀ଓ͢ΔͨΊʹ͔ͭ͏ } #alexaday2019
  18. ॏ ཁ ౓ : ੺ ৭ > ࠇ ৭ >

    փ ৭ handerInput: { requestEnvelope: Alexa͔ΒͷϦΫΤετ಺༰(=event), context: LambdaͷίϯςΩετ(=context), attributesManager: attributeૢ࡞ܥʹ͔ͭ͏, responseBuilder: Ϩεϙϯεੜ੒ʹ͔ͭ͏, serviceClientFactory: ֤छAPIαʔϏεʹ઀ଓ͢ΔͨΊʹ͔ͭ͏ } #alexaday2019
  19. A l e x a ͷ Ϧ Ϋ Τ ε

    τ Λ ॲ ཧ ͢ Δ #alexaday2019
  20. ͢ ͝ ͍ J S O N { "version": "1.0",

    "session": { "new": true, "sessionId": "amzn1.echo-api.session.[unique-value-here]", "application": { "applicationId": "amzn1.ask.skill.[unique-value-here]" }, "attributes": { "key": "string value" }, "user": { "userId": "amzn1.ask.account.[unique-value-here]", "accessToken": "Atza|AAAAAAAA...", "permissions": { "consentToken": "ZZZZZZZ..." } } }, "context": { "System": { "device": { "deviceId": "string", "supportedInterfaces": { "AudioPlayer": {} } }, "application": { "applicationId": "amzn1.ask.skill.[unique-value-here]" }, "user": { "userId": "amzn1.ask.account.[unique-value-here]", "accessToken": "Atza|AAAAAAAA...", "permissions": { "consentToken": "ZZZZZZZ..." } }, "apiEndpoint": "https://api.amazonalexa.com", "apiAccessToken": "AxThk..." }, "AudioPlayer": { "playerActivity": "PLAYING", "token": "audioplayer-token", "offsetInMilliseconds": 0 } }, "request": {} } https://developer.amazon.com/ja/docs/custom-skills/request-and-response-json-reference.html #alexaday2019
  21. A l e x a ͷ Ϧ Ϋ Τ ε

    τ Λ ॲ ཧ ͢ Δ ɹ ɹ ɹ ɹ ɹ ͨ Ί ͷ ੩ త ܕ ෇ ͖ ݴ ޠ ೖ ໳ #alexaday2019
  22. A l e x a Ͱ ͸ ͡ Ί Δ

    ੩ త ܕ ෇ ͚ • Alexaͷ৔߹ɺInput / OutputͷϑΥʔϚοτ͕΄΅ݻఆ • ܕఆٛϑΝΠϧ΋ެ։͞Ε͍ͯΔͷͰɺ͋Δ΋ͷΛ࢖͑͹΄΅OK • IDEͰೖྗิ׬΍όϦσʔγϣϯ͕ೖΔͷͰɺϛε༧๷ʹͳΔ • ॳΊͯ࢖͏஋ɾϝιου΋ܕͷώϯτΛཔΓʹ࣮૷Ͱ͖Δ #alexaday2019
  23. ܕ ৘ ใ ͸ 3 ݴ ޠ Ͱ ެ ։

    ͞ Εͯ ͍ Δ • Python: https://github.com/alexa/alexa-apis-for-python • Java: https://github.com/alexa/alexa-apis-for-java • TypeScript: https://github.com/alexa/alexa-apis-for-nodejs #alexaday2019
  24. Ty p e S c r i p t ͷ

    η ο τΞ ο ϓ // install $ npm install -D typescript ask-sdk ask-sdk-model // setup $ ./node_modules/.bin/tsc --init // build $ ./node_modules/.bin/tsc #alexaday2019
  25. Ty p e S c r i p t ͷ

    η ο τΞ ο ϓ // build $ ./node_modules/.bin/tsc // package.jsonʹҎԼΛ଍͓ͯ͘͠ͱɺ // `npm run build`ͰϏϧυͰ͖Δ "scripts": { "build": "tsc" } #alexaday2019
  26. A S K C L I Ͱ ͷ σ ϓ

    ϩ Π • ࣄલʹ.ts -> .jsͷม׵͕ඞཁ • hooks/pre_deploy_hook.shΛ׆༻͢Δͱɺ
 ask deployͰϏϧυ -> σϓϩΠΛ࣮ߦՄೳ • npm prune —production΋ೖΕΔ͜ͱͰɺdevআ֎ #alexaday2019
  27. / h o o k s / p re _

    d e p l o y _ h o o k . s h install_dependencies() { npm install --prefix "$1" >/dev/null 2>&1 # Լͷ3ߦΛ௥Ճ͢Δ npm run build --prefix "$1" rm -rf "$1/node_modules" >/dev/null 2>&1 npm install --prefix "$1" --only=production >/dev/null 2>&1 return $? } #alexaday2019
  28. G e t t i n g s t a

    r t e d Ty p e S c r i p t • null / undefinedͷߟྀ࿙Ε༧๷͚ͩͰ΋ϝϦοτେ • ೖྗิ׬Λ׆͔ͭͭ͠ɺϛεͷ༧๷ʹͭͳ͛Δ • ؔ਺ɾҾ਺͢΂ͯܕ৘ใ͕͋Γɺ৽ػೳΛ௥͍΍͍͢ • gulp / webpackͳͲ͕࢖͑ΔͱɺΑΓศརʹͳΔ #alexaday2019
  29. S k i l l B u i l d

    e r s Ͳ ͬ ͪ Λ ࢖ ͏ ͔ ໰ ୊ #alexaday2019
  30. s t a n d a rd / c u

    s t o m Λ બ ΂ Δ export handler = Ask.SkillBuilders .custom() .addRequestHandlers(...) .lambda() export handler = Ask.SkillBuilders .standard() .addRequestHandlers(...) .lambda() #alexaday2019
  31. ΄ ΅ ಉ ͡ ಈ ͖ Λ ͢ Δ ί

    ʔ υ import * as Ask from 'ask-sdk' Ask.SkillBuilders.standard() .addRequestHandlers(catchAll) .withTableName('tableName') .withAutoCreateTable(true) .lambda() import * as Ask from 'ask-sdk-core' Ask.SkillBuilders.custom() .addRequestHandlers(catchAll) .withApiClient(new Ask.DefaultApiClient()) .withPersistenceAdapter(new DynamoDbPersistenceAdapter({ tableName: 'tableName', createTable: true })) .lambda() S TA N D A R D C U S T O M #alexaday2019
  32. S e r v i c e C l i

    e n t ͷ ઃ ఆ ෦ ෼ import * as Ask from 'ask-sdk' Ask.SkillBuilders.standard() .addRequestHandlers(catchAll) .withTableName('tableName') .withAutoCreateTable(true) .lambda() import * as Ask from 'ask-sdk-core' Ask.SkillBuilders.custom() .addRequestHandlers(catchAll) .withApiClient(new Ask.DefaultApiClient()) .withPersistenceAdapter(new DynamoDbPersistenceAdapter({ tableName: 'tableName', createTable: true })) .lambda() S TA N D A R D C U S T O M #alexaday2019
  33. D y n a m o D B ͷ ઃ

    ఆ ෦ ෼ import * as Ask from 'ask-sdk' Ask.SkillBuilders.standard() .addRequestHandlers(catchAll) .withTableName('tableName') .withAutoCreateTable(true) .lambda() import * as Ask from 'ask-sdk-core' Ask.SkillBuilders.custom() .addRequestHandlers(catchAll) .withApiClient(new Ask.DefaultApiClient()) .withPersistenceAdapter(new DynamoDbPersistenceAdapter({ tableName: 'tableName', createTable: true })) .lambda() S TA N D A R D C U S T O M #alexaday2019
  34. S t a n d a rd o r C

    u s t o m • Standard͸DynamoDBσϑΥϧτ • Hosted Skillͷ৔߹ɺDBೖΕΔͳΒCustomҰ୒ • ServiceClient / PersistantAttributesҎ֎͸΄΅ಉ͡ • StandardͰεϞʔϧελʔτ -> CustomҠߦ΋Մೳ #alexaday2019
  35. ͬ͟ ͘ Γ Ϣ ʔεέ ʔε Case Standard / Custom

    ɹDatabaseΛ؆୯ʹಋೖ͍ͨ͠ Standard ɹCustomer ProfileͳͲͷApiΛ࢖͍͍ͨ Standard ɹHosted SkillͰDatabase࢖͍͍ͨ Custom ɹS3ΛDatabaseʹ͍ͨ͠ Custom ɹ͍Ζ͍ΖઃఆΛΧελϜ͍ͨ͠ Custom #alexaday2019
  36. D B Ͳ ͬ ͪ Λ ࢖ ͏ ͔ ໰

    ୊ #alexaday2019
  37. D y n a m o D B o r

    S 3 • σϑΥϧτ͸DynamoDB • ͲͪΒ΋User IDΛΩʔʹɺObjectΛอଘ͢Δܗ • SLA͸΄΅ޡࠩ(S3: 99.9% / DynamoDB: 99.99%) • ແྉ࿮͕ແظݶͳͷ͸DynamoDB( S3͸12ϲ݄ͷΈ ) • ैྔ՝ۚͷ৳ͼํ͸S3ͷ΄͏͕Ժ΍͔( 0.023USD / GB) #alexaday2019
  38. D y n a m o D B ͷ p

    ro s / c o n s • [cons] શ݅औಘ͸͋·Γ͓͢͢Ί͠ͳ͍ʢྉۚɾύϑΥʔϚϯεతʹʣ • [cons] MapܕͰσʔλΛೖΕΔͷͰɺΫΤϦ͠ਏ͍ • [cons] ແྉ࿮Λ௒͑ͨ৔߹ʹS3ΑΓߴ͘ͳΓ΍͍͢ • [props] standardΛ࢖͑͹؆୯ʹηοτΞοϓͰ͖Δ • [props] ੔߹ੑ͕औΓ΍͍͢ • [props] ςʔϒϧ࡞੒΋SDK͕΍ͬͯ͘ΕΔ • [props] ແྉ࿮͕߃ৗ #alexaday2019
  39. S 3 ͷ p ro s / c o n

    s • [cons] ͦ΋ͦ΋DBͰ͸ͳ͍ͷͰɺ੔߹ੑͳͲʹଥڠ͕ඞཁ • [cons] ແྉ࿮͕12ϲ݄ݶఆ • [cons] Adapterͷઃఆ͕ඞཁ • [cons] όέοτΛखಈͰ࡞Δඞཁ͋Γ • [props] GB୯Ґͷ՝ۚͳͷͰɺແྉʹ͍ۙ • [props] HostedͰ࢖͑Δ • [props] Athena / S3 SelectͳͲͰσʔλΛݕࡧ͠΍͍͢ #alexaday2019
  40. S D K ͷ ࢖ ͍ ํ ͩ ͱ D

    y n a m o D B ɹ ɹ ɹ ɹ ɹ ׆ ༻ ͠ ͖ Εͯ ͳ ͍ આ #alexaday2019
  41. S D K ͸ M a p ܕ Ͱ શ

    ෦ 1 Ω ʔ ʹ ͍ Εͯ ͠ · ͏ dynamoDBDocumentClient.put({ TableName: 'TableName', Item: { [partitionKey]: attributesId, [attributesName]: attributes } }).promise() A S K - S D K AW S - S D K #alexaday2019
  42. Ϋ Τ Ϧ ͱ ͔ ͠ ͨ ͘ ͳ Δ

    ͱ ɺ ͜ ͏ ͠ ͨ ͍ dynamoDBDocumentClient.put({ TableName: 'TableName', Item: { [partitionKey]: attributesId, [attributesName]: attributes } }).promise() dynamoDBDocumentClient.put({ TableName: 'TableName', Item: { user_Id: attributesId, last_launched: attributes.last_launched, monthly_score: attributes.monthly_score, reminderTarget: attributes.reminder_target } }).promise() A S K - S D K AW S - S D K #alexaday2019
  43. O R Ͱ ͸ ͳ ͘ A N D ͱ

    ͍ ͏ ߟ ͑ ํ • શ݅औಘ͕ۤखͳDynamoDB • ࡉ͔͍ΫΤϦ΍੔߹ੑͷ֬อ͕ۤखͳS3 • AWS-SDKͱڞʹซ༻ͯۤ͠ख෼໺ΛΧόʔ͢Δ • ྫɿ࣌ܥྻͷϩάσʔλ͚ͩS3ʹྲྀ͠ࠐΉ • ྫɿRead͚ͩසൟͳσʔλ͸S3ɾWrite͕ଟ͍σʔλ͸DynamoDB #alexaday2019
  44. ΄ ΅ ಉ ͡ ಈ ͖ Λ ͢ Δ ί

    ʔ υ Λ S 3 A d a p t e r Ͱ import * as moment from ‘moment' … .withPersistenceAdapter(new S3PersistenceAdapter({ bucketName: process.env.S3_PERSISTENCE_BUCKET,, pathPrefix: ObjectKeyGenerators.userId(event), objectKeyGenerator: () => { const timestamp = moment(moment().format('YYYY- MM-DD')).unix() return String((Math.pow(2, 53) - 1) - timestamp) } })) https://speakerdeck.com/k1low/php-conference-2017?slide=51
  45. p a t h P re f i x Λ

    u s e r I D ʹ ͯ͠ɺ σΟ Ϩ Ϋ τ Ϧ ෼ ׂ import * as moment from ‘moment' … .withPersistenceAdapter(new S3PersistenceAdapter({ bucketName: process.env.S3_PERSISTENCE_BUCKET,, pathPrefix: ObjectKeyGenerators.userId(event), objectKeyGenerator: () => { const timestamp = moment(moment().format('YYYY- MM-DD')).unix() return String((Math.pow(2, 53) - 1) - timestamp) } })) https://speakerdeck.com/k1low/php-conference-2017?slide=51
  46. ϑ Ν Πϧ ໊ Λ R e v e r

    s e d T i m e s t a m p I D ʹ ͯ͠ ι ʔ τ ͞ ͤ Δ import * as moment from ‘moment' … .withPersistenceAdapter(new S3PersistenceAdapter({ bucketName: process.env.S3_PERSISTENCE_BUCKET,, pathPrefix: ObjectKeyGenerators.userId(event), objectKeyGenerator: () => { const timestamp = moment(moment().format('YYYY-MM-DD')).unix() return String((Math.pow(2, 53) - 1) - timestamp) } })) https://speakerdeck.com/k1low/php-conference-2017?slide=51
  47. g e t Ͱ ͖ ͳ ͘ ͳ Δ ͷ

    Ͱɺ t i m e s t a m p ͸ ೥ ݄ ೔ · Ͱ ͕ ແ ೉ import * as moment from ‘moment' … .withPersistenceAdapter(new S3PersistenceAdapter({ bucketName: process.env.S3_PERSISTENCE_BUCKET,, pathPrefix: ObjectKeyGenerators.userId(event), objectKeyGenerator: () => { const timestamp = moment(moment().format('YYYY- MM-DD')).unix() return String((Math.pow(2, 53) - 1) - timestamp) } })) https://speakerdeck.com/k1low/php-conference-2017?slide=51
  48. R e q u e s t H a n

    d l e r Λ ָ ʹ ॻ ͘ #alexaday2019
  49. I n t e n t R e q u

    e s t ͸ ল ུ ه ๏ ͕ ͋ Δ skill.addRequestHandlers({ canHandle(handlerInput) { return handlerInput.requestEnvelope.request.type === 'IntentRequest' && handlerInput.requestEnvelope.request.intent.name === 'testIntent' }, handle(handlerInput) { return handlerInput.responseBuilder .speak('test') .getResponse() } }) a d d R e q u e s t H a n d l e r s a d d R e q u e s t H a n d l e r skill.addRequestHandler( ‘testIntent', (handlerInput)=> { return handlerInput.responseBuilder .speak('test') .getResponse() } ) #alexaday2019
  50. I n t e n t R e q u

    e s t ͸ ল ུ ه ๏ ͕ ͋ Δ addRequestHandler( matcher : ((input : HandlerInput) => Promise<boolean> | boolean) | string, executor : (input : HandlerInput) => Promise<Response> | Response, ) : BaseSkillBuilder { const canHandle = typeof matcher === 'string' ? ({ requestEnvelope } : HandlerInput) => { return matcher === (requestEnvelope.request.type === 'IntentRequest' ? (requestEnvelope.request as IntentRequest).intent.name : requestEnvelope.request.type); } : matcher; runtimeConfigurationBuilder.addRequestHandler(canHandle, executor); return this; }, • addRequestHandler • ୈҰҾ਺͸จࣈྻorؔ਺ • จࣈྻΛ౉͢ͱɺ IntentRequestѻ͍ #alexaday2019
  51. ෳ ਺ ొ ࿥ ͠ ͩ ͢ͱ Χ Φ ε

    ʹ ͳΔ ͷ Ͱ ஫ ҙ 3 Π ϯ ς ϯ τ ଍ ͢ ͩ ͚ Ͱ ͜͏ ͳΔ - > #alexaday2019
  52. Ty p e S c r i p t ͰΑ

    Γ ҆ શ ͳ ɹ R e q u e s t H a n d l e r Λ ࡞ Δ #alexaday2019
  53. Ty p e S c r i p t Ͱ

    ͸ ɺ ͜ Ε ͕ ܕ Τ ϥ ʔ ʹ ͳ Δ .addRequestHandler('ExampleIntent', async (handlerInput) => { const testSlot = handlerInput.requestEnvelope.request.intent.slots.hoge.value return handlerInput.responseBuilder.speak(`You choose ${testSlot}`).getResponse() }) #alexaday2019
  54. h a n d l e r I n p

    u t . ~ . re q u e s t ͷ ܕ ͕ ൚ ༻ త ʹ ͳ ͬͯ ͍ Δ ͨ Ί • PlaybackFinishedRequest • SkillEnabledRequest • ListUpdatedEventRequest • LaunchRequest • IntentRequest • ProactiveSubscriptionChangedRequest • UserEvent • SkillDisabledRequest • ElementSelectedRequest • PermissionChangedRequest • ListItemsCreatedEventRequest • etc… #alexaday2019
  55. G e n e r i c s Λ ࢖

    ͬͯ ܕ Λ ্ ॻ ͖ Ͱ ͖ Δ import { RequestHandler } from 'ask-sdk-core'; export const testHandler2: RequestHandler = { canHandle(handlerInput) { return true }, handle(handlerInput) { return handlerInput.responseBuilder .speak('test') .getResponse() } } import { RequestHandler } from 'ask-sdk-runtime'; import { Response } from 'ask-sdk-model'; import { HandlerInput } from 'ask-sdk-core'; export const testHandler: RequestHandler<HandlerInput, Response> = { canHandle(handlerInput) { return true }, handle(handlerInput) { return handlerInput.responseBuilder .speak('test') .getResponse() } } ௨ ৗ ্ ॻ ͖ #alexaday2019
  56. ࣗ લ Ͱ i n t e r f a

    c e Λ ࡞ ͬͯ ͠ · ͏ ͱ Α Γ ศ ར import { RequestHandler } from 'ask-sdk-runtime'; import { Response, RequestEnvelope, Request } from 'ask-sdk-model'; import { HandlerInput } from ‘ask-sdk-core'; // handlerInput / requestEnvelopeͷinterfaceΛܧঝ͓ͤͯ͘͞ export interface MyCustomeRequestEnvelope<CustomRequest extends Request> extends RequestEnvelope { request: CustomRequest } export interface MyCustomHandlerInput<CustomRequest extends Request = Request> extends HandlerInput { requestEnvelope : MyCustomeRequestEnvelope<CustomRequest> } // requestͷܕΛ্ॻ͖Ͱ͖ΔΑ͏ʹͨ͠handlerͷInterface export interface MySkillRequestHandler<Input = HandlerInput, Output = Response> extends RequestHandler<Input, Output> {} #alexaday2019
  57. ࣗ લ Ͱ i n t e r f a

    c e Λ ࡞ ͬͯ ͠ · ͏ ͱ Α Γ ศ ར import { RequestHandler } from 'ask-sdk-runtime'; import { Response, RequestEnvelope, Request } from 'ask-sdk-model'; import { HandlerInput } from ‘ask-sdk-core'; // handlerInput / requestEnvelopeͷinterfaceΛܧঝ͓ͤͯ͘͞ export interface MyCustomeRequestEnvelope<CustomRequest extends Request> extends RequestEnvelope { request: CustomRequest } export interface MyCustomHandlerInput<CustomRequest extends Request = Request> extends HandlerInput { requestEnvelope : MyCustomeRequestEnvelope<CustomRequest> } // requestͷܕΛ্ॻ͖Ͱ͖ΔΑ͏ʹͨ͠handlerͷInterface export interface MySkillRequestHandler<Input = HandlerInput, Output = Response> extends RequestHandler<Input, Output> {} #alexaday2019
  58. ࣗ લ Ͱ i n t e r f a

    c e Λ ࡞ ͬͯ ͠ · ͏ ͱ Α Γ ศ ར import { RequestHandler } from 'ask-sdk-runtime'; import { Response, RequestEnvelope, Request } from 'ask-sdk-model'; import { HandlerInput } from ‘ask-sdk-core'; // handlerInput / requestEnvelopeͷinterfaceΛܧঝ͓ͤͯ͘͞ export interface MyCustomeRequestEnvelope<CustomRequest extends Request> extends RequestEnvelope { request: CustomRequest } export interface MyCustomHandlerInput<CustomRequest extends Request = Request> extends HandlerInput { requestEnvelope : MyCustomeRequestEnvelope<CustomRequest> } // requestͷܕΛ্ॻ͖Ͱ͖ΔΑ͏ʹͨ͠handlerͷInterface export interface MySkillRequestHandler<Input = HandlerInput, Output = Response> extends RequestHandler<Input, Output> {} #alexaday2019
  59. ͜ ͏ ͢ Δ ͱ I n t e n

    t R e q u e s t ͷ ͨ Ί ͷ h a n d l e r ʹ Ͱ ͖ Δ interface MyNewIntentHandlerInput extends MyCustomHandlerInput<IntentRequest> {} export const testHandler: MySkillRequestHandler<MyNewIntentHandlerInput> = { canHandle(handlerInput) { return handlerInput.requestEnvelope.request.type === 'IntentRequest' }, handle(handlerInput) { const { name } = handlerInput.requestEnvelope.request.intent return handlerInput.responseBuilder .speak(`You are request is ${name} Intent.`) .getResponse() } } #alexaday2019
  60. I n t e n t ͱ C o n

    t e x t ʢ ҙ ਤ ͱ จ ຺ ʣ #alexaday2019
  61. Ye s / N o I n t e n

    t ͳ Ͳ ͸ ɺ จ ຺ Ͱ ҙ ຯ ͕ ม Θ Δ • ʮͳʹʹର͢Δ͸͍ɾ͍͍͑ʯͳͷ͔ • Session / Persistant AttributeͰจ຺ʢContextʣΛه࿥͢Δ • ContextʹԠͯ͡ॲཧͷ಺༰Λม͑Δඞཁ͕͋Δ #alexaday2019
  62. ಉ ͡ ҙ ਤ ͩ ͕ དྷ Δ Ϧ Ϋ

    Τ ε τ ͕ ม Θ Δ ྫ • ϢʔβʔʮAlexaɺXXͰYYͯ͠ʯ • AlexaʮΘ͔Γ·ͨ͠ɻʯ • ϢʔβʔʮAlexaɺXX։͍ͯʯ • AlexaʮXX΁Α͏ͦ͜ɻYY͠·͔͢ʁʯ • Ϣʔβʔʮ͸͍ʯ • AlexaʮΘ͔Γ·ͨ͠ʯ D o S o m e t h i n g I n t e n t A M A Z O N . Ye s I n t e n t #alexaday2019
  63. c a n H a n d l e Ҏ

    ֎ ΄ ΅ ಉ ͡ ʹ ͳ Γ ΍ ͢ ͍ const DoSomethingIntent = { canHandle(handlerInput: HandlerInput) { return isDoSomethingIntent(handlerInput) } handle(handlerInput: HandlerInput) { return handlerInput.responseBuilder .speak('ॲཧΛ࣮ߦ͠·ͨ͠ɻ') .getResponse() } } const DoSomethingYesIntent = { canHandle(handlerInput: HandlerInput) { return isYesIntent(handlerInput) } handle(handlerInput: HandlerInput) { return handlerInput.responseBuilder .speak('ॲཧΛ࣮ߦ͠·ͨ͠ɻ') .getResponse() } } D o S o m e t h i n g I n t e n t A M A Z O N . Ye s I n t e n t #alexaday2019
  64. O b j e c t . a s s

    i g n Ͱ ஋ ౉ ͠ ͯ͠ ΍ Δ ͱ D RY ʹ ͳ Δ const DoSomethingIntent = { canHandle(handlerInput: HandlerInput) { return isDoSomethingIntent(handlerInput) } handle(handlerInput: HandlerInput) { return handlerInput.responseBuilder .speak('ॲཧΛ࣮ߦ͠·ͨ͠ɻ') .getResponse() } } const DoSomethingYesIntent = Object.assign( {}, DoSomethingIntent, { canHandle(handlerInput: HandlerInput) { return isYesIntent(handlerInput) } } ) D o S o m e t h i n g I n t e n t A M A Z O N . Ye s I n t e n t #alexaday2019
  65. ܧ ঝ ͢ Δ h a n d l e

    r Λ ୈ ೋ Ҿ ਺ ΁ const DoSomethingIntent = { canHandle(handlerInput: HandlerInput) { return isDoSomethingIntent(handlerInput) } handle(handlerInput: HandlerInput) { return handlerInput.responseBuilder .speak('ॲཧΛ࣮ߦ͠·ͨ͠ɻ') .getResponse() } } const DoSomethingYesIntent = Object.assign( {}, DoSomethingIntent, { canHandle(handlerInput: HandlerInput) { return isYesIntent(handlerInput) } } ) D o S o m e t h i n g I n t e n t A M A Z O N . Ye s I n t e n t #alexaday2019
  66. ୈ ࡾ Ҿ ਺ ʹ ্ ॻ ͖ ͢ Δ

    ಺ ༰ Λ ͔ ͚ ͹ O K const DoSomethingIntent = { canHandle(handlerInput: HandlerInput) { return isDoSomethingIntent(handlerInput) } handle(handlerInput: HandlerInput) { return handlerInput.responseBuilder .speak('ॲཧΛ࣮ߦ͠·ͨ͠ɻ') .getResponse() } } const DoSomethingYesIntent = Object.assign( {}, DoSomethingIntent, { canHandle(handlerInput: HandlerInput) { return isYesIntent(handlerInput) } } ) D o S o m e t h i n g I n t e n t A M A Z O N . Ye s I n t e n t #alexaday2019
  67. Te s t i n g y o u r

    S k i l l #alexaday2019
  68. A l e x a εΩϧ Ͱ ͷ ςε τ

    ͷ ߟ ͑ ํ ( ྫ ) • API / DBͳͲɺςετ͕ཉ͍͠ن໛ͷεΩϧ͸ґଘ͕ڧ͘ͳΔ • RequestHandlerͷςετ͸݁߹ςετʹͳΓ΍͍͢ • ॲཧ෦෼Λந৅Խ͠ɺͰ͖Δ͚ͩϢχοτςετͰ͖ΔΑ͏ʹ࡞Δ • UX෦෼͸ແཧʹςετίʔυΛॻ͜͏ͱ͠ͳ͍ #alexaday2019
  69. ͨ ͱ ͑ ͹ ε ϩ ο τ ̎ ͭ

    Λ ॲ ཧ ͢ Δ ϋ ϯ υ ϥ ʔ const testHandler: Ask.RequestHandler = { canHandle(handlerInput) {…}, handle(handlerInput) { const request = handlerInput.requestEnvelope.request as IntentRequest const name = request.intent.slots ? request.intent.slots.name.value : '' const number = request.intent.slots ? request.intent.slots.number.value : '' if (!name || !number) { return handlerInput.responseBuilder .speak(`you need to say name and number.`) .getResponse() } return handlerInput.responseBuilder .speak(`you say ${name} ${number}.`) .getResponse() } } #alexaday2019
  70. S l o t ͷ ॲ ཧ ෦ ෼ Λ

    ؔ ਺ ʹ ͢ Δ const testHandler: Ask.RequestHandler = { canHandle(handlerInput) {…}, handle(handlerInput) { const request = handlerInput.requestEnvelope.request as IntentRequest const content = getSpeechContent(request) if (!content) { return handlerInput.responseBuilder .speak(`you need to say name and number.`) .getResponse() } return handlerInput.responseBuilder .speak(`you say ${content}.`) .getResponse() } } const getSpeechContent = (request: IntentRequest) => { const name = request.intent.slots ? request.intent.slots.name.value : '' const number = request.intent.slots ? request.intent.slots.number.value : '' if (!name || !number) return '' return `${name} ${number}.` }
  71. ݁ ߹ ςε τ : V i r t u

    a l A l e x a https://github.com/bespoken/virtual-alexa #alexaday2019
  72. N L U Ҏ ߱ ͷ ॲ ཧ Λ ϩ

    ʔ Χ ϧ Ͱ ςε τ Ͱ ͖ Δ it("Accepts responses without dollars", async function () { const alexa = bvd.VirtualAlexa.Builder() .handler("index.handler") // Lambda function file and name .intentSchemaFile("./speechAssets/IntentSchema.json") // Uses old-style intent schema .sampleUtterancesFile("./speechAssets/SampleUtterances.txt") .create(); const launchResponse = await alexa.launch(); expect(launchResponse.response.outputSpeech.ssml).toContain(‘test') const numberResponse = await alexa.utter('I think it is 1') expect(numberResponse.response.outputSpeech.ssml).toContain('you say 1’) const numberResponse2 = await alexa.intend('NumberGuessIntent', { number: "2"}) expect(numberResponse2.response.outputSpeech.ssml).toContain('you say 2') }); #alexaday2019
  73. D y n a m o D B ͱ Ұ

    ෦ A P I ͷ Ϟ ο Ϋ ͕ ར ༻ Մ ೳ ɿ 2 0 1 9 / 4 / 6 ࣌ ఺ import { VirtualAlexa } from 'virtual-alexa'; import { handler } from '../index' const alexa = VirtualAlexa.Builder() .handler(test) .interactionModelFile('../../models/en- US.json') .create() // DynamoDBͷϞοΫ alexa.dynamoDB().mock() virtualAlexa.addressAPI().returnsFullAddr ess({ addressLine1: "address line 1", addressLine2: "address line 2", addressLine3: "address line 3", city: "city", countryCode: "country code", districtOrCounty: "district", postalCode: "postal", stateOrRegion: "state", }); #alexaday2019
  74. a s k s i m u l a t

    e Ͱ σ ϓ ϩ Π ޙ ͷ ςε τ ΋ ( Ұ Ԡ ) Մ ೳ const { execFile } = require('child_process') describe('test by ask-cli', () => { it('should return valid response when send invocation name', (done) => { execFile('ask', [ 'simulate', '-s', 'amzn1.ask.skill.d7d6176c-ab76-4e4c-b5ee-81366c4cd223', '-l', 'en-US', '-t', 'open greeter' ], (error, stdout, stderr) => { if (error) { assert.deepEqual(error, {}) } else { const { result } = JSON.parse(stdout) assert.deepEqual(result.skillExecutionInfo.invocationResponse.body.response, { card: { type: 'Simple', title: 'Hello World', content: 'Welcome to the Alexa Skills Kit, you can say hello!' }, #alexaday2019
  75. β ςε τ ɾ Ϩ Ϗϡ ʔ ґ པ ͷ

    ͢ ͢Ί • ίʔυͷςετͰ͸ςετͰ͖ͳ͍΋ͷ͕͋Δ • ʮฦ౴͕ෆࣗવͰͳ͍͔ʁʯʮಡΈؒҧ͍͸ͳ͍͔ʁʯetc… • ࣮ࡍʹ࢖ͬͯΈͯΘ͔Δ͜ͱ΋ଟ͍ • εΩϧΛ஌ͬͯ΋Β͏͜ͱ΋݉Ͷͯɺ஌Γ߹͍ʹ࢖ͬͯ΋Β͓͏ • ެ։લͳΒЌςετʹট଴ɺެ։ޙͳΒϨϏϡʔΛॻ͍ͯ΋Β͏ • ϨϏϡʔ͕૿͑Δͱ͍͍͜ͱ͕͋Δ͔΋ #alexaday2019
  76. I n t e rc e p t o r

    Ͱ R e q u e s t / R e s p o n s e Λ ه ࿥ const RequestLogger = { process (handlerInput: HandlerInput): void { console.log('RequestEnvelope: %j', handlerInput.requestEnvelope) } } const ResponseLogger ={ process (handlerInput: HandlerInput, response: Response): void { console.log(`Response: ${JSON.stringify(response)}`) } } Ask.SkillBuilders.custom() .addRequestHandlers( ... ) .addRequestInterceptors( RequestLogger ) .addResponseInterceptors( ResponseLogger ) .lambda() L o g g e r A D D #alexaday2019
  77. I n t e rc e p t o r

    Ͱ R e q u e s t / R e s p o n s e Λ ه ࿥ • Request / Responseͷϩά͕͋Ε͹࠶ݱͱΓ΍͍͢ • ͨͩ͠slot / sessionʹηϯγςΟϒͳ৘ใ͕ೖΔ͜ͱ͕͋Δ • ϓϥΠόγʔΛѻ͏εΩϧͰ͸ग़ྗલʹϚεΫΛ • Sentry / RollbarͳͲͷπʔϧͰΤϥʔ؂ࢹ͢Δͱ໰୊ʹؾ͖ͮ΍͍͢ #alexaday2019
  78. D e p l o y m e n t

    #alexaday2019
  79. A l e x a S k i l l

    b a c k e n d ͷ σ ϓ ϩ Π ํ ๏ Ұ ྫ • ASK SDKͰask deploy • Code Star + Cloud9Ͱgit push • Serverless FrameworkͰsls deploy • AWS SAMͰdeploy #alexaday2019
  80. A l e x a S k i l l

    b a c k e n d ͷ σ ϓ ϩ Π ํ ๏ Ұ ྫ • ASK SDKͰask deploy • Code Star + Cloud9Ͱgit push • Serverless FrameworkͰsls deploy • AWS SAMͰdeploy #alexaday2019
  81. ͦ Ε ͧ ΕͰ ͷ ఆ ٛ ͷ ॻ ͖

    ํ service: name: my-alexa-skill provider: name: aws runtime: nodejs8.10 functions: alexa: handler: index.alexa events: - alexaSkill: amzn1.ask.skill.xxxxxx AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: Example Alexa skill backend Resources: HelloAlexa: Type: AWS::Serverless::Function Properties: Handler: index.handler Runtime: nodejs8.10 Events: Alexa: Type: AlexaSkill Outputs: HelloAlexaFunction: Description: "Hello Alexa Lambda Function ARN" Value: !GetAtt HelloAlexa.Arn S e r v e r l e s s F r a m e w o r k S A M #alexaday2019
  82. S A M Ͱ ͷ Ξ ο ϓ σ ʔ

    τ • ৽͍͠όʔδϣϯͱͯ͠σϓϩΠ -> ΤΠϦΞεࠩ͠ସ͑Ͱ൓ө • YAMLʹ਺ߦ଍͚ͩ͢ͰCodeDeploy͕ར༻Ͱ͖Δ • ஈ֊తʹτϥϑΟοΫΛৼΓ෼͚Δ͜ͱ͕Մೳ • CloudWatch AlarmͳͲͱ૊Έ߹ΘͤΕ͹ϩʔϧόοΫ΋ • CodePiplineΛ࢖͑͹खಈঝೝͷϫʔΫϑϩʔ΋૊ΊΔ #alexaday2019
  83. S A M Ͱ Ұ ఆ ྔ Τ ϥ ʔ

    ͕ ग़ Δ ͳ Β ϩ ʔ ϧό ο Ϋ ͢ Δ α ϯ ϓϧ # ࠨͷଓ͖ HelloAlexaAlarm: Type: AWS::CloudWatch::Alarm Properties: Namespace: AWS/Lambda Dimensions: - Name: FunctionName Value: !Ref HelloAlexa MetricName: Errors ComparisonOperator: GreaterThanOrEqualToThreshold Statistic: Sum Period: 60 EvaluationPeriods: 1 Threshold: 1 Outputs: HelloAlexaFunction: Description: "Hello Alexa Lambda Function ARN" Value: !GetAtt HelloAlexa.Arn AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: Example Alexa skill backend Resources: HelloAlexa: Type: AWS::Serverless::Function Properties: Handler: index.handler Runtime: nodejs8.10 AutoPublishAlias: live DeploymentPreference: Enabled: true Type: Linear10PercentEvery1Minute Alarms: - !Ref HelloAlexaAlarm
  84. C a n a r y D e p l

    o y ͷ ઃ ఆ # ࠨͷଓ͖ HelloAlexaAlarm: Type: AWS::CloudWatch::Alarm Properties: Namespace: AWS/Lambda Dimensions: - Name: FunctionName Value: !Ref HelloAlexa MetricName: Errors ComparisonOperator: GreaterThanOrEqualToThreshold Statistic: Sum Period: 60 EvaluationPeriods: 1 Threshold: 1 Outputs: HelloAlexaFunction: Description: "Hello Alexa Lambda Function ARN" Value: !GetAtt HelloAlexa.Arn AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: Example Alexa skill backend Resources: HelloAlexa: Type: AWS::Serverless::Function Properties: Handler: index.handler Runtime: nodejs8.10 AutoPublishAlias: live DeploymentPreference: Enabled: true Type: Linear10PercentEvery1Minute Alarms: - !Ref HelloAlexaAlarm
  85. R o l l b a c k ͷ ઃ

    ఆ # ࠨͷଓ͖ HelloAlexaAlarm: Type: AWS::CloudWatch::Alarm Properties: Namespace: AWS/Lambda Dimensions: - Name: FunctionName Value: !Ref HelloAlexa MetricName: Errors ComparisonOperator: GreaterThanOrEqualToThreshold Statistic: Sum Period: 60 EvaluationPeriods: 1 Threshold: 1 Outputs: HelloAlexaFunction: Description: "Hello Alexa Lambda Function ARN" Value: !GetAtt HelloAlexa.Arn AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: Example Alexa skill backend Resources: HelloAlexa: Type: AWS::Serverless::Function Properties: Handler: index.handler Runtime: nodejs8.10 AutoPublishAlias: live DeploymentPreference: Enabled: true Type: Linear10PercentEvery1Minute Alarms: - !Ref HelloAlexaAlarm
  86. S A M ͷ p ro s / c o

    n s • [pros] CloudFormationނʹࣗ༝౓͕ߴ͍ • [pros] σϓϩΠ·ΘΓͷΦϓγϣϯ͕๛෋ • [pros] CodeXX γϦʔζͱͷ૬ੑ͕ྑ͍ • [pros] ϩʔΧϧ࣮ߦ༻ͷπʔϧ΋ެࣜͰ͋Δ • [cons] σϓϩΠίϚϯυ͕ෳࡶʹͳΓ΍͍͢ • [cons] ϩάͷtailίϚϯυ͕ͳ͍ • [cons] CloudFormationΛॻ͔ͳ͍ͱ͍͚ͳ͍
  87. S e r v e r l e s s

    F r a m e w o r k Ͱ ͷ Ξ ο ϓ σ ʔ τ • sls deployͰIAMͳͲͷϦιʔε͝ͱߋ৽Մೳ • sls deploy functionͰLambdaͷιʔεͷΈߋ৽Մೳ • σϑΥϧτͰ͸ΤΠϦΞε͕ͳ͍ͷͰɺ--stageΦϓγϣϯΛ࢖͏ • ৽stage࡞੒ -> ΤϯυϙΠϯτࠩ͠ସ͑ -> ਃ੥ -> چstage ࡟আ • ϓϥάΠϯΛ࢖͑͹Alias΋ར༻Մೳ #alexaday2019
  88. S e r v e r l e s s

    F r a m e w o r k ͷ p ro s / c o n s • [pros] CloudFormationΑΓͬ͘͟Γॻ͚Δ[ಛʹIAM·ΘΓ] • [pros] ೔ຊޠͷ৘ใଟΊɾϑΥʔϥϜ͋Δ • [pros] CLIʹϩάͷtailίϚϯυ͕͋ͬͯσόοάָ͕ • [pros] ର࿩Ϟσϧ؅ཧɾΦϑϥΠϯσόοά౳ͷϓϥάΠϯ͕๛෋ • [cons] σϓϩΠʹखಈ෦෼͕࢒Δ • [cons] ϓϥάΠϯͷબఆΛϛεΔͱͭΒ͍ • [cons] CloudFormationΛ݁ہॻ͘͜ͱʹͳΓ͔Ͷͳ͍
  89. ͦ Ε ͧ ΕͰ ͷ σ ϓ ϩ Π ί

    Ϛ ϯ υ S e r v e r l e s s F r a m e w o r k S A M #alexaday2019
  90. ͦ Ε ͧ ΕͰ ͷ σ ϓ ϩ Π ί

    Ϛ ϯ υ $ sls deploy $ aws cloudformation package \ —template-file ./template.yml \ —output-template-file template-output.yml \ —s3-bucket $S3_BUCKET_NAME $ aws cloudformation deploy \ --template-file ./template-output.yml \ —stack-name helloAlexa \ —capabilities CAPABILITY_IAM \ —region $AWS_REGION S e r v e r l e s s F r a m e w o r k S A M #alexaday2019
  91. ݸ ਓ త ࢖ ͍ ෼ ͚ • ͱΓ͋͑ͣ࡞ͬͯΈ͍ͨ: Hosted

    Skill • GitͰ؅ཧ͍ͨ͠: ASK CLI only • νʔϜͰ։ൃӡ༻͸͡Ί͍ͨɿCode Star • DBͱ͔࢖͍͍ͨ: Serverless Framework • ഁյతมߋΛఆظతʹ΍Γͦ͏ɿSAM • Ϣʔβʔ૿͑ͦ͏͔ͩΒ҆ఆӡ༻͍ͤͨ͞ɿSAM #alexaday2019
  92. A p p e n d i x : C

    o d e S t a r • AlexaεΩϧ։ൃͷͨΊʹඞཁͳϦιʔεΛ਺ΫϦοΫͰ༻ҙ • Cloud9Ͱϒϥ΢β্ͰͷνʔϜ։ൃ΋Մೳ • Code seriesͱ࿈ܞࡁͳͷͰɺσϓϩΠύΠϓϥΠϯ׬උ • ͨͩ͠Alexaͷ։ൃऀίϯιʔϧͰฤूͰ͖ͳ͘ͳΔ • Hosted SkillͷਅٯͷଘࡏʢAmazom or AWS͚ͩͰ؅ཧʣ #alexaday2019
  93. A S K S D K ΁ ͷ D e

    e p D i v e • IDEΛ࢖͑͹ؾʹͳΔϝιουɾύϥϝʔλͷܕఆٛΛ௥͑Δ • ϦΫΤετ஋ / ServiceClient͸ask-sdk-modelΛݟΔ • ֤छϝιου͸جຊతʹask-sdk-coreΛݟΔ • ίΞͷιʔεΛखݩʹclone͓ͯ͘͠ͱίʔυಡΈ͕ḿΔ #alexaday2019
  94. ઃ ܭ ཧ ղ ͸ Q i i t a

    ͷ ͜ ͷ ه ࣄ Λ https://qiita.com/shinichi-takahashi/items/7191d3d393e08b2746f0 #alexaday2019
  95. ཧ ղ ͢ Δ ͜ ͱ Ͱ ΄ ͔ ΁

    ͷ Ԡ ༻ ΋ Մ ೳ ʹ class Get { supports(method: string) { return method === 'GET' } handle(){ return ResponseBuilder.setStatusCode(StatusCode || 200) .setBody({ message: 'hello' }) .getResponse() } } export const handler: APIGatewayProxyHandler = async (event) => { event.body = event.body != null ? JSON.parse(event.body) : null const resolver = new Resolver( new Post(), new Get() ) return resolver.resolve(event.httpMethod).handle(event) } A P I G a t e w a y ʹ Ԡ ༻ ͠ ͨ ྫ #alexaday2019
  96. ந ৅ Խ ͠ ͨ ί ʔ υ Λ ެ

    ։ ͠ Α ͏ • ASK SDK޲͚Utility npm i -S ask-utils • Proactive Event޲͚SDK npm i -S @ask-utils/proactive-event • Amazon Pay޲͚Ϗϧμʔ npm i -S @ask-utils/amazon-pay #alexaday2019
  97. C o n c l u s i o n

    • ASK SDKΛཧղ͢Δ͜ͱͰɺޮ཰తͳ࣮૷͕ՄೳʹͳΔ • TypeScriptͳͲͷ੩తܕ෇͖ݴޠͰIDEͷԸܙΛ͏͚Δ • S3 / DynamoDB / and moreͷ͍͍ͱ͜औΓΛΊͦ͟͏ • ςετͱσϓϩΠΛ҆ఆԽͤͯ͞ɺεΩϧΛΑΓศརʹ #alexaday2019
  98. A p p e n d i x : ࣮

    ͸ ͜ Μ ͳ ؔ ਺ ͋ Γ · ͢ import * as Ask from ‘ask-sdk-core' const client = new Ask.DefaultApiClient() const result = await client.invoke({ method: 'GET', url: 'https://example.com', headers: [] }) import * as Ask from ‘ask-sdk-core’ // ϦΫΤετλΠϓ Ask.getRequestType(requestEnvelope) LaunchRequest // Πϯςϯτ໊ Ask.getIntentName(requestEnvelope) AMAZON.YesIntent // ৽͍͠ηογϣϯ͔൱͔Λ൑ఆ Ask.isNewSession(requestEnvelope) true ֎ ෦ ΁ ͷ F e t c h ॲ ཧ Ϧ Ϋ Τ ε τ ͷ ஋ Λ औ Γ ग़ ͢ ϔ ϧ ύ ʔ #alexaday2019