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
PRO

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. None
  5. None
  6. E c h o ΁ ͷ ൃ ࿩ ͕ L

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

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

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

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

    Ask.SkillBuilders.custom() .addRequestHandlers(...) .lambda() #alexaday2019
  11. 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
  12. 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
  13. 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
  14. 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
  15. Ϧ Ϋ Τ ε τ ಺ ༰ ( 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
  16. L a m b d a ͷ ֤ छ ৘

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

    λ ͳ Ͳ ͷ ॲ ཧ ʹ ࢖ ͏ 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. 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
  20. ॏ ཁ ౓ : ੺ ৭ > ࠇ ৭ >

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

    τ Λ ॲ ཧ ͢ Δ #alexaday2019
  22. ͢ ͝ ͍ 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
  23. ֮ ͑Β Ε ͳ ͍ #alexaday2019

  24. A l e x a ͷ Ϧ Ϋ Τ ε

    τ Λ ॲ ཧ ͢ Δ ɹ ɹ ɹ ɹ ɹ ͨ Ί ͷ ੩ త ܕ ෇ ͖ ݴ ޠ ೖ ໳ #alexaday2019
  25. #alexaday2019

  26. A l e x a Ͱ ͸ ͡ Ί Δ

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

  28. ↓ ೖ ྗ ஋ ͷ ν Σ ο Ϋ #alexaday2019

  29. ܕ ৘ ใ ͸ 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
  30. 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
  31. Ty p e S c r i p t ͷ

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

    ϩ Π • ࣄલʹ.ts -> .jsͷม׵͕ඞཁ • hooks/pre_deploy_hook.shΛ׆༻͢Δͱɺ
 ask deployͰϏϧυ -> σϓϩΠΛ࣮ߦՄೳ • npm prune —production΋ೖΕΔ͜ͱͰɺdevআ֎ #alexaday2019
  33. / 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
  34. 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
  35. S k i l l B u i l d

    e r s Ͳ ͬ ͪ Λ ࢖ ͏ ͔ ໰ ୊ #alexaday2019
  36. 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
  37. ΄ ΅ ಉ ͡ ಈ ͖ Λ ͢ Δ ί

    ʔ υ 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
  38. 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
  39. 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
  40. 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
  41. ͬ͟ ͘ Γ Ϣ ʔεέ ʔε Case Standard / Custom

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

    ୊ #alexaday2019
  43. 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
  44. 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
  45. 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
  46. S D K ͷ ࢖ ͍ ํ ͩ ͱ D

    y n a m o D B ɹ ɹ ɹ ɹ ɹ ׆ ༻ ͠ ͖ Εͯ ͳ ͍ આ #alexaday2019
  47. 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
  48. Ϋ Τ Ϧ ͱ ͔ ͠ ͨ ͘ ͳ Δ

    ͱ ɺ ͜ ͏ ͠ ͨ ͍ 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
  49. O R Ͱ ͸ ͳ ͘ A N D ͱ

    ͍ ͏ ߟ ͑ ํ • શ݅औಘ͕ۤखͳDynamoDB • ࡉ͔͍ΫΤϦ΍੔߹ੑͷ֬อ͕ۤखͳS3 • AWS-SDKͱڞʹซ༻ͯۤ͠ख෼໺ΛΧόʔ͢Δ • ྫɿ࣌ܥྻͷϩάσʔλ͚ͩS3ʹྲྀ͠ࠐΉ • ྫɿRead͚ͩසൟͳσʔλ͸S3ɾWrite͕ଟ͍σʔλ͸DynamoDB #alexaday2019
  50. https://speakerdeck.com/k1low/php-conference-2017?slide=50 A p p e n d i x :

    ࣌ ܥ ྻ σ ʔ λ ͷ Ұ ཡ ͷ ࡞ Γ ํ
  51. ΄ ΅ ಉ ͡ ಈ ͖ Λ ͢ Δ ί

    ʔ υ Λ 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
  52. 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
  53. ϑ Ν Πϧ ໊ Λ 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
  54. 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
  55. R e q u e s t H a n

    d l e r Λ ָ ʹ ॻ ͘ #alexaday2019
  56. 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
  57. 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
  58. ෳ ਺ ొ ࿥ ͠ ͩ ͢ͱ Χ Φ ε

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

    Γ ҆ શ ͳ ɹ R e q u e s t H a n d l e r Λ ࡞ Δ #alexaday2019
  60. 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
  61. 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
  62. 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
  63. ࣗ લ Ͱ 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
  64. ࣗ લ Ͱ 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
  65. ࣗ લ Ͱ 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
  66. ͜ ͏ ͢ Δ ͱ 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
  67. I n t e n t ͱ C o n

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

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

    Τ ε τ ͕ ม Θ Δ ྫ • Ϣʔβʔʮ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
  70. 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
  71. 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
  72. ܧ ঝ ͢ Δ 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
  73. ୈ ࡾ Ҿ ਺ ʹ ্ ॻ ͖ ͢ Δ

    ಺ ༰ Λ ͔ ͚ ͹ 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
  74. Te s t i n g y o u r

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

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

    Λ ॲ ཧ ͢ Δ ϋ ϯ υ ϥ ʔ 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
  77. 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}.` }
  78. ݁ ߹ ςε τ : V i r t u

    a l A l e x a https://github.com/bespoken/virtual-alexa #alexaday2019
  79. 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
  80. 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
  81. 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
  82. β ςε τ ɾ Ϩ Ϗϡ ʔ ґ པ ͷ

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

    #alexaday2019
  86. 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
  87. 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
  88. ͦ Ε ͧ ΕͰ ͷ ఆ ٛ ͷ ॻ ͖

    ํ 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
  89. S A M Ͱ ͷ Ξ ο ϓ σ ʔ

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

    n s • [pros] CloudFormationނʹࣗ༝౓͕ߴ͍ • [pros] σϓϩΠ·ΘΓͷΦϓγϣϯ͕๛෋ • [pros] CodeXX γϦʔζͱͷ૬ੑ͕ྑ͍ • [pros] ϩʔΧϧ࣮ߦ༻ͷπʔϧ΋ެࣜͰ͋Δ • [cons] σϓϩΠίϚϯυ͕ෳࡶʹͳΓ΍͍͢ • [cons] ϩάͷtailίϚϯυ͕ͳ͍ • [cons] CloudFormationΛॻ͔ͳ͍ͱ͍͚ͳ͍
  94. 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
  95. 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Λ݁ہॻ͘͜ͱʹͳΓ͔Ͷͳ͍
  96. ͦ Ε ͧ ΕͰ ͷ σ ϓ ϩ Π ί

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

    Ϛ ϯ υ $ 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
  98. ݸ ਓ త ࢖ ͍ ෼ ͚ • ͱΓ͋͑ͣ࡞ͬͯΈ͍ͨ: Hosted

    Skill • GitͰ؅ཧ͍ͨ͠: ASK CLI only • νʔϜͰ։ൃӡ༻͸͡Ί͍ͨɿCode Star • DBͱ͔࢖͍͍ͨ: Serverless Framework • ഁյతมߋΛఆظతʹ΍Γͦ͏ɿSAM • Ϣʔβʔ૿͑ͦ͏͔ͩΒ҆ఆӡ༻͍ͤͨ͞ɿSAM #alexaday2019
  99. 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
  100. A S K S D K ΁ ͷ D e

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

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

    ͷ Ԡ ༻ ΋ Մ ೳ ʹ 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
  103. ந ৅ Խ ͠ ͨ ί ʔ υ Λ ެ

    ։ ͠ Α ͏ • 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
  104. C o n c l u s i o n

    • ASK SDKΛཧղ͢Δ͜ͱͰɺޮ཰తͳ࣮૷͕ՄೳʹͳΔ • TypeScriptͳͲͷ੩తܕ෇͖ݴޠͰIDEͷԸܙΛ͏͚Δ • S3 / DynamoDB / and moreͷ͍͍ͱ͜औΓΛΊͦ͟͏ • ςετͱσϓϩΠΛ҆ఆԽͤͯ͞ɺεΩϧΛΑΓศརʹ #alexaday2019
  105. 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