Prismaで継続的にER図が更新される仕組みを試してみました

ER図などを人間が手動で更新し続けるのには無理があるなとよく思います。ということで自動でER図を生成してくれる仕組みを試してみました。
2023.07.13

こんにちは。AWS事業本部モダンアプリケーションコンサルティング部に所属している今泉(@bun76235104)です。

設計書は作った。だが更新されているとは言っていない。

そんな体験ありませんか?私はあります。

人間が手動でやる必要がない作業はあると思いますので、PrismaというORMを利用して、ER図をMermaid.jsの仕組みで生成するライブラリを試してみました。

ついでにER図のコミット漏れを防ぐためのGitHub Actionsの設定を書いてみましたので共有します。

先に結論!!

こちら長いので折り畳みますが、以下のようにPrismaのスキーマファイルを記述しています。

schema.prisma

schema.prisma

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
}

// ER図生成用途で追加
generator erd {
  provider = "prisma-erd-generator"
  theme = "forest"
  output = "ERD.md"
  includeRelationFromFields = true
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

model User {
  userId String @id @default(uuid())
  userProfile UserProfile?
  userActive UserActive?
  userDeleted UserDeleted?
  posts Post[]
}

model UserActive {
  userId String @id
  createdAt DateTime @default(now())
  user User @relation(fields: [userId], references: [userId])
}

model UserProfile {
  userId String @id
  name String
  firstName String
  lastName String
  middleName String
  age Int
  createdAt DateTime @default(now())
  user User @relation(fields: [userId], references: [userId])
}

model UserDeleted {
  userId String @id @default(uuid())
  createdAt DateTime @default(now())
  user User @relation(fields: [userId], references: [userId])
}

model Post {
  title String @id
  userId String
  user User @relation(fields: [userId], references: [userId])
}

以下のコマンドでマイグレーションを行うとER図が生成されます。

prisma migrate dev #prisma generate でもOKです

20230713_prisma_erd_image

出力されるmermaid.jsのER図
erDiagram

  "User" {
    String userId "?️"
    }
  

  "UserActive" {
    String userId "?️"
    DateTime createdAt 
    }
  

  "UserProfile" {
    String userId "?️"
    String name 
    String firstName 
    String lastName 
    String middleName 
    Int age 
    DateTime createdAt 
    }
  

  "UserDeleted" {
    String userId "?️"
    DateTime createdAt 
    }
  

  "Post" {
    String title "?️"
    String userId 
    }
  
    "User" o{--}o "UserProfile" : "userProfile"
    "User" o{--}o "UserActive" : "userActive"
    "User" o{--}o "UserDeleted" : "userDeleted"
    "User" o{--}o "Post" : "posts"
    "UserActive" o|--|| "User" : "user"
    "UserProfile" o|--|| "User" : "user"
    "UserDeleted" o|--|| "User" : "user"
    "Post" o|--|| "User" : "user"

何が嬉しいのか

  • 実際のスキーマファイルに従って自動でER図が生成される
    • ブランチがマージされる度にER図を書き直す必要がありません
  • mermaid.jsの形式で出力可能であるため、GitHubでもER図を表示できます
    • 開発者の近くにドキュメントがあるというのは非常に重要なことだと考えています
    • いつでもER図で思考を整理できます
  • mermaid.jsの形式で出力可能であるため、次の設計の検討時にも利用できます(個人的に重要)
    • 出力されたマークダウンに記載されているmermaid.jserDiagramの部分をコピーして追加したい列やテーブルを追記できます
    • GitHubのIssueで仕様のやりとりをするのにもピッタリだと思いました

参考にした記事

今回prisma-erd-generatorというライブラリを利用させていただいたのですが、存在を知ったのはこちらのZennの記事のおかげです。

@terrierscript さんありがとうございます。

手順

以下に手順を記載します。

設定ファイルやその他ファイルの全容を確認したい方は、以下のリポジトリをご覧ください。

前提条件

私のローカルPCはMacを利用しています。

Windows環境などでは未検証ですが、ご了承ください。

その他 nodenpm コマンドも利用可能な状態という前提で記事を執筆しています。

Key Value
OS macOS Monterey
npm 9.7.1

prismaの導入

Quickstart with TypeScript & SQLiteを参考に、Prismaの初期設定をします。

npm init -y
npm i -D prisma

また、今回SqLiteではなくMySQLを利用したかったため、docker-compose.yml を以下のように用意します。

version: '3'
services:
  database:
    image: mysql:8
    container_name: db
    command: --default-authentication-plugin=mysql_native_password
    ports:
      - '3306:3306'
    volumes:
      - db-volume-sample:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: 'password' #本番でこのようにパスワードを利用・直書きはやめましょう
      MYSQL_DATABASE: sample

volumes:
  db-volume-sample:

以下コマンドでコンテナを立ち上げます。

docker compose up

次に以下のようにPrismaの設定ファイルを用意しておきます。

prisma/schema.prisma

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

model User {
  userId String @id @default(uuid())
  userProfile UserProfile?
  userActive UserActive?
  userDeleted UserDeleted?
  posts Post[]
}

model UserActive {
  userId String @id
  createdAt DateTime @default(now())
  user User @relation(fields: [userId], references: [userId])
}

model UserProfile {
  userId String @id
  name String
  firstName String
  lastName String
  middleName String
  age Int
  createdAt DateTime @default(now())
  user User @relation(fields: [userId], references: [userId])
}

model UserDeleted {
  userId String @id @default(uuid())
  createdAt DateTime @default(now())
  user User @relation(fields: [userId], references: [userId])
}

model Post {
  title String @id
  userId String
  user User @relation(fields: [userId], references: [userId])
}

.envファイルには以下のようにDBの情報を記載しておきます。

DATABASE_URL="mysql://root:password@localhost:3306/sample"

ここまでの状態で、npx prisma migrate dev を実行することで、DBに User テーブルなどが作成されれば準備OKです。

prisma-erd-generatorの設定

次にprisma-erd-generatorのインストールをします。

GitHubのReadmeに記載のとおり、以下コマンドを実行します。

npm i -D prisma-erd-generator @mermaid-js/mermaid-cli

ただし、後ほどER図の生成時に以下のライブラリが足りないとエラーが出たため追加で以下のインストールも行いました。

# エラーの内容
Error: Cannot find module '@prisma/generator-helper'
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
# 追加でインストールしました
npm i -D @prisma/generator-helper

次に prisma/schema.prismaに以下を追記します。

prisma/schema.prisma

generator client {
  provider = "prisma-client-js"
}

// これを追記しました
generator erd {
  provider = "prisma-erd-generator"
  theme = "forest"
  output = "ERD.md"
  includeRelationFromFields = true
}

これで準備OKです。

以下コマンドを実行することで prisma/ERD.md が生成されました。

npx prisma generate

20230713_prisma_erd_image

出力されるmermaid.jsのER図
erDiagram

  "User" {
    String userId "?️"
    }
  

  "UserActive" {
    String userId "?️"
    DateTime createdAt 
    }
  

  "UserProfile" {
    String userId "?️"
    String name 
    String firstName 
    String lastName 
    String middleName 
    Int age 
    DateTime createdAt 
    }
  

  "UserDeleted" {
    String userId "?️"
    DateTime createdAt 
    }
  

  "Post" {
    String title "?️"
    String userId 
    }
  
    "User" o{--}o "UserProfile" : "userProfile"
    "User" o{--}o "UserActive" : "userActive"
    "User" o{--}o "UserDeleted" : "userDeleted"
    "User" o{--}o "Post" : "posts"
    "UserActive" o|--|| "User" : "user"
    "UserProfile" o|--|| "User" : "user"
    "UserDeleted" o|--|| "User" : "user"
    "Post" o|--|| "User" : "user"

テーブル規模が大きくなると、もっとごちゃついてくると思いますが、スキーマから自動で生成してくれるのは非常にありがたいですね。

おまけ ER図のコミット漏れに気づく仕組み

prisma/ERD.mdprisma migrate dev コマンドでも生成・更新されます。

Prisma Migrate in development and productionにもあるとおり、通常開発環境でマイグレーションを実行したい場合 prisma migrate dev を実行されると思います。

そのたびprisma/ERD.mdも更新されると思うのですが、うっかりコミットを漏らしてしまうとスキーマファイルとER図に乖離が生じる可能性があります。

そこで、GitHub Actionsの設定ファイルを追加して、ER図のコミット漏れを検知する仕組みを考えてみました。

まず package.json にER図を生成するためのscriptsを定義しています。

package.json

{
  "name": "prisma-cicd",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "devDependencies": {
    "@mermaid-js/mermaid-cli": "^10.2.2",
    "@prisma/generator-helper": "^4.16.1",
    "@types/node": "^20.3.2",
    "prisma-erd-generator": "^1.6.2",
    "ts-node": "^10.9.1",
    "typescript": "^5.1.3"
  },
  "scripts": {
    // ER図生成のためのコマンドをscriptsに定義
    "erd": "prisma generate"
  },
  "dependencies": {
    "@prisma/client": "4.16.1",
    "prisma": "^4.16.1"
  }
}

そして、以下のように .github/workflows/check_er.yml を準備します。

.github/workflows/check_er.yml

name: Pull Request Check

on:
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: 20
      - run: npm install
      - name: makeERD
        run: |
          npm run erd
      - id: checkErdDiff
        run: echo "changed=$(git diff --name-only prisma/ERD.md)" >> $GITHUB_OUTPUT
      - run: echo ${{ steps.checkErdDiff.outputs.changed}}
      - if: ${{ steps.checkErdDiff.outputs.changed != '' }}
        run: |
          echo "ERD.md has changed.Please update ERD.md and commit it."
          exit 1

これによりPullリクエスト作成時に実際にスキーマファイルからER図を生成してdiffをチェックしています。

私が実際に「スキーマでテーブルに列を追加したけどERD.mdをコミットせずにPull Requestを作成した」際には以下のように、更新漏れを検知してくれました。

20230713_prisma_erd_actions_fail

また、きちんとERD.mdの変更をコミットして再度プッシュすることで、このCIが通ることを確認しました。

さいごに

ER図が自動で生成される仕組みを試してみました。

大規模なプロジェクトだとER図はかなりごちゃつくとは思いますが、だからこそ人間が手動で更新し続けるのは難しいと思うので、試してみたい課題だなと感じました。

昨今ではSwagger-UIなどを利用して、OpenAPI仕様を自動で生成するなど、人間が手で更新してきていた仕組みを自動化する仕組みがありますよね。

今後もこれらを活用して、開発者が本当に価値あることに集中できるような仕組みづくりを試していきたいと思います。

以上、今泉でした。最後まで見ていただきまして、本当にありがとうございました。