見出し画像

AWS Ampliy リレーションモデルのクエリの無効化やカスタマイズをする方法

課題

@manyToManyを使うと自動でリレーションモデルが作成され、それに対するクエリも作成されます。
今回、これらのクエリを無効化したり、リレーションモデルを返り値にしたカスタムクエリを作成したいケースに遭遇しましたが、方法を調べても中々見つかりません…。

具体的に説明すると次のようなケースです。
UserとProject、そしてProjectに所属するUserを管理するUserProjectがあります。schema.graphqlには以下のように定義されています。

type User @model {
  id: ID!
  name: String!
  projects: [Project] @manyToMany(relationName: "ProjectUser")
}

type Project @model {
  id: ID!
  name: String!
  users: [User] @manyToMany(relationName: "ProjectUser")
}

これをビルドすると以下の様なリレーションモデルが生成されます。

type ProjectUser {
  id: ID!
  userId: ID!
  projectId: ID!
  user: User!
  project: Project!
}

このリレーションモデルに対してlistProjectUsersやcreateProjectUserといったクエリも生成されます。

今回、KanbanGantt(仮)の仕様としてupdateProjectUserクエリを生成せずクライアントサイドから実行できないようにする必要がありました。また、deleteProjectUserクエリの処理は複数の関連モデルの削除など、処理が複雑なので、VTLリゾルバーではなくLambdaリゾルバーを使用したいと考えました。ただし、クエリの名前はdeleteProjectUserのままで返り値の方もProjectUserのままで今までの使い勝手から変わらない様にしたいと考えました。

リレーションモデルではない普通のモデルであれば@modelの引数を指定すれば無効化することができます。

しかし、リレーションモデルに対するクエリの生成を無効化する方法を調べても見つかりません。
クエリを無効化するだけであれば、自動生成されたupdateProjectUserクエリのVTLで必ずエラーを返すように実装すれば良いかと思ったのですが、問題なのはdeleteProjectUserクエリです。

deleteProjectUserクエリをLambdaリゾルバーにしたいのでamplify add functionで関数を作成し、schema.graphqlに以下の様に記述しても自動生成されるdeleteProjectUserクエリの名前と競合したり、返り値のProjectUserの定義が無いためビルドに成功しません。

type Mutation {
  deleteProjectUer(id: ID!): ProjectUser @function(name: "function_name-${env}")
}

解決策

色々悩んだ末、現状の最適解として@manyToManyと同じ構成を@hasManyと@belongsTo、リレーションモデルの手動定義で実現するという方法を採ることにしました。
@manyToManyは結局リレーションモデルの作成などを自動でやってくれているだけなので、手動で定義しても同じことができるのではないかと。
この方法を採るとschema.graphqlのモデル定義は以下の様になります。

type User @model @auth(...(略)) {
  id: ID!
  name: String! 
  email: AWSEmail!
  projects: [ProjectUser] @hasMany(indexName: "byUser", fields: ["id"])
}

type Project @model @auth(...(略)) {
  id: ID!
  name: String!
  users: [ProjectUser] @hasMany(indexName: "byProject", fields: ["id"])
  owner: String
}

// リレーションモデルの定義
// createProjectUserだけ生成
type ProjectUser @model(mutations: {create: "createProjectUser", update: null, delete: null}) @auth(rules: [{allow: private}]) {
  id: ID!
  userId: ID! @index(name: "byUser")
  projectId: ID! @index(name: "byProject")
  user: User! @belongsTo(fields: ["userId"])
  project: Project! @belongsTo(fields: ["projectId"])
}

// ProjectUserの@modelでdelete: nullにしているのでdeleteProjectUserが使える
// ProjectUserの定義があるので、返り値の型に使える
type Mutation {
  deleteProjectUser(id: ID!): ProjectUser @function(name: "function_name-${env}")
}

これで諸々うまくいきそうです。indexの定義など自分でやらなければいけないので結構な記述量です。@manyToManyが全部やってくれていたんですね。自動でやってくれるのはかなり便利ですが、今まで開発を進めてきた経験上、リレーションモデルの細かい管理をしたいケースが多いので@manyToManyを使うのはかなり限定的になりそうです。
また、@manyToManyで自動生成されるリレーションモデルはamplify update functionで権限設定できるリソースの一覧に表示されないのですが、この方法だと自分で定義しているので、amplify update function時にも選択できて便利でした。

この後実装を進めていくと、この方法だと都合が悪い部分など段々わかってくるかもしれませんので、その時はまた記事にします。
それではまた次回。


もしこの記事があなたのお役に立てたなら幸いです。 よろしければサポートをお願いします。今後の制作資金にさせていただきます!