リゾルバーのマッピングテンプレートプログラミングガイド - AWS AppSync

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

リゾルバーのマッピングテンプレートプログラミングガイド

注記

現在、主に APPSYNC_JS ランタイムとそのドキュメントをサポートしています。APPSYNC_JS ランタイムとそのガイドをここで使用することを検討してください。

これは、 の Apache Velocity テンプレート言語 (VTL) を使用したプログラミングのクックブックスタイルのチュートリアルです AWS AppSync。、C JavaScript、Java などの他のプログラミング言語に精通している場合は、かなりわかりやすいはずです。

AWS AppSync は VTLを使用して、クライアントからの GraphQL リクエストをデータソースへのリクエストに変換します。その後、このプロセスを逆転して、データソースレスポンスを GraphQL レスポンスに変換します。VTL は、次のような手法を使用して、ウェブアプリケーションの標準リクエスト/レスポンスフローでリクエストとレスポンスの両方を操作する権限を付与する論理テンプレート言語です。

  • 新しい項目のデフォルト値

  • 入力の検証とフォーマット

  • データ変換および整形

  • リスト、マップ、配列の値を取り出す、または変更するための繰り返し処理

  • ユーザー ID に基づくレスポンスのフィルタリング/変更

  • 複合認証チェック

たとえば、GraphQL 引数でサービスの電話番号の検証を実行する、または DynamoDB に格納する前に入力パラメータを大文字に変換する場合を考えます。または、GraphQL 引数、JWTトークンクレーム、またはHTTPヘッダーの一部としてクライアントシステムにコードを提供し、コードがリスト内の特定の文字列と一致する場合にのみデータで応答させたい場合があります。これらはすべて、 VTLで で実行できる論理チェックです AWS AppSync。

VTL では、使い慣れたプログラミング手法を使用してロジックを適用できます。ただし、GraphQL がユーザーベースの成長に合わせてAPIスケーラブルになるように、標準のリクエスト/レスポンスフロー内で実行するように制限されています。 AWS AppSync はリゾルバーとしてもサポート AWS Lambda されるため、柔軟性が必要な場合は、選択したプログラミング言語 (Node.js、Python、Go、Java など) で Lambda 関数を記述できます。

セットアップ

言語を学ぶ際の一般的な手法は、結果 ( など JavaScript) console.log(variable) を出力して、何が起こっているかを確認することです。このチュートリアルでは、シンプルな GraphQL スキーマを作成し、Lambda 関数に値のマップを渡すことで、このデモを実行します。Lambda 関数で値を出力し、それらに応答します。これにより、リクエスト/レスポンスフローを理解し、さまざまなプログラミング手法を確認できます。

以下の GraphQL スキーマを作成することから開始します。

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

次に、Node.js を言語として使用して、次の AWS Lambda 関数を作成します。

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

AWS AppSync コンソールのデータソースペインで、この Lambda 関数を新しいデータソースとして追加します。コンソールのスキーマページに戻り、get(...):Thingクエリの横にある右側のATTACHボタンをクリックします。リクエストテンプレートでは、[Invoke and forward arguments (引数の呼び出しと転送)] メニューから既存のテンプレートを選択します。レスポンステンプレートでは、[Return Lambda result (Lambda 関数の結果を返す)] を選択します。

Lambda 関数の Amazon CloudWatch Logs を 1 か所で開き、 AWS AppSync コンソールのクエリタブから次の GraphQL クエリを実行します。

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

GraphQL のレスポンスには、id:123meta:testing が含まれます。Lambda 関数がエコーバックするためです。数秒後、これらの詳細を含むレコードが CloudWatch Logs に表示されます。

変数

VTL は、データの保存または操作に使用できるリファレンス を使用します。には、VTL変数、プロパティ、メソッドの 3 種類のリファレンスがあります。変数には、先頭に $ 記号が付き、#set ディレクティブで作成されます。

#set($var = "a string")

変数は、他の言語で使い慣れた、同様の型 (数値、文字列、配列、リスト、マップなど) に保存します。Lambda リゾルバーのデフォルトのリクエストテンプレートでJSONペイロードが送信されていることに気付いたかもしれません。

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

ここで注意すべき点がいくつかあります。まず、一般的なオペレーションにいくつかの便利な関数 AWS AppSync を提供します。この例では、 は変数を $util.toJsonに変換しますJSON。次に、変数 $context.arguments は GraphQL リクエストからマップオブジェクトとして自動的に入力されます。新しいマップを次のように作成できます。

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

$myMap という変数が作成されました。これには idmeta、および upperMeta のキーがあります。これは、以下の特徴も示しています。

  • id には、GraphQL 引数からキーが入力されます。これは、クライアントから引数を取得VTLするために で一般的です。

  • meta は、値がハードコードされ、デフォルト値を示します。

  • upperMetameta 引数を .toUpperCase() メソッドを使用して変換します。

リクエストテンプレートの先頭に以前のコードを配置して、payload を変更して、新しい $myMap 変数を使用するようにします。

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

Lambda 関数を実行すると、レスポンスの変更とこのデータが CloudWatch ログに表示されます。このチュートリアルの残りの部分を実行するので、同様のテストを実行できるように $myMap の入力を保持します。

変数に properties_ を設定することもできます。これらは、単純な文字列、配列、または ですJSON。

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

クワイエットリファレンス

VTL はテンプレート言語であるため、デフォルトでは、指定したすべてのリファレンスが を実行します.toString()。参照が未定義の場合、実際の参照表現を、文字列として出力します。例:

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

これに対処するために、 VTLにはクワイエットリファレンス構文またはサイレントリファレンス構文があり、テンプレートエンジンにこの動作を抑制するように指示します。この構文は $!{} です。たとえば、前のコードで $!{somethingelse} を使用してわずかに変更した場合、印刷は抑制されます。

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

呼び出し方法

前の例では、変数を作成し、同時に値を設定する方法を説明しました。次に示すように、データをマップに追加して、これを 2 つのステップで実行することもできます。

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

HOWEVER この動作について知っておくべきことがあります。静的参照表記 $!{} で、上記のようにメソッドを呼び出すことができますが、実行されたメソッドの戻り値は抑制されません。そのため ##Prints "first value"##Prints true が追加されています。キーがすでに存在している場合に、値を挿入するなど、リストやマップで反復処理をしているときに、エラーが発生する場合があります。出力で評価時に予期しない文字列がテンプレートに追加されるためです。

これに対する回避策は、#set ディレクティブを使用してメソッドを呼び出し、変数を無視するとうまくいくことがあります。例:

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

テンプレートでこの手法を使用すると、予期しない文字列がテンプレートに出力されないようにできます。 は、より簡潔な表記で同じ動作を提供する代替の便利な関数 AWS AppSync を提供します。これにより、これらの実装の詳細を検討する必要がなくなります。$util.quiet() またはそのエイリアス $util.qr() でこの関数にアクセスできます。例:

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

文字列

多くのプログラミング言語の場合と同じように、文字列は、特に変数から構築する場合に対処が困難なことがあります。には、いくつかの一般的な問題がありますVTL。

DynamoDB のようなデータソースに文字列としてデータを挿入する場合、GraphQL 引数などの変数から入力されます。文字列は、二重引用符で囲まれ、文字列で変数を参照するために必要なのは "${}" だけです (静的参照表記! がない場合)。これは、/Referencehttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Template_literals のテンプレートリテラルに似ています JavaScript。

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

GraphQL クライアントから引数を使用するときの "author": { "S" : "${context.arguments.author}"}、または "id" : { "S" : "$util.autoId()"} のような ID 自動生成などの DynamoDB リクエストテンプレートでこれを確認できます。つまり、データを入力するため、文字列の中でメソッドの結果または変数を参照できるということです。

部分文字列を取り出すなど、Java String クラスのパブリックメソッドを使用することもできます。

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

文字列連結も非常に一般的なタスクです。これを行うには、変数参照を単独で、または静的な値とともに使用します。

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

loop

変数を作成して、メソッドを呼び出したので、ロジックをコードに追加できます。他の言語とは異なり、 では、反復回数が事前に決定されているループのみVTLが許可されます。Velocity には do..while はありません。この設計により、評価プロセスは常に確実に終了します。これは GraphQL オペレーションを実行するときに、スケーラビリティのための境界になります。

ループは #foreach で作成され、ループ変数や、配列、リスト、マップ、コレクションなど反復可能オブジェクトの入力が必要です。#foreach ループの典型的なプログラミング例では、コレクションの項目をループし、出力します。このケースでは、それらを取り出し、マップに追加します。

#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

この例では、いくつかの注意点を示します。まず、範囲 [..] 演算子で変数を使用して、反復可能オブジェクトを作成します。次に、各項目は、操作できる $i 変数によって参照されます。前の例では、コメントは、二重の ## 記号で示されます。これは、文字列を使用した、異なるメソッドの連結であるとともに、キー (値) の両方に、ループ変数を使用する例でもあります。

$i は整数であることに注意してください。.toString() メソッドを呼び出すことができます。の GraphQL タイプではINT、これは便利です。

範囲演算子を直接使用することもできます。次に例を示します。

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

配列

ここまでマップを操作してきましたが、配列も で一般的ですVTL。配列では、.isEmpty().size().set().get().add() などのいくつかの基本メソッドで、以下のようにアクセスできます。

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

前の例では配列インデックス表記を使用して arr2[$idx] の要素を取得しました。同様に、マップ/ディクショナリから名前で参照することができます。

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

条件式を使用して、レスポンステンプレートでデータソースから返される結果をフィルタリングするときに、これがよく使われます。

条件付きチェック

前のセクションでは、ロジックを使用して でデータを変換する例をいくつか#foreach紹介しましたVTL。データを評価する条件チェックがランタイムにも適用されます。

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

上記のブール式の #if() チェックは、問題ありませんが、分岐に演算子と #elseif() を使用することもできます。

#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

これらの 2 つの例は否定 (!) と一致 (==) を示しています。||、&&、>、<、>=、<=、および != を使用することもできます。

#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

注意 : Boolean.FALSEnull だけが条件で false と見なされます。ゼロ (0) と空の文字列 ("") は false に該当しません。

演算子

何らかの演算処理を実行する演算子がないと、プログラミング言語は完全とはいえません。手始めにいくつか例を紹介します。

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

ループと条件を一緒に使用する

データソースから書き込む前や読み取る前などVTL、 でデータを変換してオブジェクトをループオーバーし、アクションを実行する前にチェックを実行する場合、非常に一般的です。前のセクションのいくつかのツールを組み合わせることで、多くの機能が利用できます。1 つの便利な手法は #foreach では各項目に .count が自動的に使用できる点を理解することです。

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

たとえば、一定サイズ未満の値をマップから取り出す場合があります。#break ステートメントで、条件とともにカウントを使用してこれを実行できます。

#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

前の #foreach はマップに対して使用できる .keySet() で反復します。これにより、$key を取得し .get($key) で値を参照するためにアクセスできます。のクライアントからの GraphQL 引数はマップとして AWS AppSync 保存されます。また、.entrySet() で反復処理を実行でき、Set としてのキーと値の両方にアクセスでき、入力の検証や変換などの複雑な条件チェックを実行するか、他の変数を入力できます。

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

他の一般的な例は、データを同期するときの初期オブジェクトバージョンなど、デフォルト情報の自動入力です (競合の解決に非常に重要)。または認可チェック用のオブジェクトのデフォルト所有者もあります。Mary がこのブログ記事を作成した場合は以下のようになります。

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

Context

リゾルバーでの AWS AppSync を使用した論理チェックの実行に慣れたのでVTL、コンテキストオブジェクトを確認します。

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

これには、GraphQL リクエストの、アクセスできるすべての情報が含まれます。詳細な説明については、コンテキストリファレンスを参照してください。

フィルタリング

このチュートリアルのこれまでのところ、Lambda 関数のすべての情報が、非常に単純なJSON変換で GraphQL クエリに返されています。

$util.toJson($context.result)

VTL ロジックは、データソースからレスポンスを取得するとき、特にリソースで認証チェックを実行するときにも同じように強力です。いくつかの例を見てみましょう。最初に、次のようにレスポンステンプレートの変更を試します。

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

GraphQL オペレーションで何が起こるかにかかわらず、ハードコードされた値がクライアントに返されます。meta フィールドに Lambda レスポンスから値が設定されるように少し変更し、条件式についての前のチュートリアルのように、elseIfCheck 値に設定します。

#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 はマップで、キーまたは値のいずれかで返されるロジックを実行するために entrySet() を使用できます。GraphQL オペレーションを実行したユーザーに関する情報が $context.identity に含まれているため、データソースから認証情報を返す場合は、ロジックに基づいて、ユーザーに返すデータ (すべて、一部、なし) を決定できます。次のようにレスポンステンプレートを変更します。

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

GraphQL クエリを実行した場合、データがノーマルとして返されます。ただし、id 引数を 123 以外のものに変更した場合 (query test { get(id:456 meta:"badrequest"){} })、認証失敗のメッセージが表示されます。

認証ユースケースセクションに他の認証シナリオがあります。

テンプレートサンプル

このチュートリアルを実行していくと、ステップごとに拡張されて、このテンプレートになります。このテンプレートがまだない場合は、以下のテンプレートをテスト用にコピーします。

リクエストテンプレート

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

レスポンステンプレート

#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