En el src especial cocinas tu habilidad.
En el nivel superior
Efectivamente tenemos nuestros activos y accesorios de desarrollo. Potencialmente podrías dividirlos en su propio directorio, pero se sentía como una exageración. Así que lo hemos hecho:
- eslint configs que tienen que ser bastante liberales debido al SDK con el que trabajaremos
- tu secreto deploy.env.json (que obviamente crearás tú mismo) que se verá un poco como esto:
12345{"ROLE": "arn:aws:iam:::[123456789012]:role/[el rol lambda de AWS que usted definió]", "ACCESS_KEY_ID": "ABCDEFGHIJKLMNOPQRST", "SECRET_ACCESS_KEY": "abcdefghijklmnopqrst/abcdefghijklmnopqrs"}
json
esto nos permite desplegar nuestro código con una tarea de tragar
- nuestros enums.js que nos ayudarán a manejar el estado de nuestra habilidad, sólo para darnos alguna apariencia de seguridad de tipo en tierra de JavaScript
- un gulpfile.js que detalla nuestras tareas de despliegue. Elimina nuestras cosas viejas de dist, coge todas las cosas que nos importan y las pone en dist, lo comprime porque así es como AWS lambda toma múltiples archivos y usa node-aws-lambda para subir nuestro archivo usando el lambda-config que ha hecho que sus valores sean reemplazados por nuestros secretos de arriba
- index.js – la entrada a nuestra habilidad – establece el camino del entorno para ayudar a nuestra lambda AWS a atravesar nuestros archivos, registra nuestro appId y handlers (a los que llegaremos) y lo pone todo en marcha
- el lambda-config.js que se verá muy familiar en la pantalla de su consola lambda de AWS. Establezca estos valores como desee
- paquete.json el cual – voy a asumir que lo sabes. Los únicos puntos reales a tener en cuenta son los guiones:
- Encontrar una buena manera de desplegar una habilidad de Alexa es un poco de PITA, esto no es un flujo de trabajo de despliegue. Encontré estos dos ejemplos (Rick$0027s y Thoughtworks$0027) de los cuales tomé prestado mucho para mi propio gulpfile. Sin embargo, a ambos les gusta una instalación PROD mientras que yo he optado por un enfoque ligeramente diferente para evitar hacer una instalación cada vez que me despliego. Cuando usted npm i por su habilidad, va a pegar las dependencias PROD directamente en dist
- También he metido ahí un nodo de hierro, con el que he tenido experiencias mixtas, pero que es realmente inestimable cuando quieres depurar algo fácilmente en las herramientas de desarrollo de Chrome. Por favor, siéntete libre de depurar el código de tu nodo de la manera que prefieras, como por ejemplo a través de tu IDE
- respuestas.js debería ser donde pones cualquier cosa que diga Alexa. Las haría como funciones para tener una API consistente donde puedes pasar valores para ser construidos dinámicamente. De hecho, las he refactorizado para llamar a los métodos de emisión con el fin de almacenar las últimas respuestas y estados conocidos para ayudarnos a continuar después de una «pausa». Tener todas las respuestas en un solo lugar facilita las pruebas (ya que puedes cambiar las frases sin romper tus pruebas) y cuando i18n entra, puedes intercambiar este archivo, en lugar de tener un texto codificado duro sobre tu código base
manejadores
Estos son los chicos que reciben las intenciones (lo que Alexa piensa que el usuario dijo basado en sus activos de habla), realizan algún tipo de lógica de negocios, tal vez almacenan algunas cosas en estado de sesión (o dínamo db) y devuelven una respuesta (algo para que Alexa responda).
El material del estado es necesario porque la habilidad es muy tonta, toma algo de JSON, envía algo de JSON. Es debido a esta naturaleza pura, TDD (y las pruebas en general) es tan fácil. Cuando lleguemos a las muestras de los eventos más tarde verás cómo es ese JSON. Es una buena idea mantener a sus manejadores tan delgados como sea posible y mover la lógica de negocio a la carpeta de módulos.
He optado por un enfoque más bien rígido de comentar la intención de cada manipulador para asegurar que siga el mismo patrón cuando se edite. El patrón es, configuración, cambios de estado, respuesta. Como miembro de la iglesia de la programación funcional, habría sido preferible obtener un modelo inmutable en la intención, realizar su lógica, actualizar esa cosa, y luego devolverla con su respuesta. Sin embargo, el SDK ha optado por un enfoque más OO con un uso intensivo de esto para disparar eventos y manejar el estado… lo cual es menos que ideal y ha llevado a algunas soluciones poco acertadas.
Aconsejo tener tantos manipuladores pequeños como pueda manejar, esto hará que el manejo de intenciones ambiguas como «sí» y «no» sea mucho más fácil y ayude a organizar el código y la prueba.
En el kit de inicio sólo tenemos tres manipuladores:
- core.handlers.js – llegaremos a la «certificación» al final, pero por ahora sólo necesitas saber que son necesarios, así que los mezclaremos con todos nuestros otros handlers. AMAZON.StopIntent es básicamente una pausa y AMAZON.CancelIntent es «sácame de aquí». El SessionEndedRequest es sólo un agradable para tener para propósitos de registro
- new-session.handlers.js – este es el punto de entrada de la habilidad (en oposición a index.js que es el punto de entrada del código), estableceremos algún estado aquí (que decide a qué handler ir) y le diremos a nuestra habilidad qué intención ejecutar. También está el «catch-all» aleatorio, que registrará algo realmente inútil…
- stop.handlers.js – porque generalmente se requiere StopIntent y CancelIntent, idealmente queremos que un lugar para manejar sea «pausado». Aquí establecemos el estado de vuelta al estado anterior si el usuario desea continuar y salir si… no quiere continuar
módulos
Aquí es donde debería vivir tu lógica de negocios. Como se mencionó anteriormente, todo el trabajo pesado debería estar aquí. No queremos que los intentos se pongan muy ocupados y sucios porque lo hace difícil de leer y de mantener. He incluido una pequeña e ingenua implementación en el módulo de utilidades para que no tengamos que añadir los intentos de núcleo una y otra vez.
pruebas
Ninguna de las muestras tiene pruebas. No creo que se pueda desarrollar y enviar una habilidad de Alexa sin pruebas. Hay tantos caminos, ramas e intenciones, que tan pronto como añadas un poco de complejidad a tu habilidad, obtendrás bichos.
En la carpeta de pruebas tenemos nuestros accesorios conocidos como muestras de eventos y un archivo de pruebas gigante. Ahora, probablemente podrías dividir esto pero se siente como una «pre-optimización». Me gustó bastante tener todas mis rutas de prueba de habilidades juntas.
Así que tenemos algunos ayudantes en la parte superior:
- sanear sólo para hacer la vida más fácil si has usado una cadena de plantillas de varias líneas y quieres afirmarte en la salida de voz
- getOutputSpeech que sólo desestructura la respuesta, despoja de todo (las etiquetas ssml) y nos permite afirmar en lo que nos importa, lo que Alexa dijo
- getAttribute que de nuevo es una buena abstracción para elegir nuestros atributos de sesión para probarlos
- Finalmente, pero no por ello menos importante, esto es lo que hará que todas sus pruebas sean un sueño. Utiliza un contexto aws-lambda-mock para eliminar todas las cosas de contexto que no nos importan y nos da una API fluida para invocar nuestro intento con facilidad
He optado por un enfoque fuertemente anidado para darle la sensación de una conversación al leer la salida. Así que cada descripción es lo que el usuario diría, luego el nombre de la prueba es lo que crees que tu habilidad debería haber hecho. Como pueden ver, ahora podemos desestructurar y afirmar de forma bastante limpia. Además, el uso de flechas gruesas significa menos tirantes rizados para cada uno de ellos para la victoria.
Dividiría las muestras de los eventos por el estado del controlador. Esto sólo los hace más manejables. Este es un ejemplo de muestra de evento que estaría en el directorio de juego:
12345678910111213141516171819202122232425262728293031323334353637383940414243{"session":{"sessionId": "sessionId", "application":{"applicationId": "appId"}, "attributes":{"playerCount":1, "activePlayer":0, "players":[{"name": "craig", "score":0, "correctAnswers":0}], "STATE": "PLAYING", "startTime": "2016-07-31T16:58: 47Z", "timeOfLastQuestion": "2016-07-31T16:58:47Z", "currentAnswer":6}, "user":{"userId": "userId"}, "new":false}, "request":{"type": "IntentRequest", "requestId": "reqId", "locale": "en-US", "timestamp": "2016-07-31T16:59:00Z", "intent":{"name": "AnswerIntent", "slots":{"Answer":{"name": "Answer", "value": "5"}}}}, "version": "1. 0"}
json
Puedes ver que nuestro estado de juego viene como atributos, junto con la solicitud que tiene un nombre y los valores de la tragaperras de la intención (que por alguna razón duplica el nombre ¯_(ツ)_/¯). Por eso es una belleza para los propósitos de TDD, tomamos este JSON, probamos el siguiente estado y la respuesta, hecho.
Una pequeña nota, necesitarás el Nodo 5.x.x + para obtener los beneficios de ES2015 pero las lambdas de AWS sólo soportan el Nodo 4.3.2 que tiene un soporte limitado de ES2015. Para tener una buena experiencia de desarrollo pero aún así poder probar la habilidad localmente, uso nvm para cambiar fácilmente de uno a otro.