Amazon Web Services ブログ

AWS AppSync を使用して Amazon QLDB への GraphQL インターフェイスを構築する: パート 1



Amazon Quantum Ledger Database (QLDB) は、信頼できるデータソースを必要とするユースケース向けに設計されたデータベースです。Amazon QLDB は、データベースにコミットされたすべての変更の履歴を完全で不変な形で保持します (これは台帳と呼ばれます)。Amazon QLDB は、金融、e コマース、在庫管理、政府、その他数多くの用途でお使いいただけます。

Amazon QLDB を AWS AppSync などのサービスとペアリングすると、モバイルアプリケーション、ウェブサイト、データレイクのデータとそのデータの履歴を安全に公開できます。この記事では、Amazon QLDB を AWS AppSync と統合して政府のユースケースを強化するための再利用可能なアプローチについて説明します。

Amazon QLDB を AWS AppSync のデータソースとして追加するには、AWS Lambda 関数を使用してデータベースに接続します。次の図は、このソリューションのアーキテクチャを示しています。

この記事では、Department of Motor Vehicles (DMV) のユースケースを用いて、Amazon QLDB をデータソースとして AWS AppSync に追加します。このユースケースは、Getting Started with the Amazon QLDB Console でご利用いただけます。Amazon QLDB データソースを接続するだけでなく、簡単なクエリも作成します。

今後の記事では、データの変更や履歴の取得など、より高度な Amazon QLDB オペレーションの操作方法について説明します。AWS AppSync を Amazon ElastiCache および Amazon Neptune と統合する方法については、「代替データソースの AWS AppSync との統合: Amazon Neptune および Amazon ElastiCache」を参照してください。

AWS AppSync を理解する

AWS AppSync は、GraphQL を使用してデータ量の多いアプリケーションを構築するためのマネージドサービスです。AWS AppSync API のクライアントは、必要な正確なデータを選択できるため、複数のデータソースからのデータを組み合わせることができる豊富で柔軟な API を構築できます。AWS AppSync は、スケーリングを管理することなく、リアルタイムおよびオフラインのユースケースでもお使いいただけます。

AWS AppSync で API を構築するときは、まず GraphQL スキーマを定義します。スキーマは、API で使用できるデータ型の形状と、その API を介して実行できる操作を定義します。GraphQL の操作には、クエリ (データの読み取り)、ミューテーション (データの書き込み)、サブスクリプション (リアルタイムの更新の受信) が含まれます。各操作はデータソースによってサポートされています。AWS AppSync は、Amazon DynamoDBAmazon Elasticsearch Service、HTTP エンドポイント、Lambda など、さまざまなデータソースをサポートしていて、すぐにお使いいただけます。Lambda 関数の柔軟性により、Amazon QLDB を含むさまざまなデータソースを作成できます。

データソースに加えて、各 GraphQL 操作はリゾルバーに関連付けられています。リゾルバーは、Apache Velocity テンプレート言語 (VTL) で構成された 2 つのマッピングテンプレートで構成されています。リクエストマッピングテンプレートは、AWS AppSync がデータソースのデータをクエリまたは変更する方法を定義します。応答テンプレートは、操作の結果をクライアントに返す方法を定義します。GraphQL 操作は通常、JSON データ形式を使用してクライアントとやり取りします。次の図は、このアーキテクチャを示しています。

AWS AppSync の幅広い機能については、この記事では扱いません。詳細については、AWS AppSync 開発者ガイドを参照してください。また、AWS AppSync のサポートを含む、モバイルおよびウェブアプリケーションを構築するための開発プラットフォームである AWS Amplify を詳しく見てみてもいいでしょう。

AWS AppSync で DMV API を構築する

AWS AppSync で GraphQL API を構築する最初のステップは、API で使用できるデータの形状と操作を定義するスキーマを指定することです。完全なコードは、GitHub リポジトリで入手できます。

この記事では、最初に 5 つの GraphQL タイプと 1 つのクエリをスキーマに含めます。次のコードを参照してください。

type Person {
   FirstName: String!
   LastName: String!
   DOB: AWSDate
   GovId: ID!
   GovIdType: String
   Address: String
}

type Owner {
   PersonId: ID!
}

type Owners {
   PrimaryOwner: Person!
   SecondaryOwners: [Person]
}

type Vehicle {
   VIN: ID!
   Type: String
   Year: Int
   Make: String
   Model: String
   Color: String
}

type VehicleRegistration {
   VIN: ID!
   LicensePlateNumber: String!
   State: String
   City: String
   PendingPenaltyTicketAmount: Float
   ValidFromDate: AWSDateTime!
   ValidToDate: AWSDateTime!
   Owners: Owners
}

type Query {
   getVehicle(vin:ID!): Vehicle
}

schema {
   query: Query
}

リレーショナルデータベースと SQL を使用した経験があるなら、同じ様な感覚で Amazon QLDB で作業できるかもしれません。リレーショナルデータベースと同様に、Amazon QLDB はデータをテーブルに編成します。スキーマ内の 3 つの GraphQL タイプは同じ名前のテーブルにマップされ、ネストされたデータを表す 2 つのタイプ (OwnerOwners) が追加されています。

この記事のサンプルコードは、必要な AWS リソースと小さなデータセットの両方をデプロイします。 (リレーショナルデータベースのデータベースに似た) Amazon QLDB 台帳には、4 つのテーブルとサンプルデータが含まれています。次のスクリーンショットを参照してください。

台帳のスキーマとテーブルを確認すると、スキーマのタイプとフィールドが、台帳のテーブルとドキュメントの属性と密接に関係していることがわかります。

車両データのクエリ

DMV API は現在、データにアクセスするためのクエリ、getVehicle をサポートしています。getVehicle クエリは、単一のパラメータの車両識別番号 (VIN) を取り、その車両に関するデータを返します。

次のコードは、DMV データセット内の 2019 Mercedes CLK 350 に関する情報を取得するための GraphQL クエリを示しています。GraphQL では、結果に含まれるフィールドを指定できます (それらが全体のデータ型の一部である場合)。次のコードの結果にはメーカー、モデル、年式が含まれていますが、色やその他の属性は含まれていません。

query GetVehicle {
  getVehicle(vin: "1C4RJFAG0FC625797") {
    Make
    Model
    Year
  }
}

各 AWS AppSync の操作 (クエリまたはミューテーション) は、データソースとリゾルバーに関連付けられています。Amazon QLDB はそのままでは AWS AppSync と直接統合されていませんが、Lambda を使用して Amazon QLDB をデータソースとして有効にすることができます。

Amazon QLDB データソースを構築する

単一の統合関数がサンプルアプリケーションの AWS AppSync と Amazon QLDB 間のすべての相互作用を管理しますが、別の方法で実装することもできます。Amazon QLDB を操作するには、統合関数にパッケージ化したドライバー (リレーショナルデータベース、Amazon ElastiCache や Neptune に類似) が必要です。この関数には、Amazon QLDB 台帳でクエリを実行するための IAM アクセス許可も必要です。Amazon QLDB は Amazon VPC にはありませんが、Lambda データソースを使用して AWS AppSync を VPC にあるデータベースと統合することもできます。

Amazon QLDB は現在、Java のドライバーと、Node.js および Python のプレビューを提供しています。この記事では Java を使用して、成熟度に基づいて Amazon QLDB 統合関数を構築していますが、Lambda は他のオプションのいずれかもサポートしています。この記事では、AWS Serverless Application Model (SAM) を使用して関数の管理を簡素化し、AWS SAM CLI を使用して関数を構築しています。

統合関数を AWS AppSync API にアタッチするには、関数を参照する新しい Lambda データソースとして追加し、AWS AppSync が関数を呼び出せるようにするサービスロールを指定します。この記事では、この作業を AWS CloudFormation で実行しますが、AWS CLI と AWS マネジメントコンソールを介して接続することもできます。次のコードは、CloudFormation テンプレートの該当する部分を示しています。

QLDBIntegrationDataSource:
  Type: AWS::AppSync::DataSource
  Properties:
    ApiId: !GetAtt DmvApi.ApiId
    Name: QldbIntegration
    Description: Lambda function to integrate with QLDB
    Type: AWS_LAMBDA
    ServiceRoleArn: !GetAtt AppSyncServiceRole.Arn
    LambdaConfig:
      LambdaFunctionArn: !GetAtt QLDBIntegrationFunction.Arn

リゾルバーをアタッチする

Amazon QLDB データソースを作成したら、getVehicle クエリのリゾルバーを定義できます。リゾルバーの最初の部分はリクエストマッピングで、AWS AppSync がデータソースと対話する方法を定義します。リクエストマッピングテンプレートは JSON で定義されており、すべての Lambda データソースに共通のエンベロープが含まれています。Amazon QLDB 統合関数の場合、payload フィールドには、この特定のクエリの詳細が含まれています。次のコードを参照してください。

{
  "version": "2017-02-28",
  "operation": "Invoke",
  "payload": {
    "action": "Query",
    "payload": [
      {
        "query": "SELECT * FROM Vehicle AS t WHERE t.VIN = ?",
        "args": [ "$context.args.vin" ]
      }
    ]
  }
}

getVehicle クエリが呼び出されると、AWS AppSync が統合関数を呼び出し、outer payload フィールドの内容をイベントとして渡します。このユースケースでは、AWS AppSync は $ctx.args.vin をクエリの vin 引数として渡された値で置き換えます (前のクエリの値は 1C4RJFAG0FC625797 です)。

統合関数は、action 引数と、実際のクエリを含む別の payload を取ります。呼び出しペイロードの構造は柔軟ですが、呼び出される Lambda 関数はそれを理解する必要があります。このユースケースでは、統合関数は次のスキーマを持ったペイロードを想定しています。

{
  "action": "STRING_VALUE", /* required - always "Query" */
  "payload": [
    {
      "query": "STRING_VALUE", /* required – PartiQL query */
      "args": [
        "STRING_VALUE" /* optional – one or more arguments */
      ]
    }
    /* optional - additional queries (covered in subsequent post) */
  ]
}

SQL に精通している場合は、前述のリクエストマッピングに含まれているクエリに詳しいはずです。このユースケースでは、VIN 属性が何らかの値である Vehicle テーブルのすべての属性をクエリします。VIN 引数の値は、リゾルバーが利用できる $context 変数で AWS AppSync から渡されます。このユースケースでは、AWS AppSync は変数を実際の値に変換してから、Lambda 関数を呼び出します。リゾルバーマッピングテンプレートの詳細については、「リゾルバーマッピングテンプレートコンテキストリファレンス」を参照してください。

Amazon QLDB コンソールのクエリエディタを使用してこのクエリを自分でテストし、疑問符を台帳の有効な VIN に置き換えることができます。次のスクリーンショットを参照してください。

Amazon QLDB 統合関数の探索

getVehicle リクエストマッピングテンプレートを変換した後、AWS AppSync は Amazon QLDB 統合関数を呼び出します。このセクションでは、関数の実行について説明します。

Amazon QLDB への接続

台帳でクエリを実行する前に、台帳への接続を確立する必要があります。統合関数では PooledQldbDriver を使用するのが Amazon QLDB のベストプラクティスです。ベストプラクティスの詳細については、「Amazon QLDB とは」を参照してください。 ドライバーの詳細については、Javadocs ウェブサイトの「Amazon QLDB Java Driver 1.1.0 API Reference」を参照してください。Lambda 関数では、ドライバーは static コードブロックで初期化されるため、呼び出しのたびに作成されるわけではありません。接続の作成プロセスは比較的遅いため、これが Lambda のベストプラクティスです。

接続をインスタンス化するには、PooledQldbDriver クラスによって提供される builder オブジェクトを使用して、台帳の名前を渡します。台帳の名前は vehicle-registration です。その名前は Lambda 環境変数 (QLDB_LEDGER) を介して渡されます。次のコードを参照してください。

private static PooledQldbDriver createQLDBDriver() {
    AmazonQLDBSessionClientBuilder builder =    
      AmazonQLDBSessionClientBuilder.standard();

    return PooledQldbDriver.builder()
             .withLedger(System.getenv("QLDB_LEDGER"))
             .withRetryLimit(3)
             .withSessionClientBuilder(builder)
             .build();
}

前述のように、Amazon QLDB には VPC は必要ありませんが、呼び出し元には特定の台帳のクエリを実行するための IAM アクセス許可が必要です。次のような IAM ポリシーは、Lambda 関数に Amazon QLDB への適切なアクセスを許可します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "qldb:SendCommand"
            ],
            "Resource": "arn:aws:qldb:REGION:ACCOUNT_ID:ledger/vehicle-registration",
            "Effect": "Allow"
        }
    ]
}

クエリを実行する

Amazon QLDB と取引するには、ドライバーを介してセッションを作成する必要があります。統合関数は、Lambda 関数の呼び出しごとに新しいセッションを作成します。次のコードを参照してください。

private QldbSession createQldbSession() {
    return DRIVER.getSession();
}

セッションでは、Amazon QLDB 台帳との対話を開始できます。Amazon QLDB は PartiQL クエリ言語をサポートし、構造化、半構造化、およびネストされたデータ全体に SQL 互換のクエリアクセスを提供します。1 つのトランザクション内で複数のクエリを実行できます。

再利用性を高めるために、Amazon QLDB 統合関数では、単一の AWS AppSync クエリまたはミューテーションで複数のクエリを実行できます。この記事では単一のクエリ操作に焦点を当てていますが、後の記事では、より複雑なトランザクションで複数の Amazon QLDB クエリを使用する方法について説明します。

Amazon QLDB でクエリを実行するには、トランザクションを作成し、目的の各クエリを実行します。次のコードを参照してください。

private String executeTransaction(Query query) {
  try (QldbSession qldbSession = createQldbSession()) {
    String result = "";

    qldbSession.execute((ExecutorNoReturn) txn -> {
      result = executeQuery(txn, query));
    }, (retryAttempt) -> LOGGER.info("Retrying due to OCC conflict..."));

    return result;
  } catch (QldbClientException e) {
    LOGGER.error("Unable to create QLDB session: {}", e.getMessage());
  }

  return "{}";
}


private String executeQuery(TransactionExecutor txn, Query query) {
  final List<IonValue> params = new ArrayList<IonValue>();
  query.getArgs().forEach((a) -> {
    try {
      params.add(MAPPER.writeValueAsIonValue(arg));
    } catch (IOException e) {
      LOGGER.error("Could not write value as Ion: {}", a);
    }
  });

  // Execute the query and transform response to JSON string...
  List<String> json = new ArrayList<String>();
  txn.execute(query.getQuery(), params).iterator().forEachRemaining(r -> {
    String j = convertToJson(r.toPrettyString());
    json.add(j);
  });

  return json.toString();
}

クエリ結果は、JSON の拡張である Amazon ION で Amazon QLDB から返されます。ただし、AWS AppSync では、データを JSON 形式で渡す必要があります。ION Cookbook レシピの派生を使用して、ION から JSON に変換できます。次のコードを参照してください。

rivate String convertToJson(String ionText) {
    StringBuilder builder = new StringBuilder();
    try (IonWriter jsonWriter = IonTextWriterBuilder.json()
                                   .withPrettyPrinting().build(builder)) {
        IonReader reader = IonReaderBuilder.standard().build(ionText);
        jsonWriter.writeValues(reader);
    } catch (IOException e) {
        LOGGER.error(e.getMessage());
    }
    return builder.toString();
}

Lambda 関数の詳細については、今後執筆する記事で説明します。詳細については、GitHub リポジトリを参照してください。

Amazon QLDB 関数からの結果は、JSON レスポンスの一部として返されます。Amazon QLDB の実際の結果は、関数から返されるときに文字列エンコードされます。次のコードを参照してください。

"result": {
  "result": "[\n{\n  \"VIN\":\"1C4RJFAG0FC625797\",\n  \"Type\":\"Sedan\",\n  \"Year\":2019,\n  \"Make\":\"Mercedes\",\n  \"Model\":\"CLK 350\",\n  \"Color\":\"White\"\n}]",
  "success": true
}

結果を解決する

AWS AppSync は、呼び出し元に結果を返す前に、リゾルバーの 2 番目の部分であるレスポンスマッピングテンプレートを適用します。データソースへのリクエストと同様に、呼び出し元へのレスポンスは、レスポンステンプレートを変換した結果です。

AWS AppSync は、データソースを呼び出した結果を、前述のクエリパラメータと同じ $context オブジェクトで利用できるようにします。この場合、結果は特に result フィールドにあります。Amazon QLDB からの結果を有効な AWS AppSync 結果にマッピングするために、マッピングテンプレートは組み込みユーティリティ関数を使用して、統合関数からの「stringified」JSON 結果を解析し、最初の結果を JSON オブジェクトとして返します。次のコードは、getVehicle レスポンスマッピングテンプレートの簡略版です。

#set( $result = $util.parseJson($ctx.result.result) )
$util.toJson($result[0])

リゾルバーを AWS AppSync の操作に一意に関連付けることができるため、リクエストとレスポンスのマッピングテンプレートは、ユースケースによってはかなりの柔軟性がもたらされます。この記事では、単一の結果 (またはエラー) しか期待できません。他の操作では、結果の配列やその他のレスポンスが返される場合があります。これらは、マッピングテンプレートを使用してカスタマイズできます。

次のコードは、元の getVehicle クエリの結果です。結果の形状はスキーマの Vehicle タイプのサブセットで、リクエストで選択したフィールドによります。

{
  "data": {
    "getVehicle": {
      "Make": "Mercedes",
      "Model": "CLK 350",
      "Year": 2019
    }
  }
}

まとめ

この記事では、AWS AppSync、Lambda、Amazon QLDB を使用して比較的簡単なクエリを実行する方法について説明しました。getVehicle クエリを実行するには、AWS AppSync リゾルバーを作成し、Lambda 統合関数をアタッチして、Amazon QLDB をクエリしました。

Amazon QLDB と AWS AppSync を統合することにより、これらの固有の利点を活用できます。検証可能なトランザクションログを必要とし、AWS AppSync を介してさまざまなクライアントからの台帳とやり取りするユースケースでは、Amazon QLDB のマネージド台帳を使用できます。

AWS AppSync を使用して Amazon QLDB への GraphQL インターフェイスを構築する: パート 2」では、マルチステップクエリ、ミューテーション、台帳のデータ変更のクエリなど、DMV API の機能について詳しく説明しているので、是非ご覧ください。完全に機能する例については、GitHub リポジトリをご覧ください。

 


著者について

 

Josh Kahn は、アマゾン ウェブ サービスのプリンシパルソリューションアーキテクトです。 AWS のお客様と協力して、データベースプロジェクトに関する指導と技術支援を行い、お客様が AWS を使用する際、ソリューションの価値を向上させられるように手助けしています。