AWS AppSync guía de programación de plantillas de mapeo de resolución - AWS AppSync

Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.

AWS AppSync guía de programación de plantillas de mapeo de resolución

nota

Ahora admitimos principalmente el tiempo de ejecución APPSYNC _JS y su documentación. Considere la posibilidad de utilizar el motor de ejecución APPSYNC _JS y sus guías aquí.

Este es un tutorial de programación al estilo de un libro de cocina con el lenguaje de plantillas Apache Velocity () incluido. VTL AWS AppSync Si está familiarizado con otros lenguajes de programación como JavaScript C o Java, debería ser bastante sencillo.

AWS AppSync utiliza VTL para convertir las solicitudes de GraphQL de los clientes en una solicitud a su fuente de datos. A continuación, invierte el proceso para traducir la respuesta del origen de datos a una respuesta de GraphQL. VTLes un lenguaje de plantillas lógico que permite manipular tanto la solicitud como la respuesta en el flujo estándar de solicitud/respuesta de una aplicación web, mediante técnicas como:

  • Valores predeterminados para nuevos elementos

  • Validación y formato de entrada

  • Transformación y moldeado de datos

  • Iteración en listas, mapas y matrices para puntear o modificar valores

  • Filtrado/cambio de las respuestas en función de la identidad del usuario

  • Comprobaciones de autorización complejas

Por ejemplo, puede ser conveniente validar un número de teléfono del servicio en un argumento de GraphQL o convertir un parámetro de entrada a mayúsculas antes de almacenarlo en DynamoDB. O tal vez desee que los sistemas cliente proporcionen un código, como parte de un argumento de GraphQL, una afirmación de JWT token o un HTTP encabezado, y que solo respondan con datos si el código coincide con una cadena específica de una lista. Todas estas son comprobaciones lógicas con VTL las que puedes realizar. AWS AppSync

VTLpermite aplicar la lógica mediante técnicas de programación que podrían resultarle familiares. Sin embargo, está limitado a ejecutarse dentro del flujo de solicitud/respuesta estándar para garantizar que GraphQL API sea escalable a medida que crece su base de usuarios. AWS Lambda Como AWS AppSync también es compatible con un solucionador, puede escribir funciones Lambda en el lenguaje de programación que prefiera (Node.js, Python, Go, Java, etc.) si necesita más flexibilidad.

Configuración

Una técnica habitual a la hora de aprender un idioma consiste en imprimir los resultados (por ejemplo, console.log(variable) en JavaScript) para ver qué pasa. En este tutorial, lo demostramos mediante la creación de un esquema de GraphQL sencillo y la transferencia de un mapa de valores a una función Lambda. La función Lambda imprime los valores y, a continuación, los utiliza para responder. Esto le permitirá comprender el flujo de solicitud/respuesta y ver diferentes técnicas de programación.

Empiece con la creación del siguiente esquema de GraphQL:

type Query { get(id: ID, meta: String): Thing } type Thing { id: ID! title: String! meta: String } schema { query: Query }

Ahora cree la siguiente AWS Lambda función, utilizando Node.js como idioma:

exports.handler = (event, context, callback) => { console.log('VTL details: ', event); callback(null, event); };

En el panel Fuentes de datos de la AWS AppSync consola, añada esta función Lambda como una nueva fuente de datos. Vuelva a la página de esquema de la consola y haga clic en el ATTACHbotón de la derecha, junto a la get(...):Thing consulta. Para la plantilla de la solicitud, elija la plantilla existente en el menú Invoke and forward arguments (Invocar y reenviar argumentos). Para la plantilla de la respuesta, elija Return Lambda result (Devolver resultado Lambda).

Abra Amazon CloudWatch Logs para su función Lambda en una ubicación y, en la pestaña Consultas de la AWS AppSync consola, ejecute la siguiente consulta de GraphQL:

query test { get(id:123 meta:"testing"){ id meta } }

La respuesta de GraphQL debería incluir id:123 y meta:testing, ya que la función Lambda los devuelve. Transcurridos unos segundos, debería ver también un registro en CloudWatch Logs con estos detalles.

Variables

VTLutiliza referencias, que puede utilizar para almacenar o manipular datos. Hay tres tipos de referencias enVTL: variables, propiedades y métodos. Las variables van precedidas de un signo $ y se crean con la directiva #set:

#set($var = "a string")

Las variables almacenan tipos similares a los de otros lenguajes que ya conoce, como números, cadenas, matrices, listas y mapas. Es posible que haya observado que se envía una JSON carga útil en la plantilla de solicitud predeterminada para los resolutores Lambda:

"payload": $util.toJson($context.arguments)

Un par de cosas a tener en cuenta aquí: en primer lugar, AWS AppSync proporciona varias funciones prácticas para operaciones comunes. En este ejemplo, $util.toJson convierte una variable enJSON. En segundo lugar, la variable $context.arguments se rellena automáticamente desde una solicitud de GraphQL como objeto de mapa. Puede crear un nuevo mapa del modo siguiente:

#set( $myMap = { "id": $context.arguments.id, "meta": "stuff", "upperMeta" : $context.arguments.meta.toUpperCase() } )

Ya ha creado una variable llamada $myMap que contiene las claves id, meta y upperMeta. Esto también ilustra algunos puntos:

  • id se rellena con una clave tomada de los argumentos de GraphQL. Esto es habitual VTL para obtener argumentos de los clientes.

  • meta está codificada literalmente con un valor, lo que ilustra el uso de valores predeterminados.

  • upperMeta transforma el argumento meta con el método .toUpperCase().

Coloque el código anterior en la parte superior de la plantilla de solicitud y cambie payload para que utilice la nueva variable $myMap:

"payload": $util.toJson($myMap)

Ejecute la función Lambda y podrá ver el cambio de respuesta y estos datos en CloudWatch los registros. A medida que vaya avanzando por este tutorial, iremos rellenando $myMap para que pueda ejecutar pruebas similares.

También puede definir properties_ para las variables, Pueden ser cadenas simples, matrices o: JSON

#set($myMap.myProperty = "ABC") #set($myMap.arrProperty = ["Write", "Some", "GraphQL"]) #set($myMap.jsonProperty = { "AppSync" : "Offline and Realtime", "Cognito" : "AuthN and AuthZ" })

Referencias silenciosas

Como VTL es un lenguaje de plantillas, de forma predeterminada, cada referencia que le dé tendrá un.toString(). Si la referencia no está definida, imprime la representación de la referencia real en forma de cadena. Por ejemplo:

#set($myValue = 5) ##Prints '5' $myValue ##Prints '$somethingelse' $somethingelse

Para solucionar este problema, VTL utiliza una sintaxis de referencia silenciosa o silenciosa, que indica al motor de plantillas que suprima este comportamiento. La sintaxis utilizada es $!{}. Por ejemplo, si cambiamos ligeramente el código anterior para utilizar $!{somethingelse}, se suprimirá la impresión:

#set($myValue = 5) ##Prints '5' $myValue ##Nothing prints out $!{somethingelse}

Métodos de llamada

En un ejemplo anterior vimos cómo crear una variable y definir valores de forma simultánea. Esto también puede hacerse en dos pasos, añadiendo datos a un mapa como se muestra a continuación:

#set ($myMap = {}) #set ($myList = []) ##Nothing prints out $!{myMap.put("id", "first value")} ##Prints "first value" $!{myMap.put("id", "another value")} ##Prints true $!{myList.add("something")}

HOWEVERhay algo que saber sobre este comportamiento. Aunque la notación de referencia silenciosa $!{} le permite llamar a métodos, como en el ejemplo anterior, no suprimirá el valor devuelto por el método ejecutado. Esta es la razón por la que observamos ##Prints "first value" y ##Prints true en el ejemplo anterior. Esto puede provocar errores al iterar en mapas o listas, por ejemplo al insertar un valor donde ya hay una clave, ya que la salida añadirá cadenas inesperadas a la plantilla durante la evaluación.

La forma de evitarlo consiste a veces en llamar a los métodos utilizando una directiva #set y pasar por alto la variable. Por ejemplo:

#set ($myMap = {}) #set($discard = $myMap.put("id", "first value"))

Puede utilizar esta técnica en sus plantillas, ya que evita que se impriman cadenas inesperadas en la plantilla. AWS AppSync proporciona una función de conveniencia alternativa que ofrece el mismo comportamiento en una notación más sucinta. Esto le evitará preocuparse por estos detalles de implementación. El acceso a esta función puede obtenerse con $util.quiet() o su alias $util.qr(). Por ejemplo:

#set ($myMap = {}) #set ($myList = []) ##Nothing prints out $util.quiet($myMap.put("id", "first value")) ##Nothing prints out $util.qr($myList.add("something"))

Cadenas

Al igual que ocurre en muchos lenguajes de programación, puede ser difícil tratar con las cadenas, en especial si se quiere crearlas a partir de variables. Se nos ocurren algunas cosas comunes. VTL

Supongamos que desea insertar datos en forma de cadena en un origen de datos como DynamoDB, pero que los rellena desde una variable, como un argumento de GraphQL. En este caso, la cadena tendría comillas dobles, pero para hacer referencia a la variable en una cadena solo se necesita "${}" (y no ! como en la notación de referencia silenciosa). Esto es similar a un literal de plantilla en JavaScript: https://developer.mozilla. org/en-US/docs/Web/JavaScript/Reference/Template_literales

#set($firstname = "Jeff") $!{myMap.put("Firstname", "${firstname}")}

Esto puede observarse en las plantillas de solicitud de DynamoDB, como "author": { "S" : "${context.arguments.author}"}, cuando se usan argumentos de clientes GraphQL o para la generación automática de ID, como "id" : { "S" : "$util.autoId()"}. Esto significa que puede hacer referencia a una variable o al resultado de un método dentro de una cadena para rellenar los datos.

También puede utilizar métodos públicos de la clase String de Java, por ejemplo para extraer una subcadena:

#set($bigstring = "This is a long string, I want to pull out everything after the comma") #set ($comma = $bigstring.indexOf(',')) #set ($comma = $comma +2) #set ($substring = $bigstring.substring($comma)) $util.qr($myMap.put("substring", "${substring}"))

La concatenación de cadenas es también una tarea muy frecuente. Puede efectuarla solo con referencias a variables o con valores estáticos:

#set($s1 = "Hello") #set($s2 = " World") $util.qr($myMap.put("concat","$s1$s2")) $util.qr($myMap.put("concat2","Second $s1 World"))

Bucles

Ahora que ha creado variables y ha llamado a métodos, puede agregar algo de lógica a su código. A diferencia de otros lenguajes, solo VTL permite bucles, donde el número de iteraciones está predeterminado. En Velocity no existe do..while. Este diseño garantiza que el proceso de evaluación termine siempre y proporciona límites para la escalabilidad cuando se ejecutan las operaciones de GraphQL.

Los bucles se crean con #foreach y requieren que proporcione una variable de bucle y un objeto iterable, como una matriz, una lista, un mapa o una colección. Un ejemplo típico de programación con un bucle #foreach es iterar para todos los elementos de una colección e imprimirlos, por lo que en nuestro caso los punteamos y los añadimos al mapa:

#set($start = 0) #set($end = 5) #set($range = [$start..$end]) #foreach($i in $range) ##$util.qr($myMap.put($i, "abc")) ##$util.qr($myMap.put($i, $i.toString()+"foo")) ##Concat variable with string $util.qr($myMap.put($i, "${i}foo")) ##Reference a variable in a string with "${varname}" #end

En este ejemplo se ilustran varios puntos. El primero es el uso de variables con el operador de rango [..] para crear un objeto iterable. A continuación, se hace referencia a cada elemento mediante una variable $i con la que se puede operar. En el ejemplo anterior, también puede ver comentarios, que van precedidos de dos almohadillas ##. Esto también ilustra el uso de la variable de bucle tanto en las claves como en los valores, así como de diferentes métodos de concatenación con cadenas.

Observe que $i es un número entero, por lo que puede llamar a un método .toString(). Para los tipos de GraphQLINT, esto puede resultar útil.

También puede utilizar un operador de rango directamente, por ejemplo:

#foreach($item in [1..5]) ... #end

Matrices

Hasta este momento has estado manipulando un mapa, pero las matrices también son habituales en ellas. VTL Las matrices también dan acceso a algunos métodos subyacentes, como .isEmpty(), .size(), .set(), .get() y .add(), como se muestra a continuación:

#set($array = []) #set($idx = 0) ##adding elements $util.qr($array.add("element in array")) $util.qr($myMap.put("array", $array[$idx])) ##initialize array vals on create #set($arr2 = [42, "a string", 21, "test"]) $util.qr($myMap.put("arr2", $arr2[$idx])) $util.qr($myMap.put("isEmpty", $array.isEmpty())) ##isEmpty == false $util.qr($myMap.put("size", $array.size())) ##Get and set items in an array $util.qr($myMap.put("set", $array.set(0, 'changing array value'))) $util.qr($myMap.put("get", $array.get(0)))

El ejemplo anterior utiliza la notación de índice de matriz para recuperar un elemento con arr2[$idx]. Puede buscar por nombre en un mapa/diccionario de forma similar:

#set($result = { "Author" : "Nadia", "Topic" : "GraphQL" }) $util.qr($myMap.put("Author", $result["Author"]))

Esto es muy habitual para filtrar los resultados devueltos desde los orígenes de datos en plantillas de respuesta aplicando condiciones.

Controles condicionales

La sección anterior #foreach mostró algunos ejemplos del uso de la lógica para transformar datos. VTL También puede aplicar comprobaciones condicionales para evaluar los datos en tiempo de ejecución:

#if(!$array.isEmpty()) $util.qr($myMap.put("ifCheck", "Array not empty")) #else $util.qr($myMap.put("ifCheck", "Your array is empty")) #end

La comprobación #if() anterior de una expresión booleana está bien, pero también puede utilizar operadores y #elseif() para seguir ramificaciones:

#if ($arr2.size() == 0) $util.qr($myMap.put("elseIfCheck", "You forgot to put anything into this array!")) #elseif ($arr2.size() == 1) $util.qr($myMap.put("elseIfCheck", "Good start but please add more stuff")) #else $util.qr($myMap.put("elseIfCheck", "Good job!")) #end

Estos dos ejemplos emplean la negación (!) y la igualdad (==). También pueden usarse ||, &&, >, <, >=, <= y !=.

#set($T = true) #set($F = false) #if ($T || $F) $util.qr($myMap.put("OR", "TRUE")) #end #if ($T && $F) $util.qr($myMap.put("AND", "TRUE")) #end

Nota: En las condiciones solo se consideran falsos los valores Boolean.FALSE y null. El cero (0) y las cadenas vacías (“”) no equivalen a falso.

Operadores

Ningún lenguaje de programación estaría completo sin algunos operadores para realizar algunas acciones matemáticas. A continuación mostramos algunos ejemplos para comenzar:

#set($x = 5) #set($y = 7) #set($z = $x + $y) #set($x-y = $x - $y) #set($xy = $x * $y) #set($xDIVy = $x / $y) #set($xMODy = $x % $y) $util.qr($myMap.put("z", $z)) $util.qr($myMap.put("x-y", $x-y)) $util.qr($myMap.put("x*y", $xy)) $util.qr($myMap.put("x/y", $xDIVy)) $util.qr($myMap.put("x|y", $xMODy))

Uso de bucles y condicionales juntos

Cuando se transforman datos, por ejemploVTL, antes de escribirlos o leerlos de una fuente de datos, es muy común pasar por encima de los objetos y, a continuación, realizar comprobaciones antes de realizar una acción. Combinando algunas de las herramientas de las secciones anteriores se consigue una gran variedad de funciones. Resulta muy útil saber que #foreach proporciona automáticamente .count para cada elemento:

#foreach ($item in $arr2) #set($idx = "item" + $foreach.count) $util.qr($myMap.put($idx, $item)) #end

Por ejemplo, puede que solo desee puntear los valores de un mapa si su tamaño es menor que un valor determinado. Con el recuento, condiciones y la instrucción #break puede hacer lo siguiente:

#set($hashmap = { "DynamoDB" : "https://aws.amazon.com/dynamodb/", "Amplify" : "https://github.com/aws/aws-amplify", "DynamoDB2" : "https://aws.amazon.com/dynamodb/", "Amplify2" : "https://github.com/aws/aws-amplify" }) #foreach ($key in $hashmap.keySet()) #if($foreach.count > 2) #break #end $util.qr($myMap.put($key, $hashmap.get($key))) #end

El #foreach anterior se itera con .keySet(), que puede utilizar en mapas. De este modo tiene acceso para obtener $key y hacer referencia al valor con .get($key). Los argumentos de GraphQL de los clientes AWS AppSync se almacenan como un mapa. También es posible iterar por ellos con .entrySet(), lo que le da acceso tanto a las claves como a los valores como un conjunto, y le permite rellenar otras variables o efectuar comprobaciones condicionales complejas, como una validación o una transformación de la entrada:

#foreach( $entry in $context.arguments.entrySet() ) #if ($entry.key == "XYZ" && $entry.value == "BAD") #set($myvar = "...") #else #break #end #end

Otros ejemplos comunes son el rellenado automático con información predeterminada, como las versiones iniciales de los objetos al sincronizar datos (muy importante en la resolución de conflictos) o el propietario predeterminado de un objeto para las comprobaciones de autorización. Mary creó esta publicación del blog, de modo que:

#set($myMap.owner ="Mary") #set($myMap.defaultOwners = ["Admins", "Editors"])

Context

Ahora que estás más familiarizado con la realización de comprobaciones lógicas en AWS AppSync los resolutoresVTL, echa un vistazo al objeto de contexto:

$util.qr($myMap.put("context", $context))

este objeto contiene toda la información a la que tiene acceso desde una solicitud de GraphQL. Para obtener una explicación detallada, consulte la referencia del contexto.

Filtrado

Hasta ahora, en este tutorial, toda la información de la función Lambda se ha devuelto a la consulta GraphQL con una transformación muy sencilla: JSON

$util.toJson($context.result)

La VTL lógica es igual de poderosa cuando se obtienen respuestas de una fuente de datos, especialmente cuando se comprueban las autorizaciones de los recursos. Veamos algunos ejemplos. En primer lugar, intente cambiar la plantilla de respuesta del siguiente modo:

#set($data = { "id" : "456", "meta" : "Valid Response" }) $util.toJson($data)

Independientemente de lo que ocurra con la operación de GraphQL, se devuelven al cliente los valores codificados literalmente. Cambie esto ligeramente de forma que el campo meta se rellene con la respuesta Lambda definida previamente en el tutorial con el valor elseIfCheck cuando explicamos las instrucciones condicionales:

#set($data = { "id" : "456" }) #foreach($item in $context.result.entrySet()) #if($item.key == "elseIfCheck") $util.qr($data.put("meta", $item.value)) #end #end $util.toJson($data)

$context.result es un mapa, por lo que puede utilizar entrySet() para aplicar la lógica a las claves o a los valores devueltos. Debido a que $context.identity contiene información sobre el usuario que ha realizado la operación de GraphQL, si devuelve información de autorización del origen de datos, entonces podrá decidir si quiere devolver todos, parte o ningún dato a un usuario en función de la lógica aplicada. Cambie la plantilla de respuesta para que tenga el siguiente aspecto:

#if($context.result["id"] == 123) $util.toJson($context.result) #else $util.unauthorized() #end

Si ejecuta la consulta de GraphQL, los datos se devolverán con normalidad. Sin embargo, si cambia el argumento id por algo que no sea 123 (query test { get(id:456 meta:"badrequest"){} }), recibirá un mensaje de error de autorización.

Encontrará más ejemplos de situaciones de autorización en la sección sobre casos de uso de autorización.

Ejemplo de plantilla

Si ha seguido todo el tutorial, probablemente ya habrá creado esta plantilla paso a paso. En caso de que no lo haya hecho, lo incluimos a continuación para copiarlo y realizar pruebas.

Plantilla de solicitud

#set( $myMap = { "id": $context.arguments.id, "meta": "stuff", "upperMeta" : "$context.arguments.meta.toUpperCase()" } ) ##This is how you would do it in two steps with a "quiet reference" and you can use it for invoking methods, such as .put() to add items to a Map #set ($myMap2 = {}) $util.qr($myMap2.put("id", "first value")) ## Properties are created with a dot notation #set($myMap.myProperty = "ABC") #set($myMap.arrProperty = ["Write", "Some", "GraphQL"]) #set($myMap.jsonProperty = { "AppSync" : "Offline and Realtime", "Cognito" : "AuthN and AuthZ" }) ##When you are inside a string and just have ${} without ! it means stuff inside curly braces are a reference #set($firstname = "Jeff") $util.qr($myMap.put("Firstname", "${firstname}")) #set($bigstring = "This is a long string, I want to pull out everything after the comma") #set ($comma = $bigstring.indexOf(',')) #set ($comma = $comma +2) #set ($substring = $bigstring.substring($comma)) $util.qr($myMap.put("substring", "${substring}")) ##Classic for-each loop over N items: #set($start = 0) #set($end = 5) #set($range = [$start..$end]) #foreach($i in $range) ##Can also use range operator directly like #foreach($item in [1...5]) ##$util.qr($myMap.put($i, "abc")) ##$util.qr($myMap.put($i, $i.toString()+"foo")) ##Concat variable with string $util.qr($myMap.put($i, "${i}foo")) ##Reference a variable in a string with "${varname)" #end ##Operators don't work #set($x = 5) #set($y = 7) #set($z = $x + $y) #set($x-y = $x - $y) #set($xy = $x * $y) #set($xDIVy = $x / $y) #set($xMODy = $x % $y) $util.qr($myMap.put("z", $z)) $util.qr($myMap.put("x-y", $x-y)) $util.qr($myMap.put("x*y", $xy)) $util.qr($myMap.put("x/y", $xDIVy)) $util.qr($myMap.put("x|y", $xMODy)) ##arrays #set($array = ["first"]) #set($idx = 0) $util.qr($myMap.put("array", $array[$idx])) ##initialize array vals on create #set($arr2 = [42, "a string", 21, "test"]) $util.qr($myMap.put("arr2", $arr2[$idx])) $util.qr($myMap.put("isEmpty", $array.isEmpty())) ##Returns false $util.qr($myMap.put("size", $array.size())) ##Get and set items in an array $util.qr($myMap.put("set", $array.set(0, 'changing array value'))) $util.qr($myMap.put("get", $array.get(0))) ##Lookup by name from a Map/dictionary in a similar way: #set($result = { "Author" : "Nadia", "Topic" : "GraphQL" }) $util.qr($myMap.put("Author", $result["Author"])) ##Conditional examples #if(!$array.isEmpty()) $util.qr($myMap.put("ifCheck", "Array not empty")) #else $util.qr($myMap.put("ifCheck", "Your array is empty")) #end #if ($arr2.size() == 0) $util.qr($myMap.put("elseIfCheck", "You forgot to put anything into this array!")) #elseif ($arr2.size() == 1) $util.qr($myMap.put("elseIfCheck", "Good start but please add more stuff")) #else $util.qr($myMap.put("elseIfCheck", "Good job!")) #end ##Above showed negation(!) and equality (==), we can also use OR, AND, >, <, >=, <=, and != #set($T = true) #set($F = false) #if ($T || $F) $util.qr($myMap.put("OR", "TRUE")) #end #if ($T && $F) $util.qr($myMap.put("AND", "TRUE")) #end ##Using the foreach loop counter - $foreach.count #foreach ($item in $arr2) #set($idx = "item" + $foreach.count) $util.qr($myMap.put($idx, $item)) #end ##Using a Map and plucking out keys/vals #set($hashmap = { "DynamoDB" : "https://aws.amazon.com/dynamodb/", "Amplify" : "https://github.com/aws/aws-amplify", "DynamoDB2" : "https://aws.amazon.com/dynamodb/", "Amplify2" : "https://github.com/aws/aws-amplify" }) #foreach ($key in $hashmap.keySet()) #if($foreach.count > 2) #break #end $util.qr($myMap.put($key, $hashmap.get($key))) #end ##concatenate strings #set($s1 = "Hello") #set($s2 = " World") $util.qr($myMap.put("concat","$s1$s2")) $util.qr($myMap.put("concat2","Second $s1 World")) $util.qr($myMap.put("context", $context)) { "version" : "2017-02-28", "operation": "Invoke", "payload": $util.toJson($myMap) }

Plantilla de respuesta

#set($data = { "id" : "456" }) #foreach($item in $context.result.entrySet()) ##$context.result is a MAP so we use entrySet() #if($item.key == "ifCheck") $util.qr($data.put("meta", "$item.value")) #end #end ##Uncomment this out if you want to test and remove the below #if check ##$util.toJson($data) #if($context.result["id"] == 123) $util.toJson($context.result) #else $util.unauthorized() #end