AWS AppSync guia de programação do modelo de mapeamento do resolvedor - AWS AppSync

As traduções são geradas por tradução automática. Em caso de conflito entre o conteúdo da tradução e da versão original em inglês, a versão em inglês prevalecerá.

AWS AppSync guia de programação do modelo de mapeamento do resolvedor

nota

Agora, oferecemos suporte principalmente ao tempo de execução APPSYNC _JS e sua documentação. Considere usar o tempo de execução APPSYNC _JS e seus guias aqui.

Este é um tutorial de programação em estilo de livro de receitas com a Apache Velocity Template Language () em. VTL AWS AppSync Se você estiver familiarizado com outras linguagens de programação JavaScript, como C ou Java, isso deve ser bastante simples.

AWS AppSync usa VTL para traduzir solicitações do GraphQL de clientes em uma solicitação para sua fonte de dados. Em seguida, ele inverte o processo para traduzir a resposta da fonte de dados de volta como uma resposta do GraphQL. VTLé uma linguagem de modelo lógico que permite manipular a solicitação e a resposta no fluxo padrão de solicitação/resposta de um aplicativo web, usando técnicas como:

  • Valores padrão para novos itens

  • Validação de entrada e formatação

  • Transformação e modelagem de dados

  • Iteração por meio de listas, mapas e matrizes para arrancar ou alterar valores

  • Filtrar/alterar respostas com base na identidade do usuário

  • Verificações de autorização complexas

Por exemplo, você pode executar uma validação do número de telefone no serviço em um argumento do GraphQL ou converter um parâmetro de entrada para letras maiúsculas antes de armazená-lo no DynamoDB. Ou talvez você queira que os sistemas cliente forneçam um código, como parte de um argumento, declaração de JWT token ou HTTP cabeçalho do GraphQL, e só respondam com dados se o código corresponder a uma string específica em uma lista. Todas essas são verificações lógicas que você pode realizar com o VTL in AWS AppSync.

VTLpermite aplicar a lógica usando técnicas de programação que podem ser familiares. No entanto, ele é limitado a ser executado dentro do fluxo padrão de solicitação/resposta para garantir que seu GraphQL API seja escalável à medida que sua base de usuários cresce. Como AWS AppSync também oferece suporte AWS Lambda como resolvedor, você pode escrever funções Lambda na linguagem de programação de sua escolha (Node.js, Python, Go, Java etc.) se precisar de mais flexibilidade.

Configuração

Uma técnica comum ao aprender um idioma é imprimir resultados (por exemplo, console.log(variable) em JavaScript) para ver o que acontece. Nesse tutorial, demonstramos isso ao criar um esquema simples do GraphQL e enviar um mapa de valores para uma função do Lambda. A função do Lambda imprime os valores e, em seguida, responde com eles. Isso permitirá que você entenda o fluxo de solicitação/resposta e veja diferentes técnicas de programação.

Comece criando o seguinte esquema do GraphQL:

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

Agora, crie a AWS Lambda função a seguir, usando Node.js como linguagem:

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

No painel Fontes de dados do AWS AppSync console, adicione essa função Lambda como uma nova fonte de dados. Volte para a página Esquema do console e clique no ATTACHbotão à direita, ao lado da get(...):Thing consulta. Para o modelo de solicitação, selecione o modelo existente no menu Invoke and forward arguments (Invocar e encaminhar argumentos). Para o modelo de resposta, selecione Return Lambda result (Retornar o resultado Lambda).

Abra o Amazon CloudWatch Logs para sua função Lambda em um único local e, na guia Consultas do AWS AppSync console, execute a seguinte consulta do GraphQL:

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

A resposta do GraphQL deve conter id:123 e meta:testing, pois a função do Lambda está repetindo-os. Depois de alguns segundos, você também verá um registro no CloudWatch Logs com esses detalhes.

Variáveis

VTLusa referências, que você pode usar para armazenar ou manipular dados. Há três tipos de referências emVTL: variáveis, propriedades e métodos. As variáveis têm um sinal $ na frente e são criadas com a diretiva #set:

#set($var = "a string")

As variáveis armazenam tipos semelhantes com os quais você está familiarizado de outras linguagens, como números, strings, matrizes, listas e mapas. Você deve ter notado uma JSON carga sendo enviada no modelo de solicitação padrão para resolvedores Lambda:

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

Algumas coisas a serem observadas aqui: primeiro, AWS AppSync fornece várias funções de conveniência para operações comuns. Neste exemplo, $util.toJson converte uma variável em. JSON Em segundo lugar, a variável $context.arguments será preenchida automaticamente a partir de uma solicitação do GraphQL como um objeto mapa. Você pode criar um novo mapa da seguinte forma:

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

Agora você criou uma variável chamada $myMap, que tem chaves de id, meta e upperMeta. Isso também demonstra algumas coisas:

  • id é preenchido com uma chave dos argumentos do GraphQL. Isso é comum VTL para obter argumentos de clientes.

  • meta é codificado com um valor, exibindo valores padrão.

  • upperMeta está transformando o argumento meta usando um método .toUpperCase().

Coloque o código anterior na parte superior do modelo de solicitação e altere a payload para usar a nova variável $myMap:

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

Execute sua função Lambda e você poderá ver a mudança de resposta e esses dados nos CloudWatch registros. À medida que você avança pelo restante desse tutorial, continuaremos a preencher $myMap para que você possa executar testes semelhantes.

Você também pode definir properties_ nas variáveis. Podem ser cadeias de caracteres simples, matrizes ou: JSON

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

Referências silenciosas

Por ser VTL uma linguagem de modelagem, por padrão, cada referência que você fornecer a ela fará uma.toString(). Se a referência não estiver definida, ela imprime a representação de referência real, como uma string. Por exemplo:

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

Para resolver isso, VTL tem uma sintaxe de referência silenciosa ou referência silenciosa, que instrui o mecanismo de modelo a suprimir esse comportamento. A sintaxe para isso é $!{}. Por exemplo, se alterarmos o código anterior levemente para usar $!{somethingelse}, a impressão será inibida:

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

Métodos de chamada

Em um exemplo anterior, mostramos como criar uma variável definir valores simultaneamente. Isso também pode ser feito em duas etapas adicionando dados ao mapa, conforme mostrado a seguir:

#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")}

HOWEVERhá algo a saber sobre esse comportamento. Embora a notação da referência silenciosa $!{} permita que você chame métodos, como acima, ela NÃO inibirá o valor retornado do método executado. É por isso que observamos ##Prints "first value" e ##Prints true acima. Isso pode causar erros ao fazer a iteração pelos mapas ou listas, como inserir um valor onde uma chave já existe, porque a saída adiciona strings inesperadas ao modelo no momento da avaliação.

A alternativa para isso é chamar os métodos usando uma diretiva #set e ignorar a variável. Por exemplo:

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

Você pode usar essa técnica em seus modelos, pois ela evita que as sequências inesperadas sejam impressas no modelo. AWS AppSync fornece uma função de conveniência alternativa que oferece o mesmo comportamento em uma notação mais sucinta. Isso permite que você não tenha que pensar sobre essas especificações da implementação. Acesse essa função em $util.quiet() ou seu alias $util.qr(). Por exemplo:

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

Strings

Assim como ocorre com muitas linguagens de programação, as strings podem ser difíceis de lidar, especialmente quando você deseja criá-las a partir de variáveis. Existem algumas coisas comuns que surgemVTL.

Digamos que você esteja inserindo dados como uma string em uma fonte de dados como o DynamoDB, mas ela seja preenchida a partir de uma variável, como um argumento do GraphQL. Uma string terá aspas duplas e para referenciar a variável em uma string basta "${}" (portanto, sem !, como na notação de referência silenciosa). Isso é semelhante a um modelo literal em JavaScript: https://developer.mozilla. org/en-US/docs/Web/JavaScript/Reference/Template_literais

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

Você pode ver isso em modelos de solicitação do DynamoDB como "author": { "S" : "${context.arguments.author}"} ao usar argumentos de clientes do GraphQL ou para geração automática de ID como "id" : { "S" : "$util.autoId()"}. Isso significa que você pode fazer referência a uma variável ou ao resultado de um método dentro de uma string para preencher os dados.

Você também pode usar métodos públicos da classe String em Java, como remover uma substring:

#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}"))

A concatenação de string também é uma tarefa muito comum. Você pode fazer isso apenas com referências a variáveis ou com valores estáticos:

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

Loops

Agora que você criou variáveis e chamou métodos, adicione alguma lógica ao seu código. Ao contrário de outras linguagens, VTL permite apenas loops, onde o número de iterações é predeterminado. Não há do..while em Velocidade. Esse design garante que o processo de avaliação sempre é encerrado e fornece limites para escalabilidade ao executar as operações do GraphQL.

Os loops são criados com um #foreach e exigem que você forneça uma variável de loop e um objeto iterável, como uma matriz, lista, mapa ou coleção. Um exemplo de programação clássico com um loop #foreach é fazer loop pelos itens de uma coleção e imprimi-los, portanto em nosso caso os extraímos e adicionamos ao 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

Esse exemplo mostra algumas coisas. O primeiro é o uso de variáveis com o operador [..] de intervalo para criar um objeto iterável. Em seguida, cada item é referenciado por uma variável $i com a qual você pode operar. No exemplo anterior, vemos também Comentários indicados por uma cerquilha dupla ##. Isso também é demonstrado ao usar a variável de loop nas chaves ou valores, bem como diferentes métodos de concatenação que usam strings.

Observe que $i é um número inteiro, portanto você pode chamar um método .toString(). Para tipos de GraphQLINT, isso pode ser útil.

Você também pode usar um operador de intervalo diretamente, por exemplo:

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

Matrizes

Você tem manipulado um mapa até agora, mas matrizes também são comuns em. VTL Com matrizes você também tem acesso a alguns métodos subjacentes, como .isEmpty(), .size(), .set(), .get() e .add(), como mostrado abaixo:

#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)))

O exemplo anterior usou a notação de índice de matriz para recuperar um elemento com arr2[$idx]. Você pode pesquisar por nome a partir de um Mapa/dicionário de uma maneira semelhante:

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

Isso é muito comum ao filtrar resultados retornados das fontes de dados em Modelos de resposta ao usar condicionais.

Verificações condicionais

A seção anterior #foreach apresentou alguns exemplos do uso da lógica para transformar dados com. VTL Também é possível aplicar verificações condicionais para avaliar dados durante o runtime:

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

A verificação #if() acima de uma expressão Booliana é legal, mas você também pode usar operadores e #elseif() para a ramificação:

#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

Esses dois exemplos mostraram negação (!) e igualdade (==). Também podemos usar ||, &&, >, <, >=, <= e !=.

#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

Observação: somente Boolean.FALSE e null são considerados falsos em condicionais. Zero (0) e strings vazias (" ") não são equivalentes a falso.

Operadores

Nenhuma linguagem de programação seria completa sem algumas operadores para executar algumas ações matemáticas. Veja aqui alguns exemplos para começar a usar:

#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))

Usando loops e condicionais juntos

É muito comum, ao transformar dados emVTL, por exemplo, antes de gravar ou ler de uma fonte de dados, fazer um loop sobre objetos e, em seguida, realizar verificações antes de realizar uma ação. Combinar algumas das ferramentas das seções anteriores oferece várias funcionalidades. Uma ferramenta útil é saber que #foreach fornece automaticamente uma .count em cada item:

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

Por exemplo, talvez você deseja apenas extrair os valores de um mapa se estiver abaixo de um determinado tamanho. Usar a contagem junto com condicionais e a instrução #break permite que você faça o seguinte:

#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

O anterior #foreach é iterado com .keySet(), que você pode usar em mapas. Isso oferece acesso para obter a $key e fazer referência a um valor com uma .get($key). Os argumentos do GraphQL dos clientes em AWS AppSync são armazenados como um mapa. Eles também podem ser percorridos com .entrySet(), o que pode acessar chaves e valores como um Conjunto e popular outras variáveis ou realizar verificações condicionais complexas, como validação ou transformação de entrada:

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

Outros exemplos comuns são o preenchimento automático de informações padrão, como as versões do objeto inicial durante a sincronização de dados (muito importante na resolução de conflitos) ou o proprietário padrão de um objeto para verificações de autorização – Mary criou esta publicação de blog, portanto:

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

Contexto

Agora que você está mais familiarizado com a realização de verificações lógicas em AWS AppSync resolvedores comVTL, dê uma olhada no objeto de contexto:

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

Isso contém todas as informações que podem ser acessadas na solicitação do GraphQL. Para obter uma explicação detalhada, consulte a referência do contexto.

Filtrar

Até agora, neste tutorial, todas as informações da sua função Lambda foram retornadas para a consulta do GraphQL com uma transformação muito simples: JSON

$util.toJson($context.result)

A VTL lógica é igualmente poderosa quando você recebe respostas de uma fonte de dados, especialmente ao fazer verificações de autorização em recursos. Vamos examinar alguns exemplos. Primeiro, tente alterar o modelo de resposta da seguinte forma:

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

Não importa o que aconteça com a operação do GraphQL, os valores codificados são retornados ao cliente. Altere um pouco para que o campo meta seja preenchido a partir da resposta do Lambda, definido anteriormente no tutorial no valor elseIfCheck ao aprender sobre condicionais:

#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 é um mapa, portanto você pode usar entrySet() para executar a lógica nas chaves ou valores retornados. Como $context.identity contém informações sobre o usuário que executou a operação do GraphQL, se você retornar informações de autorização a partir da fonte de dados, é possível decidir retornar todos os dados, dados parciais ou nenhum dado para um usuário com base na sua lógica. Altere o modelo da resposta para se parecer com o seguinte:

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

Se você executar a consulta do GraphQL, os dados serão retornados como normal. No entanto, se você alterar o argumento do id para algo diferente de 123 (query test { get(id:456 meta:"badrequest"){} }), você receberá uma mensagem de falha de autorização.

Encontre mais exemplos de cenários de autorização na seção dos casos de uso de autorização.

Amostra de modelo

Se você acompanhou o tutorial, pode ter criado esse modelo passo a passo. Caso não tenha feito isso, o incluímos abaixo para copiá-lo para testes.

Modelo de solicitação

#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) }

Modelo da resposta

#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