wawoon blog

Prisma.ioでGraphQL APIサーバーを楽して作る

現在開発中の新規サービスに採用したPrisma.ioがかなり良かったので、紹介記事をまとめてみました。

Prisma.ioとは

Prisma.ioは、MySQLやPostgresといったRDBMSにまるごとGraphQL APIを生やすミドルウェアです。 投稿日(2018/05/29)時点では8,471件のスターがついており、かなり勢いのあるオープンソースプロジェクトです。

https://github.com/prismagraphql/prisma

SDLというDSLを使って、モデルがどのようなフィールドを持つのか、およびモデル間のリレーションなどを宣言的に定義するだけで、

  • prisma deployコマンドによる自動マイグレーション
  • GraphQLのAPIが自動的に生成・公開

されます。

たとえば以下のようなスキーマを定義して、

type User {
  id: ID! @unique
  name: String!
  posts: [Post!]!
}
 
type Post {
  id: ID! @unique
  title: String!
  content: String!
  published: Boolean! @default(value: "false")
  author: User!
}

prisma deployコマンドを実行すると、

以下のようなGraphQL APIを得ることができます。

スクリーンショット 2018-05-29 20.45.50.png

user関連に限ってピックアップすると

  • users
  • usersConnection
  • createUser
  • updateUser
  • deleteUser
  • updateManuUsers

といったQuery, およびMutationが自動的に作成されていることがわかります。 Prisma.ioは、スキーマ定義するだけでテーブルのマイグレーションもしてくれて、かつCRUD操作用のGraphQL APIを自動的に生成してくれるスグレモノです。

Prisma.ioで楽になること

Prisma.ioは、GraphQL APIを作成するためのCRUD操作をラップした内部APIを提供してくれるので、SQLやORMを触らずにGraphQL APIサーバーを書けるようになります。

これまでSQLやORMでリゾルバ実装辛かった

これまで、GraphQLのAPIサーバーを作成するときには、GraphQL APIのインターフェースを定義して、各クエリやミューテーションの実装をするために、SQLやSequelizeやActiveRecordのようなORMを駆使して、データをこねこねする必要がありました。

GraphQLのCRUD APIでリソルバ実装

Prisma.ioをいれておくと、使いやすいCRUD APIが勝手に定義されているのでリゾルバの実装をPrisma.ioが提供してくれているGraphQLを操作するだけで作れてしまいます。

GraphQLとSQLやORMとではインピーダンスミスマッチが大きいので、プログラマが楽にGraphQLを書けるようになったというのが非常に大きなポイントだと思います。

Prisma.ioで楽にならないこと

結局のところ、自分でGraphQLサーバーを書かないとダメです。

Prisma.ioを導入することで、通常のアプリケーションで必要になるAPIというのは勝手に提供されるわけなのですが、**Prisma.ioは直接フロントエンドからアクセスされることを意図していません。**Prisma.ioはテーブル内の情報をすべて一律に公開するので、パスワードやユーザーの個人情報など外部に公開してはならないデータへのアクセスを許してしまいます。

そのため、外部公開用のGraphQLサーバーを立て、そこで外部からのリクエストを処理することが必要です。ざっくりイメージを掴んでもらうためにいくつかチュートリアルから例を引っ張ってきました。

外部公開用のAPIサーバーの例

外部公開用のサーバーは何を使ってもいいわけなのですが、公式ではgraphql-yogaというライブラリが推奨されています。

graphql-yogaはNode.jsのライブラリで、Express.jsのミドルウェアとして動作するので、Node.jsでできることはなんでもできます。

例(チュートリアルより)

const { GraphQLServer } = require('graphql-yoga')
const { Prisma } = require('prisma-binding')
 
const resolvers = {
  Query: {
    posts: (_, args, context, info) => {
      // ...
    },
    user: (_, args, context, info) => {
      // ...
    }
  },
  Mutation: {
    createDraft: (_, args, context, info) => {
      // ...
    },
    publish: (_, args, context, info) => {
      // ...
    },
    deletePost: (_, args, context, info) => {
      // ...
    },
    signup: (_, args, context, info) => {
      // ...
    }
  }
}
 
const server = new GraphQLServer({
  typeDefs: 'src/schema.graphql',
  resolvers,
  context: req => ({
    ...req,
    prisma: new Prisma({
      typeDefs: 'src/generated/prisma.graphql',
      endpoint: '__YOUR_PRISMA_ENDPOINT__',
    }),
  }),
})
server.start(() => console.log(`GraphQL server is running on http://localhost:4000`))

たとえば、JWTの検証をするなどの場合は

https://www.prisma.io/docs/reference/upgrade-guides/graphcool-to-prisma/authentication-and-authorization-yaeco6ieth

にあるように、リゾルバの中でjwtの検証を行うことができます。

導入に関する雑感

どうやってデプロイするの?

Prisma.ioを導入すると、内部用のPrismaサーバーと、外部向けのGraphQLサーバーの2台をデプロイする必要があります。問題になるのは内部向けのPrismaサーバーのほうです。

Prismaサーバーについては、公式がやっているPrisma cloudというサービスがあります。

https://www.prisma.io/cloud/

見た目フルマネージドでPrismaサーバーのホスティングなど全部やってくれそうなのですが、ホスティングしてくれるのはデモまでで、本番向けのPrismaサーバーは結局自分で立てる必要があります。

現状、いろいろPrismaサーバーを立てる方法があるみたいなんですが、公式が推奨しているのがAWSのCloud Formationを使って各種サービス(RDS + Fargate + ECSあたり)を構築するチュートリアルがあるので、それに従うのが無難です。

https://www.prisma.io/docs/tutorials/deploy-prisma-servers/aws-fargate-joofei3ahd

Prismaサーバーは何でもできてしまうので、アクセス制限をしっかり掛ける必要があるのはお忘れなく。

スキーマ定義については使い勝手よさそう?

Prisma.ioは、独自のDSLを使ってスキーマを定義する必要があるのですが、大抵の要件についてはなんとかなりそうです。

https://www.prisma.io/docs/reference/service-configuration/data-model/data-modelling-(sdl)-eiroozae8u/

いくつかピックアップすると

  • リレーション貼れる
  • カラムの存在制約を定義できる
  • @default(value: hogehoge)でデフォルト値を設定できる
  • @uniqueでユニーク制約
  • json型も対応
  • @relationで、モデル名と違うカラム定義可。削除時に追従してリレーション先削除可。

などできます。

N+1クエリ大丈夫そう?

調べたところ、prismaサーバー内でのSQLの発行方法などは細かくチューニングできないみたいなのですが、自動的に効率よくクエリを吐いてくれるようです。

https://www.prisma.io/forum/t/do-prisma-resolve-n-1-problem/2792

Prisma will load data efficiently in those cases (2 queries). Prisma has a data loader that will first fetch the users with one query and will then make a deferred pass to fetch all friends.

すでにあるDBにPrisma.io導入できそう?

チュートリアルはComing Soon!らしいです。 https://www.prisma.io/docs/tutorials/setup-prisma/connect-db-with-data/mysql-vuthaine2f

ただし、createdAt, updatedAtといったカラムなどPrismaのシステム内で利用しているカラムがあったり、モデル間のリレーションが自動的にテーブルになっていたりする(PostとUserのリレーションは_PostToUserテーブルにある)ので、移行は大変かもしれません。

https://www.prisma.io/docs/tutorials/database-workflows/data-export-and-import-caith9teiy

感想

まだ新規サービスは開発中なのですが、触ってる感じかなり楽です。PrismaはあくまでORMなので、外部公開用のGraphQLサーバーでちゃんとビジネスロジックを書けば破綻はしないかな、という印象です。一方で、ある程度プロジェクトが進んでPrisma.ioをやめるとなると、テーブルがすでにPrisma.io仕様になってるので他のORMの採用などはかなり厳しそうです。

運用まで回してまた感じたところがあればアップデートしたいと思います。 prismaを使って新規サービス立ち上げたいエンジニアはこちら

参考

https://www.prisma.io/docs/ https://www.prisma.io/cloud/ https://qiita.com/mizchi/items/74fb8b1177e83fe10273