データモデルをカスタマイズする
Amplifyは、GraphQLスキーマで@modelディレクティブで注釈が付けられたGraphQLタイプに対して、Amazon DynamoDBデータベーステーブルを自動的に作成します。@hasOne、@hasMany、@belongsTo、および@manyToManyディレクティブを介して、データモデル間の関係を作成できます。
データベーステーブルのセットアップ
次のGraphQLスキーマは、「Todo」のデータベーステーブルを自動的に作成します。@modelは、データベーステーブルに主キーとしてidフィールドも自動的に追加します。主キーをカスタマイズする方法を学ぶために、主キーを構成するを参照してください。
type Todo @model { content: String}amplify pushまたはcdk deploy時に、Amplifyはtodoデータベーステーブルと、作成、読み取り、更新、削除、およびリスト操作を実行するための対応するGraphQL APIをデプロイします。
さらに、@modelは、タイプにcreatedAtとupdatedAtヘルパーフィールドも追加します。これらのフィールドの値は、明示的に上書きされない限り、クライアント側では読み取り専用です。詳細については、作成および更新タイムスタンプをカスタマイズするを参照してください。
次のクエリを実行して、すべてのtodoをリストしてみてください:
query QueryAllTodos { listTodos() { todos { items { id content createdAt updatedAt } } }}主キーを構成する
@modelディレクティブを持つすべてのGraphQLタイプは、自動的に主キーとして設定されたidフィールドを持ちます。@primaryKeyディレクティブで別の必須フィールドをマークすることで、この動作をオーバーライドできます。
下の例では、todoIdがデータベースの主キーであり、@modelディレクティブによってidフィールドは自動的に作成されなくなります。
type Todo @model { todoId: ID! @primaryKey content: String}さらに設定しない場合、その主キーフィールドの完全な等値マッチでのみTodoをクエリできます。上の例では、これはtodoIdフィールドです。
注意:主キーが設定されてデプロイされた後、データベーステーブルを削除して再作成することなく、それを変更することはできません。
「ソートキー」を指定して、異なるフィールドの組み合わせを主キーとして使用することもできます。これにより、指定された「ソートキーフィールド」に対してより高度なソートおよびフィルタリング条件を適用できます。
type Inventory @model { productID: ID! @primaryKey(sortKeyFields: ["warehouseID"]) warehouseID: ID! InventoryAmount: Int!}上のスキーマにより、異なる条件を渡して正しい在庫項目をクエリできます:
query QueryInventoryByProductAndWarehouse($productID: ID!, $warehouseID: ID!) { getInventory(productID: $productID, warehouseID: $warehouseID) { productID warehouseID inventoryAmount }}セカンダリインデックスを構成する
AmplifyはAmazon DynamoDBテーブルを@modelタイプの基盤となるデータソースとして使用します。キー値データベースの場合、「セカンダリインデックス」でアクセスパターンをモデル化することが重要です。@indexディレクティブを使用して、セカンダリインデックスを構成します。
Amazon DynamoDBは、任意のスケールで1桁のミリ秒パフォーマンスを提供するキー値およびドキュメントデータベースですが、アクセスパターンに合わせて機能させるには少し前もった検討が必要です。DynamoDBクエリ操作は、データを効率的にクエリするために最大2つの属性を使用できます。クエリに渡された最初のクエリ引数(ハッシュキー)は厳密な等値を使用する必要があり、2番目の属性(ソートキー)はgt、ge、lt、le、eq、beginsWith、およびbetweenを使用できます。DynamoDBはほとんどのアプリケーションに十分な強力なさまざまなアクセスパターンを効果的に実装できます。
セカンダリインデックスは、「ハッシュキー」と、オプションで「ソートキー」で構成されます。「ハッシュキー」を使用して厳密な等値を実行し、「ソートキー」を使用してより大きい(gt)、以上(ge)、より小さい(lt)、以下(le)、等しい(eq)、で始まる、およびbetweenの操作を実行します。
type Customer @model { id: ID! name: String! phoneNumber: String accountRepresentativeID: ID! @index}下のクライアントクエリ例により、accountRepresentativeIDに基づいて「Customer」レコードをクエリできます:
query QueryCustomersForAccountRepresentative($accountRepresentativeID: ID!) { customersByAccountRepresentativeID( accountRepresentativeID: $accountRepresentativeID ) { customers { items { id name phoneNumber } } }}queryFieldまたはnameをオーバーライドして、GraphQLクエリ名またはセカンダリインデックス名をカスタマイズできます:
type Customer @model { id: ID! name: String! phoneNumber: String accountRepresentativeID: ID! @index(name: "byRepresentative", queryField: "customerByRepresentative")}query QueryCustomersForAccountRepresentative($representativeId: ID!) { customerByRepresentative(accountRepresentativeID: $representativeId) { customers { items { id name phoneNumber } } }}オプションでソートキーを構成するには、sortKeyFieldsパラメータに追加フィールドを指定します:
type Customer @model @auth(rules: [{ allow: public }]) { id: ID! name: String! @index(name: "byNameAndPhoneNumber", sortKeyFields: ["phoneNumber"], queryField: "customerByNameAndPhone") phoneNumber: String accountRepresentativeID: ID! @index下のクライアントクエリ例により、「Customer」をnameに基づいてクエリし、phoneNumberに基づいてフィルタできます:
query MyQuery { customerByNameAndPhone(phoneNumber: { beginsWith: "+1" }, name: "Rene") { items { id name phoneNumber } }}高度な設定
生成されたクエリ、ミューテーション、サブスクリプションの名前を変更する
目的の名前を指定することで、@model生成のGraphQLクエリ、ミューテーション、サブスクリプションの名前をオーバーライドできます。
type Todo @model(queries: { get: "queryFor" }) { name: String! description: String}上の例では、単一のTodo要素を取得するためにqueryForTodoクエリを実行できます。
生成されたクエリ、ミューテーション、サブスクリプションを無効にする
値をnullに割り当てることで、特定の操作を無効にできます。
type Todo @model(queries: { get: null }, mutations: null, subscriptions: null) { name: String! description: String}上の例はgetTodoクエリ、すべてのミューテーション、およびすべてのサブスクリプションを無効にしながら、listTodoなどの他のクエリの生成を許可します。
カスタムクエリを作成する
getクエリを無効にして、単一のTodoモデルを取得できるカスタムクエリを作成できます。
type Query { getMyTodo(id: ID!): Todo @function(name: "getmytodofunction-${env}")}上の例は、このクエリ用のlambda関数を呼び出すために@functionディレクティブを使用するカスタムクエリを作成します。
クエリ、ミューテーション、およびサブスクリプションのタイプ定義については、@modelディレクティブのタイプ定義を参照してください。
作成および更新タイムスタンプをカスタマイズする
@modelディレクティブは、各エンティティにcreatedAtとupdatedAtタイムスタンプを自動的に追加します。タイムスタンプフィールド名は、ディレクティブにtimestamps属性を渡すことで変更できます。
type Todo @model(timestamps: { createdAt: "createdOn", updatedAt: "updatedOn" }) { name: String! description: String}例えば、上のスキーマにより、以下の内容をクエリできます:
type Todo { id: ID! name: String! description: String createdOn: AWSDateTime! updatedOn: AWSDateTime!}サブスクリプション(リアルタイム更新)のアクセスレベルを変更する
デフォルトでは、リアルタイム更新はすべての@modelタイプに対してオンになっています。つまり、顧客はリアルタイム更新を受け取り、認可ルールは初期接続時に適用されます。そのモデルのサブスクリプションをオフにするか、リアルタイム更新をパブリックにして、すべてのサブスクライバーが受信できるようにすることもできます。
type Todo @model(subscriptions: { level: off }) { # or level: public name: String! description: String}2つのモデル間に複数の関係を作成する
同じタイプの2つの接続を作成するために関係ディレクティブを使用する場合は、接続フィールド名を明示的に指定する必要があります。
type Individual @model { id: ID! homeAddress: Address @hasOne shippingAddress: Address @hasOne}
type Address @model { id: ID! homeIndividualID: ID shippingIndividualID: ID homeIndividual: Individual @belongsTo(fields: ["homeIndividualID"]) shipIndividual: Individual @belongsTo(fields: ["shippingIndividualID"])}複合主キーを持つモデルへの関係
主キーが_ソートキー_に加えて_ハッシュキー_で定義されている場合、それは複合主キーと呼ばれます。
@hasOne、@hasMany、または@belongsToディレクティブでfields引数を明示的に定義し、複合主キーを持つモデルを参照する場合は、fields引数の値を特定の順序で設定する必要があります:
- 最初の値は常に関連モデルの主キーである必要があります。
- 残りの値は、関連モデルの
@primaryKeyディレクティブで指定されたsortKeyFieldsと一致する必要があります。
type Project @model { projectId: ID! @primaryKey(sortKeyFields: ["name"]) name: String! team: Team @hasOne(fields: ["teamId", "teamName"]) teamId: ID # customized foreign key for child primary key teamName: String # customized foreign key for child sort key}
type Team @model { teamId: ID! @primaryKey(sortKeyFields: ["name"]) name: String!}type Project @model { projectId: ID! @primaryKey(sortKeyFields: ["name"]) name: String! team: Team @hasOne(fields: ["teamId", "teamName"]) teamId: ID # customized foreign key for child primary key teamName: String # customized foreign key for child sort key}
type Team @model { teamId: ID! @primaryKey(sortKeyFields: ["name"]) name: String! project: Project @belongsTo(fields: ["projectId", "projectName"]) projectId: ID # customized foreign key for parent primary key projectName: String # customized foreign key for parent sort key}type Post @model { postId: ID! @primaryKey(sortKeyFields: ["title"]) title: String! comments: [Comment] @hasMany(indexName: "byPost", fields: ["postId", "title"])}
type Comment @model { commentId: ID! @primaryKey(sortKeyFields: ["content"]) content: String! postId: ID @index(name: "byPost", sortKeyFields: ["postTitle"]) # customized foreign key for parent primary key postTitle: String # customized foreign key for parent sort key}type Post @model { postId: ID! @primaryKey(sortKeyFields: ["title"]) title: String! comments: [Comment] @hasMany(indexName: "byPost", fields: ["postId", "title"])}
type Comment @model { commentId: ID! @primaryKey(sortKeyFields: ["content"]) content: String! post: Post @belongsTo(fields: ["postId", "postTitle"]) postId: ID @index(name: "byPost", sortKeyFields: ["postTitle"]) # customized foreign key for parent primary key postTitle: String # customized foreign key for parent sort key}セカンダリインデックスをGraphQLクエリなしで生成する
セカンダリインデックスに対するクエリ作成が自動的であるため、対応するクエリがAPIに存在しないセカンダリインデックスを定義する場合は、queryFieldパラメータをnullに設定します。
type Customer @model { id: ID! name: String! phoneNumber: String accountRepresentativeID: ID! @index(queryField: null)}GraphQLファイルを分割する
AWS Amplifyは、GraphQLスキーマを複数の.graphqlファイルに分割することをサポートしています。
amplify/backend/api/<api-name>/schema/ディレクトリを作成することで開始できます。例として、ブログサイトのスキーマをBlog.graphql、Post.graphql、およびComment.graphqlファイルを作成して分割することができます。
その後、amplify api gql-compileを実行し、出力ビルドスキーマにはスキーマファイル全体で宣言されたすべてのタイプが含まれます。
プロジェクトが成長するにつれて、プロジェクトのサイズとメンテナンス要件に応じて、カスタムクエリ、ミューテーション、およびサブスクリプションを整理することができます。それらをすべて1つのファイルに統合することも、対応するモデルと同じ場所に配置することもできます。
単一のQuery.graphqlファイルを使用する
この方法では、すべてのクエリを単一のQuery.graphqlファイルに統合することが含まれます。これは小規模なプロジェクトや、すべてのクエリを1つの場所に保ちたい場合に便利です。
-
amplify/backend/api/<api-name>/schema/ディレクトリで、Query.graphqlという名前のファイルを作成します。 -
複数のスキーマファイルからすべてのクエリタイプ定義を
Query.graphqlファイルにコピーします。 -
すべてのクエリが適切にフォーマットされ、単一の
type Query { ... }ブロック内に囲まれていることを確認します。
extendキーワードを使用する
複数のスキーマファイルでQueryタイプを宣言すると、amplify api gql-compileを実行するときに以下のようなスキーマ検証エラーが発生します:
🛑 Schema validation failed.
There can be only one type named "Query".AmplifyGraphQLスキーマはextendキーワードをサポートしており、これにより追加のフィールドを持つタイプを拡張できます。この場合、カスタムクエリ、ミューテーション、およびサブスクリプションを複数のファイルに分割することもできます。これは、より大規模でより複雑なプロジェクトに対してより理想的である可能性があります。
-
プロジェクトのアーキテクチャに従ってGraphQLスキーマを複数のファイルに整理します。
-
ファイルの1つ(例えば、
schema1.graphql)で、タイプを通常通り宣言します:
type Query { # initial custom queries myQuery: String @function(name: "myQueryFunction-${env}")}- 他のスキーマファイル(例えば、
schema2.graphql)で、extendキーワードを使用してタイプを追加します:
extend type Query { # additional custom queries myQuery2: String @function(name: "myQuery2Function-${env}")}- Amplifyディレクティブを使用して拡張型のフィールドに機能を追加します。Amplifyは
Query、Mutation、およびSubscription型拡張のフィールドで@auth、@function、および@httpディレクティブをサポートしています。または、extendキーワードを使用して、カスタムリゾルバーを使用するカスタムクエリ、ミューテーション、およびサブスクリプションを整理できます。
動作方法
モデルディレクティブ
@modelディレクティブは以下を生成します:
- デフォルトでPAY_PER_REQUEST課金モードが有効になっているAmazonDynamoDBテーブル。
- 上記のテーブルにアクセスするように構成されたAWS AppSync DataSource。
- 上記のテーブルを代理でAWS AppSyncを呼び出すことを可能にするAWS IAMロール。
- 最大8つのリゾルバー(作成、更新、削除、取得、リスト、onCreate、onUpdate、onDelete)ですが、これは@modelディレクティブの queries、mutations、および subscriptions引数を介して設定可能です。
- 作成、更新、および削除ミューテーション用の入力オブジェクト。
- リストクエリおよび関係フィールド内のオブジェクトをフィルタできるフィルタ入力オブジェクト。
- リストクエリについて、返されるオブジェクトのデフォルト数は100です。limit引数を設定することでこの動作をオーバーライドできます。
@modelディレクティブのタイプ定義
directive @model( queries: ModelQueryMap mutations: ModelMutationMap subscriptions: ModelSubscriptionMap timestamps: TimestampConfiguration) on OBJECT
input ModelMutationMap { create: String update: String delete: String}
input ModelQueryMap { get: String list: String}
input ModelSubscriptionMap { onCreate: [String] onUpdate: [String] onDelete: [String] level: ModelSubscriptionLevel}
enum ModelSubscriptionLevel { off public on}
input TimestampConfiguration { createdAt: String updatedAt: String}関係ディレクティブ
関係ディレクティブは@hasOne、@hasMany、@belongsToおよび@manyToManyです。
@hasOneは以下を生成します:
- 子モデルの主キーおよびソートキーフィールドを参照する親タイプの外部キーフィールド。
- 作成および更新ミューテーション親入力オブジェクトの外部キーフィールド。
@hasOneディレクティブのタイプ定義
directive @hasOne(fields: [String!]) on FIELD_DEFINITION@hasManyは以下を生成します:
- 親モデルの主キーおよびソートキーフィールドを参照する子タイプの外部キーフィールド。
- 作成および更新ミューテーション子入力オブジェクトの外部キーフィールド。
- 子タイプAmazonDynamoDBテーブルのグローバルセカンダリインデックス(GSI)。
@hasManyディレクティブのタイプ定義
directive @hasMany( indexName: String fields: [String!] limit: Int = 100) on FIELD_DEFINITION- 返されるネストされたオブジェクトのデフォルト数は100です。limit引数を設定することでこの動作をオーバーライドできます。
@belongsToは以下を生成します:
- 関連モデルの主キーおよびソートキーフィールドを参照する外部キーフィールド。
- 作成および更新ミューテーション入力オブジェクトの外部キーフィールド。
@belongsToディレクティブのタイプ定義
directive @belongsTo(fields: [String!]) on FIELD_DEFINITION@manyToManyは以下を生成します:
relationNameの名前を持つ中間モデルタイプを定義する結合テーブル。- 両方のモデルの主キーおよびソートキーフィールドを参照する結合テーブルの外部キーフィールド。
- 中間モデル入力オブジェクト作成および更新ミューテーションの外部キーフィールド。
@manyToManyディレクティブのタイプ定義
directive @manyToMany( relationName: String! limit: Int = 100) on FIELD_DEFINITION- 返されるネストされたオブジェクトのデフォルト数は100です。limit引数を設定することでこの動作をオーバーライドできます。