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 は、ディレクティブ などのカスタマイズされた GraphQL クエリを拡張します @auth および @transaction

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

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

またはアップサート。

# 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 は、フィールドの更新に次のオペレーションをサポートしています。

  • incIntInt64FloatDateTimestamp のデータ型を増やす
  • decIntInt64FloatDateTimestamp のデータ型を減らす

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

  • add: ベクター リストを除くリスト型にアイテムがまだ存在しない場合は、アイテムを追加する
  • remove: ベクター リストを除くリスト型にアイテムが存在する場合は、すべてのアイテムを削除する
  • append: ベクター リストを除くリスト型にアイテムを追加する
  • prepend: ベクター リストを除くリスト型にアイテムを先頭に追加する

削除を実行する

もちろん、映画データを削除することもできます。映画の保存者は、物理的な映画をできるだけ長く維持したいと考えています。

# 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

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

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

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

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

高度なミューテーションを作成する: Data Connect 構文を使用して値を指定させるfield_expr

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

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

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

# 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 などのミューテーション フィールドは、指定されたキーを持つレコードが存在しない場合、何も行わないことがあります。同様に、検索で 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結果 にアクセスすることで、この一般的なケースを単一の マルチステップ オペレーションで処理できます。

成功する映画レビュー アプリを作成するには、多くの作業が必要です。新しい例で 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 は、アタッチ先のステップの後に常に実行されるため、response.query にすでにアクセスできることに注意してください。@check

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 で中断されたオペレーションを理解する

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

  • データベース オペレーションが失敗する可能性があります。
  • query @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 ミューテーション ミューテーションが常にデータベース トランザクションで実行されるようにします。 マルチステップ オペレーションをご覧ください。

次のステップ

次の情報もご覧ください。