Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

L e a r n a b o u t … • ASK SDK • Developing Alexa Skills by TypeScript • Difference of S3 / DynamoDB • Testing & Deployment #alexaday2019

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

E c h o ΁ ͷ ൃ ࿩ ͕ L a m b d a ʹ ಧ ͘ · Ͱ #alexaday2019

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

A S K S D K ͕ ΍ ͬͯ ͍ Δ ͜ ͱ #alexaday2019

Slide 10

Slide 10 text

Ұ ൠ త ͳ ॻ ͖ ํ export handler = Ask.SkillBuilders.custom() .addRequestHandlers(...) .lambda() #alexaday2019

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Ϧ Ϋ Τ ε τ ಺ ༰ ( 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

Slide 16

Slide 16 text

L a m b d a ͷ ֤ छ ৘ ใ ͕ ೖ ͬͯ ͍ Δ handerInput: { requestEnvelope: Alexa͔ΒͷϦΫΤετ಺༰(=event), context: LambdaͷίϯςΩετ(=context), attributesManager: attributeૢ࡞ܥʹ͔ͭ͏, responseBuilder: Ϩεϙϯεੜ੒ʹ͔ͭ͏, serviceClientFactory: ֤छAPIαʔϏεʹ઀ଓ͢ΔͨΊʹ͔ͭ͏ } #alexaday2019

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

A l e x a ͷ Ϧ Ϋ Τ ε τ Λ ॲ ཧ ͢ Δ #alexaday2019

Slide 22

Slide 22 text

͢ ͝ ͍ 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

Slide 23

Slide 23 text

֮ ͑Β Ε ͳ ͍ #alexaday2019

Slide 24

Slide 24 text

A l e x a ͷ Ϧ Ϋ Τ ε τ Λ ॲ ཧ ͢ Δ ɹ ɹ ɹ ɹ ɹ ͨ Ί ͷ ੩ త ܕ ෇ ͖ ݴ ޠ ೖ ໳ #alexaday2019

Slide 25

Slide 25 text

#alexaday2019

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

↑ ೖ ྗ ิ ׬ ͷ ྫ #alexaday2019

Slide 28

Slide 28 text

↓ ೖ ྗ ஋ ͷ ν Σ ο Ϋ #alexaday2019

Slide 29

Slide 29 text

ܕ ৘ ใ ͸ 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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

Ty p e S c r i p t ͷ η ο τΞ ο ϓ // build $ ./node_modules/.bin/tsc // package.jsonʹҎԼΛ଍͓ͯ͘͠ͱɺ // `npm run build`ͰϏϧυͰ͖Δ "scripts": { "build": "tsc" } #alexaday2019

Slide 32

Slide 32 text

A S K C L I Ͱ ͷ σ ϓ ϩ Π • ࣄલʹ.ts -> .jsͷม׵͕ඞཁ • hooks/pre_deploy_hook.shΛ׆༻͢Δͱɺ
 ask deployͰϏϧυ -> σϓϩΠΛ࣮ߦՄೳ • npm prune —production΋ೖΕΔ͜ͱͰɺdevআ֎ #alexaday2019

Slide 33

Slide 33 text

/ 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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

S k i l l B u i l d e r s Ͳ ͬ ͪ Λ ࢖ ͏ ͔ ໰ ୊ #alexaday2019

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

΄ ΅ ಉ ͡ ಈ ͖ Λ ͢ Δ ί ʔ υ 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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

ͬ͟ ͘ Γ Ϣ ʔεέ ʔε Case Standard / Custom ɹDatabaseΛ؆୯ʹಋೖ͍ͨ͠ Standard ɹCustomer ProfileͳͲͷApiΛ࢖͍͍ͨ Standard ɹHosted SkillͰDatabase࢖͍͍ͨ Custom ɹS3ΛDatabaseʹ͍ͨ͠ Custom ɹ͍Ζ͍ΖઃఆΛΧελϜ͍ͨ͠ Custom #alexaday2019

Slide 42

Slide 42 text

D B Ͳ ͬ ͪ Λ ࢖ ͏ ͔ ໰ ୊ #alexaday2019

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

S D K ͷ ࢖ ͍ ํ ͩ ͱ D y n a m o D B ɹ ɹ ɹ ɹ ɹ ׆ ༻ ͠ ͖ Εͯ ͳ ͍ આ #alexaday2019

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

Ϋ Τ Ϧ ͱ ͔ ͠ ͨ ͘ ͳ Δ ͱ ɺ ͜ ͏ ͠ ͨ ͍ 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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

https://speakerdeck.com/k1low/php-conference-2017?slide=50 A p p e n d i x : ࣌ ܥ ྻ σ ʔ λ ͷ Ұ ཡ ͷ ࡞ Γ ํ

Slide 51

Slide 51 text

΄ ΅ ಉ ͡ ಈ ͖ Λ ͢ Δ ί ʔ υ Λ 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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

ϑ Ν Πϧ ໊ Λ 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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

R e q u e s t H a n d l e r Λ ָ ʹ ॻ ͘ #alexaday2019

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

I n t e n t R e q u e s t ͸ ল ུ ه ๏ ͕ ͋ Δ addRequestHandler( matcher : ((input : HandlerInput) => Promise | boolean) | string, executor : (input : HandlerInput) => Promise | 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

Slide 58

Slide 58 text

ෳ ਺ ొ ࿥ ͠ ͩ ͢ͱ Χ Φ ε ʹ ͳΔ ͷ Ͱ ஫ ҙ 3 Π ϯ ς ϯ τ ଍ ͢ ͩ ͚ Ͱ ͜͏ ͳΔ - > #alexaday2019

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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 = { canHandle(handlerInput) { return true }, handle(handlerInput) { return handlerInput.responseBuilder .speak('test') .getResponse() } } ௨ ৗ ্ ॻ ͖ #alexaday2019

Slide 63

Slide 63 text

ࣗ લ Ͱ 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 extends RequestEnvelope { request: CustomRequest } export interface MyCustomHandlerInput extends HandlerInput { requestEnvelope : MyCustomeRequestEnvelope } // requestͷܕΛ্ॻ͖Ͱ͖ΔΑ͏ʹͨ͠handlerͷInterface export interface MySkillRequestHandler extends RequestHandler {} #alexaday2019

Slide 64

Slide 64 text

ࣗ લ Ͱ 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 extends RequestEnvelope { request: CustomRequest } export interface MyCustomHandlerInput extends HandlerInput { requestEnvelope : MyCustomeRequestEnvelope } // requestͷܕΛ্ॻ͖Ͱ͖ΔΑ͏ʹͨ͠handlerͷInterface export interface MySkillRequestHandler extends RequestHandler {} #alexaday2019

Slide 65

Slide 65 text

ࣗ લ Ͱ 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 extends RequestEnvelope { request: CustomRequest } export interface MyCustomHandlerInput extends HandlerInput { requestEnvelope : MyCustomeRequestEnvelope } // requestͷܕΛ্ॻ͖Ͱ͖ΔΑ͏ʹͨ͠handlerͷInterface export interface MySkillRequestHandler extends RequestHandler {} #alexaday2019

Slide 66

Slide 66 text

͜ ͏ ͢ Δ ͱ I n t e n t R e q u e s t ͷ ͨ Ί ͷ h a n d l e r ʹ Ͱ ͖ Δ interface MyNewIntentHandlerInput extends MyCustomHandlerInput {} export const testHandler: MySkillRequestHandler = { 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

Slide 67

Slide 67 text

I n t e n t ͱ C o n t e x t ʢ ҙ ਤ ͱ จ ຺ ʣ #alexaday2019

Slide 68

Slide 68 text

Ye s / N o I n t e n t ͳ Ͳ ͸ ɺ จ ຺ Ͱ ҙ ຯ ͕ ม Θ Δ • ʮͳʹʹର͢Δ͸͍ɾ͍͍͑ʯͳͷ͔ • Session / Persistant AttributeͰจ຺ʢContextʣΛه࿥͢Δ • ContextʹԠͯ͡ॲཧͷ಺༰Λม͑Δඞཁ͕͋Δ #alexaday2019

Slide 69

Slide 69 text

ಉ ͡ ҙ ਤ ͩ ͕ དྷ Δ Ϧ Ϋ Τ ε τ ͕ ม Θ Δ ྫ • Ϣʔβʔʮ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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

ܧ ঝ ͢ Δ 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

Slide 73

Slide 73 text

ୈ ࡾ Ҿ ਺ ʹ ্ ॻ ͖ ͢ Δ ಺ ༰ Λ ͔ ͚ ͹ 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

Slide 74

Slide 74 text

Te s t i n g y o u r S k i l l #alexaday2019

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

ͨ ͱ ͑ ͹ ε ϩ ο τ ̎ ͭ Λ ॲ ཧ ͢ Δ ϋ ϯ υ ϥ ʔ 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

Slide 77

Slide 77 text

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}.` }

Slide 78

Slide 78 text

݁ ߹ ςε τ : V i r t u a l A l e x a https://github.com/bespoken/virtual-alexa #alexaday2019

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

D e p l o y m e n t #alexaday2019

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

ͦ Ε ͧ ΕͰ ͷ ఆ ٛ ͷ ॻ ͖ ํ 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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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Λ݁ہॻ͘͜ͱʹͳΓ͔Ͷͳ͍

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

ͦ Ε ͧ ΕͰ ͷ σ ϓ ϩ Π ί Ϛ ϯ υ $ 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

Slide 98

Slide 98 text

ݸ ਓ త ࢖ ͍ ෼ ͚ • ͱΓ͋͑ͣ࡞ͬͯΈ͍ͨ: Hosted Skill • GitͰ؅ཧ͍ͨ͠: ASK CLI only • νʔϜͰ։ൃӡ༻͸͡Ί͍ͨɿCode Star • DBͱ͔࢖͍͍ͨ: Serverless Framework • ഁյతมߋΛఆظతʹ΍Γͦ͏ɿSAM • Ϣʔβʔ૿͑ͦ͏͔ͩΒ҆ఆӡ༻͍ͤͨ͞ɿSAM #alexaday2019

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

ઃ ܭ ཧ ղ ͸ Q i i t a ͷ ͜ ͷ ه ࣄ Λ https://qiita.com/shinichi-takahashi/items/7191d3d393e08b2746f0 #alexaday2019

Slide 102

Slide 102 text

ཧ ղ ͢ Δ ͜ ͱ Ͱ ΄ ͔ ΁ ͷ Ԡ ༻ ΋ Մ ೳ ʹ 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

Slide 103

Slide 103 text

ந ৅ Խ ͠ ͨ ί ʔ υ Λ ެ ։ ͠ Α ͏ • 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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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