Deep Dive Development Alexa Skill by Node.js

Deep Dive Development Alexa Skill by Node.js

Alexa day 2019

75486cbfd37125f121cf4a6c5614601c?s=128

Hidetaka Okamoto

April 06, 2019
Tweet

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