Data Connect ミューテーションを実装する

Firebase Data Connect を使用すると、Google Cloud SQL で管理されている PostgreSQL インスタンスのコネクタを作成できます。これらのコネクタは、スキーマからデータを使用するためのクエリとミューテーションの組み合わせです。

スタートガイドでは、PostgreSQL の映画レビュー アプリのスキーマを紹介しました。

このガイドでは、変更を含むデプロイ可能な管理オペレーションとアドホック管理オペレーションの両方も紹介しました。

  • デプロイ可能なミューテーションは、定義した API エンドポイントを使用して、コネクタ内のクライアント アプリから呼び出すために実装するものです。Data Connect は、これらのミューテーションに認証と認可を統合し、API に基づいてクライアント SDK を生成します。
  • アドホック管理ミューテーションは、特権環境から実行され、テーブルの入力と管理を行います。これらは、Firebase コンソールで作成して実行できます。また、Firebase Admin SDK を使用して特権環境から実行することも、Data Connect VS Code 拡張機能を使用してローカル開発環境で実行することもできます。

このガイドでは、デプロイ可能なミューテーションについて詳しく説明します。

Data Connect ミューテーションの機能

Data Connect を使用すると、PostgreSQL データベースで想定されるすべての方法で基本的なミューテーションを実行できます。

  • CRUD オペレーションを実行する
  • トランザクションを使用して複数ステップのオペレーションを管理する

ただし、Data Connect の GraphQL 拡張機能を使用すると、高度なミューテーションを実装して、より高速で効率的なアプリを実現できます。

  • 多くのオペレーションから返されるキー スカラーを使用して、レコードに対する繰り返しオペレーションを簡素化する
  • サーバー値を使用して、サーバーが提供するオペレーションでデータを入力する
  • マルチステップ ミューテーション オペレーションの過程でクエリを実行してデータをルックアップし、コード行とサーバーへのラウンド トリップを節約します。

生成されたフィールドを使用してミューテーションを実装する

Data Connect オペレーションは、スキーマの型と型の関係に基づいて自動的に生成された Data Connect のフィールドのセットを拡張します。これらのフィールドは、スキーマを編集するたびにローカル ツールによって生成されます。

生成されたフィールドを使用して、単一テーブル内の個々のレコードの作成、更新、削除から、より複雑なマルチテーブル更新まで、ミューテーションを実装できます。

スキーマに Movie 型と関連する Actor 型が含まれているとします。Data Connect は、movie_insertmovie_updatemovie_delete フィールドなどを生成します。


movie_insert フィールドを使用したミューテーション

movie_insert フィールドは、Movie テーブルに単一のレコードを作成するミューテーションを表します。

このフィールドを使用して、1 つの動画を作成します。

mutation CreateMovie($data: Movie_Data!) {
  movie_insert(data: $data) { key }
}


movie_update フィールドを使用したミューテーション

movie_update フィールドは、Movie テーブル内の単一のレコードを更新するミューテーションを表します。

このフィールドを使用して、キーで 1 つの映画を更新します。

mutation UpdateMovie($myKey: Movie_Key!, $data: Movie_Data!) {
  movie_update(key: $myKey, data: $data) { key }
}


movie_delete フィールドを使用したミューテーション

movie_delete フィールドは、Movie テーブル内の単一のレコードを削除するミューテーションを表します。

このフィールドを使用して、キーで 1 つの映画を削除します。

  mutation DeleteMovie($myKey: Movie_Key!) {
    movie_delete(key: $myKey) { key }
  }

ミューテーションの必須要素

Data Connect ミューテーションは、Data Connect 拡張機能を含む GraphQL ミューテーションです。通常の GraphQL ミューテーションと同様に、オペレーション名と GraphQL 変数のリストを定義できます。

Data Connect は、@auth@transaction などのカスタマイズされたディレクティブを使用して GraphQL クエリを拡張します。

したがって、次のミューテーションには次のものが含まれます。

  • mutation 型の定義
  • SignUp オペレーション(ミューテーション)の名前
  • 単一の変数 $username オペレーション引数
  • 単一のディレクティブ @auth
  • 単一のフィールド user_insert
mutation SignUp($username: String!) @auth(level: USER) {
  user_insert(data: {
    id_expr: "auth.uid"
    username: $username
  })
}

すべてのミューテーション引数には、型宣言(String などの組み込み型、または Movie などのスキーマ定義のカスタム型)が必要です。

基本的なミューテーションを作成する

ミューテーションの記述を開始して、データベースから個々のレコードを作成、更新、削除できます。

作成

基本的な作成を行いましょう。

# Create a movie based on user input
mutation CreateMovie($title: String!, $releaseYear: Int!, $genre: String!, $rating: Int!) {
  movie_insert(data: {
    title: $title
    releaseYear: $releaseYear
    genre: $genre
    rating: $rating
  })
}

# Create a movie with default values
mutation CreateMovie2 {
  movie_insert(data: {
    title: "Sherlock Holmes"
    releaseYear: 2009
    genre: "Mystery"
    rating: 5
  })
}

または upsert。

# Movie upsert using combination of variables and literals
mutation UpsertMovie($title: String!) {
  movie_upsert(data: {
    title: $title
    releaseYear: 2009
    genre: "Mystery"
    rating: 5
    genre: "Mystery/Thriller"
  })
}

更新を実行する

最新情報をお知らせします。プロデューサーや監督は、平均評価がトレンドに沿っていることを願っています。

movie_update フィールドには、レコードを識別するための想定される id 引数と、この更新で値を設定するために使用できる data フィールドが含まれています。

mutation UpdateMovie(
  $id: UUID!,
  $genre: String!,
  $rating: Int!,
  $description: String!
) {
  movie_update(id: $id,
    data: {
      genre: $genre
      rating: $rating
      description: $description
    })
}

複数の更新を行うには、movie_updateMany フィールドを使用します。

# Multiple updates (increase all ratings of a genre)
mutation IncreaseRatingForGenre($genre: String!, $rating: Int!) {
  movie_updateMany(
    where: { genre: { eq: $genre } },
    data:
      {
        rating: $rating
      })
}

_update でインクリメント、デクリメント、追加、先頭追加のオペレーションを使用する

_update ミューテーションと _updateMany ミューテーションでは data: で値を明示的に設定できますが、値を更新するために増分などの演算子を適用する方が理にかなっていることがよくあります。

前の更新の例を変更して、特定の映画の評価を増やすとします。inc 演算子で rating_update 構文を使用できます。

mutation UpdateMovie(
  $id: UUID!,
  $ratingIncrement: Int!
) {
  movie_update(id: $id, data: {
    rating_update: {
      inc: $ratingIncrement
    }
  })
}

Data Connect は、フィールド更新で次の演算子をサポートしています。

  • inc: IntInt64FloatDateTimestamp のデータ型を増分する
  • dec: IntInt64FloatDateTimestamp のデータ型を減分する

リストの場合、次の方法で個々の値または値のリストを使用して更新することもできます。

  • add: Vector リストを除くリストタイプにアイテムがまだ存在しない場合は、アイテムを追加します。
  • remove: Vector リストを除くリストタイプから、すべてのアイテムを削除します(存在する場合)。
  • append: Vector リストを除くリスト型にアイテムを追加する
  • prepend: Vector リストを除くリスト型にアイテムを追加します

削除を実行する

もちろん、映画のデータを削除することもできます。映画の保存に携わる人々は、物理的なフィルムをできるだけ長く維持したいと考えているでしょう。

# Delete by key
mutation DeleteMovie($id: UUID!) {
  movie_delete(id: $id)
}

ここでは _deleteMany を使用できます。

# Multiple deletes
mutation DeleteUnpopularMovies($minRating: Int!) {
  movie_deleteMany(where: { rating: { le: $minRating } })
}

リレーションにミューテーションを書き込む

関係で暗黙的な _upsert ミューテーションを使用する方法を確認します。

# Create or update a one to one relation
mutation MovieMetadataUpsert($movieId: UUID!, $director: String!) {
  movieMetadata_upsert(
    data: { movie: { id: $movieId }, director: $director }
  )
}

効率的なミューテーションのためのスキーマを設計する

Data Connect には、より効率的なミューテーションを記述し、ラウンド トリップ オペレーションを節約できる 2 つの重要な機能があります。

キー スカラーは、Data Connect がスキーマのキー フィールドから自動的に組み立てる簡潔なオブジェクト識別子です。キー スカラーは効率性に関するもので、1 回の呼び出しでデータの ID と構造に関する情報を見つけることができます。これらは、新しいレコードに対して順次アクションを実行し、今後のオペレーションに渡す一意の識別子が必要な場合や、リレーショナル キーにアクセスしてより複雑なオペレーションを実行する場合に特に便利です。

サーバー値を使用すると、expr 引数の特定のサーバーサイド CEL 式に従って、保存された値またはすぐに計算可能な値を使用して、サーバーがテーブルのフィールドに動的に入力できます。たとえば、オペレーション リクエスト updatedAt: Timestamp! @default(expr: "request.time") に保存された時間を使用してフィールドにアクセスしたときにタイムスタンプが適用されるフィールドを定義できます。

高度なミューテーションを記述する: field_expr 構文を使用して Data Connect に値を指定する

キー スカラーとサーバー値で説明したように、クライアント リクエストに応じてサーバーが id や日付などの共通フィールドの値を入力するようにスキーマを設計できます。

また、クライアント アプリから Data Connect request オブジェクトで送信されたユーザー ID などのデータを利用することもできます。

ミューテーションを実装する場合は、field_expr 構文を使用して、サーバー生成の更新をトリガーするか、リクエストからデータにアクセスします。たとえば、リクエストに保存されている承認 uid_upsert オペレーションに渡すには、userId_expr フィールドに "auth.uid" を渡します。

# Add a movie to the user's favorites list
mutation AddFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie_upsert(data: { userId_expr: "auth.uid", movieId: $movieId })
}

# Remove a movie from the user's favorites list
mutation DeleteFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
}

また、使い慣れた ToDo リスト アプリで新しい ToDo リストを作成するときに、id_expr を渡して、リストの UUID を自動生成するようサーバーに指示することもできます。

mutation CreateTodoListWithFirstItem(
  $listName: String!
) @transaction {
  # Step 1
  todoList_insert(data: {
    id_expr: "uuidV4()", # <-- auto-generated. Or a column-level @default on `type TodoList` will also work
    name: $listName,
  })
}

詳細については、スカラー リファレンス_Expr スカラーをご覧ください。

高度なミューテーションを作成する: 複数ステップのオペレーション

1 つのミューテーションに複数の書き込みフィールド(挿入など)を含めることが必要な状況は数多くあります。また、ミューテーションの実行中にデータベースを読み取り、挿入や更新などの操作を行う前に既存のデータを検索して検証することもできます。これらのオプションを使用すると、ラウンド トリップ オペレーションが節約され、コストが削減されます。

Data Connect を使用すると、次のサポートにより、ミューテーションで複数ステップのロジックを実行できます。

  • 複数の書き込みフィールド

  • ミューテーション内の複数の読み取りフィールド(query フィールド キーワードを使用)。

  • リレーショナル データベースでおなじみのトランザクション サポートを提供する @transaction ディレクティブ

  • @check ディレクティブ。CEL 式を使用して読み取りの内容を評価し、その評価結果に基づいて次の処理を行うことができます。

    • ミューテーションで定義された作成、更新、削除を続行する
    • クエリ フィールドの結果を返す処理に進みます
    • 返されたメッセージを使用して、クライアント コードで適切なロジックを実行する
  • @redact ディレクティブ。これにより、クエリ フィールドの結果をワイヤー プロトコルの結果から除外できます。

  • CEL response バインディング。複雑なマルチステップ オペレーションで実行されたすべてのミューテーションとクエリの累積結果を保存します。response バインディングにアクセスできます。

    • @check ディレクティブで、expr: 引数を使用する
    • サーバー値(field_expr 構文を使用)

@transaction ディレクティブ

マルチステップ ミューテーションのサポートには、トランザクションを使用したエラー処理が含まれます。

@transaction ディレクティブは、単一の書き込みフィールド(_insert_update など)または複数の書き込みフィールドを含むミューテーションが常にデータベース トランザクションで実行されるようにします。

  • @transaction のないミューテーションは、各ルートフィールドを順番に実行します。オペレーションは、エラーを部分フィールド エラーとして表示しますが、後続の実行の影響は表示しません。

  • @transaction を含むミューテーションは、完全に成功するか完全に失敗するかのいずれかになります。トランザクション内のフィールドのいずれかが失敗すると、トランザクション全体がロールバックされます。

@check ディレクティブと @redact ディレクティブ

@check ディレクティブは、指定されたフィールドがクエリ結果に存在することを確認します。Common Expression Language(CEL)式は、フィールド値のテストに使用されます。ディレクティブのデフォルトの動作は、値が null または [](空のリスト)であるノードをチェックして拒否することです。

@redact ディレクティブは、クライアントからのレスポンスの一部を編集します。削除されたフィールドは、副作用(データ変更や @check など)について引き続き評価され、結果は CEL 式の後続のステップで引き続き使用できます。

@check@check(message:)@redact を使用する

@check@redact の主な用途は、関連データをルックアップして、特定のオペレーションを承認するかどうかを決定することです。このルックアップはロジックで使用されますが、クライアントには表示されません。クエリは、クライアント コードで正しく処理するための有用なメッセージを返すことができます。

たとえば、次のクエリ フィールドは、映画を編集できるユーザーを表示する適切な「管理者」ロールがリクエスト元にあるかどうかを確認します。

query GetMovieEditors($movieId: UUID!) @auth(level: USER) {
  moviePermission(key: { movieId: $movieId, userId_expr: "auth.uid" }) @redact {
    role @check(expr: "this == 'admin'", message: "You must be an admin to view all editors of a movie.")
  }
  moviePermissions(where: { movieId: { eq: $movieId }, role: { eq: "editor" } }) {
    user {
      id
      username
    }
  }
}

認可チェックの @check ディレクティブと @redact ディレクティブの詳細については、認可データのルックアップに関する説明をご覧ください。

@check を使用して鍵を検証する

_update などの一部のミューテーション フィールドは、指定されたキーを持つレコードが存在しない場合、no-op になることがあります。同様に、ルックアップは null または空のリストを返すことがあります。これらはエラーと見なされないため、ロールバックはトリガーされません。

この結果を防ぐには、@check ディレクティブを使用してキーが見つかるかどうかをテストします。

# Delete by key, error if not found
mutation MustDeleteMovie($id: UUID!) @transaction {
  movie_delete(id: $id) @check(expr: "this != null", message: "Movie not found, therefore nothing is deleted")
}

response バインディングを使用して複数ステップのミューテーションをチェーンする

関連レコード(たとえば、新しい Movie と関連する MovieMetadata エントリ)を作成する基本的な方法は次のとおりです。

  1. Movie_insert ミューテーションを呼び出す
  2. 作成したムービーの返されたキーを保存する
  3. 次に、2 つ目の _insert ミューテーションを呼び出して MovieMetadata レコードを作成します。

ただし、Data Connect を使用すると、2 番目の _insert で最初の _insert結果にアクセスすることで、この一般的なケースを 1 つの複数ステップのオペレーションで処理できます。

映画レビュー アプリを成功させるには、多くの作業が必要です。新しい例で、ToDo リストを追跡してみましょう。

response を使用してサーバー値でフィールドを設定する

次の ToDo リストのミューテーションでは:

  • response バインディングは、現在のものまでのすべての最上位ミューテーション フィールドを含む、これまでの部分レスポンス オブジェクトを表します。
  • 最初の todoList_insert オペレーションの結果(id(キー)フィールドを返す)は、後で response.todoList_insert.id でアクセスされるため、新しい ToDo アイテムをすぐに挿入できます。
mutation CreateTodoListWithFirstItem(
  $listName: String!,
  $itemContent: String!
) @transaction {
  # Sub-step 1:
  todoList_insert(data: {
    id_expr: "uuidV4()", # <-- auto-generated. Or a column-level @default on `type TodoList` will also work
    name: $listName,
  })
  # Sub-step 2:
  todo_insert(data: {
    listId_expr: "response.todoList_insert.id" # <-- Grab the newly generated ID from the partial response so far.
    content: $itemContent,
  })
}

response を使用して @check でフィールドを検証する

response@check(expr: "...") でも利用できるため、これを使用してさらに複雑なサーバーサイド ロジックを構築できます。ミューテーションの query { … } ステップと組み合わせることで、クライアントとサーバー間の追加のラウンド トリップなしで、より多くのことを実現できます。

次の例では、@check は常にアタッチされているステップの後に実行されるため、@check はすでに response.query にアクセスできることに注意してください。

mutation CreateTodoInNamedList(
  $listName: String!,
  $itemContent: String!
) @transaction {
  # Sub-step 1: Look up List.id by its name
  query
  @check(expr: "response.query.todoLists.size() > 0", message: "No such TodoList with the name!")
  @check(expr: "response.query.todoLists.size() < 2", message: "Ambiguous listName!") {
    todoLists(where: { name: $listName }) {
      id
    }
  }
  # Sub-step 2:
  todo_insert(data: {
    listId_expr: "response.todoLists[0].id" # <-- Now we have the parent list ID to insert to
    content: $itemContent,
  })
}

response バインディングの詳細については、CEL リファレンスをご覧ください。

@transactionquery @check で中断されたオペレーションを理解する

マルチステップ ミューテーションではエラーが発生する可能性があります。

  • データベース オペレーションが失敗する可能性があります。
  • クエリ @check ロジックはオペレーションを終了する場合があります。

Data Connect では、マルチステップ ミューテーションで @transaction ディレクティブを使用することをおすすめします。これにより、より一貫性のあるデータベースと、クライアント コードで処理しやすいミューテーション結果が得られます。

  • 最初のエラーまたは @check の失敗でオペレーションは終了するため、後続のフィールドの実行や CEL の評価を管理する必要はありません。
  • ロールバックは、データベース エラーまたは @check ロジックに応じて実行され、一貫性のあるデータベース状態になります。
  • ロールバック エラーは常にクライアント コードに返されます。

@transaction を使用しないユースケースもあります。たとえば、スループット、スケーラビリティ、可用性を高める必要がある場合は、結果整合性を選択できます。ただし、結果を許可するには、データベースとクライアント コードを管理する必要があります。

  • データベース オペレーションが原因で 1 つのフィールドが失敗しても、後続のフィールドは引き続き実行されます。ただし、失敗した @check はオペレーション全体を終了させます。
  • ロールバックは実行されません。つまり、一部の更新は成功し、一部の更新は失敗した混合データベース状態になります。
  • @check ロジックで前のステップの読み取りまたは書き込みの結果を使用している場合、@check を使用したオペレーションで一貫性のない結果が返される可能性があります。
  • クライアント コードに返される結果には、処理される成功と失敗のレスポンスがより複雑に混在しています。

Data Connect ミューテーションのディレクティブ

Data Connect には、型とテーブルの定義で使用するディレクティブに加えて、オペレーションの動作を拡張するための @auth@check@redact@transaction ディレクティブが用意されています。

ディレクティブ 適用対象 説明
@auth クエリとミューテーション クエリまたはミューテーションの認可ポリシーを定義します。承認と構成証明のガイドをご覧ください。
@check 複数ステップのオペレーションの query フィールド 指定されたフィールドがクエリ結果に存在することを確認します。フィールド値のテストには、Common Expression Language(CEL)式が使用されます。マルチステップ オペレーションをご覧ください。
@redact クエリ クライアントからのレスポンスの一部を編集します。マルチステップ オペレーションをご覧ください。
@transaction ミューテーション ミューテーションが常にデータベース トランザクションで実行されるようにします。マルチステップ オペレーションをご覧ください。

次のステップ

関連情報: