SMARTCAMP Engineer Blog

スマートキャンプ株式会社(SMARTCAMP Co., Ltd.)のエンジニアブログです。業務で取り入れた新しい技術や試行錯誤を知見として共有していきます。

コーディング不要でGraphQLサーバが作れるPrismaを触ってみて可能性を感じた

スマートキャンプのエンジニア瀧川です。

弊社では昨年からエンジニア合宿を企画していまして、今年は10月15日から17日までの2泊3日で実施しました!

合宿のテーマや全体感は別記事でまとめるかなと思いますが、3日の限られた時間で1チーム(4人)1つのプロダクトを作り、成果として発表する必要がありました。

この条件だとあまり技術的なチャレンジもできないな...と感じてはいたのですが、どうしてもチーム内でGraphQL触りたい欲求が高まってしまったので、なんとか負荷があまりかからない形で導入できないか調べて見つかったのがPrismaというツールでした!

本記事ではPrismaを試した際のメモ、Tips、所感を書いていきます!

(公式でPrisma2がアナウンスされてますが、ほぼ別物なので今回はPrisma1について書いています)

(多分最終的な成果物の進捗は、慣れ親しんだツールを使った場合とほぼ同じだったかなと思っています)

GraphQLとは

GraphQLとは、Web APIのための規格の1つで、「スキーマ定義」と「クエリ定義」によってサーバとクライアントの通信をサポートします。

「スキーマ定義」で標準化された型付きのスキーマを定義し、それに則ったクエリをクライアントから発行することで、安全で柔軟にデータ取得が行えます。

詳しくは公式ドキュメントなどお読みください。

また、GitHubが外部向けAPIとしてGraphQLを採用しているので、こちらで試すのも良いと思います。

developer.github.com

Prismaとは

インストールし、GraphQLのスキーマ定義をするだけで、以下ができるようになる便利なツールです。

  • 起動用docker-compose.yml生成
  • データ管理アプリ(Prisma Admin)
  • CRUDの自動生成(GraphQLサーバの実装)
  • DB(MySQL/PostgreSQL)へのMigration
  • Seed定義
  • CRUDにアクセスするクライアント(JavaScript, TypeScript, Golang)生成

インストール

まず、以下のようにpackage.jsonを定義し、 yarn install などで依存解決します。

package.json

{
  "name": "prisma-sample",
  "devDependencies": {
    "prisma": "^1.34.8",
    "ts-node": "^8.4.1",
    "typescript": "^3.6.3"
  }
}

初期化

Prismaはinitコマンドがあり、それにより必要なファイルを自動生成してくれます。

以下のように yarn prisma init と実行すると、「データベースの接続先をどうするか」「データベースの種類はなにか」「Prismaサーバにアクセスするためのクライアントを生成するか」の質問がでます。

以下では、LocalのDockerでMySQLを新規で起動し、TypeScriptのクライアントを生成するようにしました。

$ yarn prisma init                                          

? Set up a new Prisma server or deploy to an existing server? Create new database
? What kind of database do you want to deploy to? MySQL
? Select the programming language for the generated Prisma client Prisma TypeScrip
t Client

Created 3 new files:                                                              

  prisma.yml           Prisma service definition
  datamodel.prisma    GraphQL SDL-based datamodel (foundation for database)
  docker-compose.yml   Docker configuration file

Next steps:

  1. Start your Prisma server: docker-compose up -d
  2. Deploy your Prisma service: prisma deploy
  3. Read more about Prisma server:
     http://bit.ly/prisma-server-overview

Generating schema... 25ms
Saving Prisma Client (TypeScript) at /Users/takigawa.yosuke/projects/personal/prisma-sample/generated/prisma-client/

✨  Done in 31.93s.

実行後に以下のファイルが生成されていればOKです。

  • prisma.yml
    • Prisma全般の設定を記述する
  • datamodel.prisma
    • GraphQLのスキーマ定義
  • docker-compose.yml
    • Prismaサーバ、DBなど起動用

起動

$ docker-compose up -d
$ docker-compose ps                  
         Name                    Command            State           Ports
----------------------------------------------------------------------------------
prisma-sample_mysql_1    docker-entrypoint.sh       Up      3306/tcp, 33060/tcp
                         mysqld
prisma-sample_prisma_1   /bin/sh -c /app/start.sh   Up      0.0.0.0:4466->4466/tcp

localhost:4600/_admin でPrisma Admin(Sequel Proみたいなデータ閲覧、アップデートなどできる)にアクセスができます。

(localhost:4600 でおなじみのGraphQLのPlaygroundにもアクセスできますがPrisma Adminのほうが使い勝手がよさそうです)

datamodel.prisma に初期状態で User が定義されているので以下を実行し、Prismaサーバを更新するとPrisma AdminでもUserが確認できると思います。

$ yarn prisma deploy

f:id:tkgwy:20191108190746p:plain

これでほぼ準備は完了です!

クエリ

試しにデータ定義を以下のように修正して、「Create」「Read」のクエリを作成してみようと思います。

ユーザの所属する会社を追加する想定でのスキーマ定義を追加します(Company has_many Usersですね)

  • ID! @id でユニークかつインデックス追加をしてくれているようです
    • @hogehoge はPrismaのアノテーションでいくつか種類がありそう
  • 関連定義をする際に、相互に参照定義するほうがクエリを書く際に楽そう

datamodel.prisma

type Company {
  id: ID! @id
  name: String!
  users: [User!]!
}

type User {
  id: ID! @id
  name: String!
  company: Company!
}

※ datamodel.prisma更新後は、Prismaサーバに yarn prisma deploy で反映してください

Mutation

試しに Companyを1つ、Userを2つ生成してみます。

mutation {
  createCompany(data: {
    name: "スマートキャンプ",
    users: {
      create: [
        {name: "瀧川スマ太郎"},
        {name: "瀧川スマ次郎"}
      ]
    }
  }) {
    id
  }
}

Prisma Adminでクエリを実装すると、補完がしっかりと効いていてすばらしいですね!

createCompany などはPrismaによって自動生成されたものです。

createCompany の ブロック {} はmutation成功時に返却してほしいデータを記述しています。

なので実行後のResponseは以下のように自動付与されたIDが含まれています。

{
  "data": {
    "createCompany": {
      "id": "ck2q06vkc006n07980p4l1av0"
    }
  }
}

Query

生成したCompanyとUserをすべて取得するクエリを作成します。

query {
  companies {
    id,
    name,
    users {
      id,
      name
    }
  }
}

結果がこちらになります。

{
  "data": {
    "companies": [
      {
        "id": "ck2q06vkc006n07980p4l1av0",
        "name": "スマートキャンプ",
        "users": [
          {
            "id": "ck2q06vmf006o07989z7b6e1s",
            "name": "瀧川スマ太郎"
          },
          {
            "id": "ck2q06vmo006p0798yllvbja7",
            "name": "瀧川スマ次郎"
          }
        ]
      }
    ]
  }
}

このクエリとレスポンスのわかりやすさはGraphQLのメリットの1つですよね。

ちなみにPrismaでは、データの絞り込み用の関数も組み込まれています(使い勝手は少しむずかしいですが...)

例えば「会社名がキャンプを含み、ユーザ名の後方が次郎ではない」のような条件だと以下のようになります。

query {
  companies(where: {
    name_contains: "キャンプ"
  }) {
    id,
    name,
    users(where: {
      name_not_ends_with: "次郎"
    }) {
      id,
      name
    }
  }
}

このようなPrismaによって生成されているものは非常にたくさんあるので、公式ドキュメントや補完をよくみて慣れていく必要がありそうです。

Tips

Seedを実行する

以下のようにTypeScriptのPrismaクライアントを生成し、それを使った seed.ts をts-nodeで実行してやるだけですね。

prisma.yml

generate:
  - generator: typescript-client
    output: ./generated/prisma-client/

seed:
  run: yarn ts-node ./seed.ts

seed.ts

import { prisma } from './generated/prisma-client'

async function main() {
  await prisma.createCompany({
    name: 'スマートキャンプ'
  })
}

main().catch(e => console.error(e))
$ yarn prisma seed

たまにDBをResetする必要がある

スキーマ定義で関連を変更したり、Seedを2回実行などがエラーになるため、リセットする必要が出てきてしまいます。

ローカルでは以下のコマンド一発ですが、プロダクションではどのように運用するといいのでしょうか...

怖いですね

$ yarn prisma reset

所感

GraphQL自体は、処理がクライアントに寄せられて、必要なデータを都度取得できるのでやりやすさを感じました。

また、型があるので、Prismaのような自動生成するタイプのツールでも補完が効き、導入障壁は低く感じました。

なによりも今回小さなアプリケーションを作成したのもありますが、サーバサイド実装要らなくてフロント実装だけで済んだのは新しい可能性を感じられました...!

ただ複雑なデータを取得する場合、それなりにクエリが複雑になるのと、Prismaの仕様、GraphQLの仕様上うまく取得できない条件があったり(知識不足もあると思いますが)難しさも感じました。

(あと、公式ではPrismaとフロントエンドとの間にもう1階層GraphQLサーバかRest APIサーバを挟むのを想定しているようなので、そのあたりも今回は無視した難しさがありますね)

またバルクインサートできないとかパフォーマンスチューニングできない、スキーマの変更をした際にresetせざるを得なくなるなど、ツールとしての成熟度はまだまだ低いかなと感じました。

ただ、GraphQL自体伸び始めたところで、ようやくプロダクション導入もちらほら聞こえ始めたので、これから大いに飛躍すると思います。

Prisma2はまた毛色が違って、よりスモールで実用的なツールになっているようなので、そちらも今後追っていきたいなと感じました。

なにはともあれ、GraphQL、Prisma、新感覚でおもしろかったです!

合宿最高でした!