DBや画面を決めていきます。
前回の続きです。
今回はDB周りを整理します。
設計~開発を広い範囲でやると大変ので、まずはフェーズ1の「レビュー投稿機能」にだけ焦点を当てて、アジャイル的な感じで進めていきましょう。
基本設計(簡易)レベルを決めてから、プログラミングに移るという流れです。(共同作業者もいないので、詳細設計レベルは省きます。)
フェーズ1:管理者画面の新規作成
- 管理者ログイン機能
- レビュー投稿機能 (CRUD操作ができる)
設計 - 制作物のイメージを作ります
DB設計
仮想クライアントが既存で運営している静的Webサイトから、レビュー機能に利用しているデータを拾い上げる想定で、リレーションを考えていきます。
前回の記事に記載しましたが、レビュー記事は、ブログタイプとYoutubeタイプの2種類がある前提です。
現在のサイトで表示されているレビュー記事
ブログ記事のイメージ
Youtube記事のイメージ
(個人的に後で視聴してみようかなと思っているNext.jsの動画をおいてみました。良ければ検索してみてください。)
レビューデータのピックアップ
表示されているデータで共通のものは、以下になるかと思います。
- 記事タイトル
- 記事の日付
- 記事の要約文
- 引用元のURL
ブログ記事でのみ必要なデータは、
- 画像URL
Yourube記事でのみ必要なデータは、
- 動画埋め込みコード(これは、YoutubeのURLがわかれば生成できるものとします。)
というところでしょうか。
見えていない部分では、ユーザー公開画面で各種出し分けをするためのフラグが必要そうです。
- Youtube記事のフラグ
- トップページの掲載フラグ
- 下書きフラグ
これらは別のテーブルを作って管理することもできますが、今回は設計・実装の負担を減らすためフラグ管理にしてみます。
※本番記事用のテーブルと下書き記事用のテーブルを双方用意する、という設計手法もあるように思いますが、皆さんどちらで実装することが多いでしょうか??教えて詳しい人!!
製品データの検討
今回の要件では、製品ページにレビュー記事を載せることになります。
ということは、レビューデータを製品データに紐づける必要があります。
製品テーブルの内容精査は別のフェーズで行う予定ですが、エンティティそのものを作って関係性を紐づけておきましょう。ひとまずID、製品名称が設定されていれば現段階は良しとします。
また、仮想クライアントの販売製品は、それぞれ〇〇シリーズのようにグループ分けがなされているようです。
シリーズごとに公開画面の表示順も制御されているので、それも現段階で設計に含めていきます。
利用するツールに依存する項目
今回は開発にあたって、Prisma ORMとCloudinaryを導入します。それらに依存する項目を洗い出します。
Prisma ORM
今回は、1製品に多数のレビュー記事が掲載される想定です。対して、1つのレビュー記事が複数製品に共通して掲載されることもありえます。同じシリーズの製品比較の記事とかを想定しています。
そうすると、レビューテーブルと製品テーブルのリレーションは多対多の関係になります。
Prismaを使ってこの関係を表すには、明示的(Explicit)に設計するか暗示的(Implicit)にPrismaに設計を任せるかのどちらかを選択します。
今回は明示的にする理由もないので、暗示的にPrisma自動生成に任せる方針にします。
詳しい実装は後述。
Cloudinary
Next.js単体では、管理画面でアップロードした画像を即座に公開画面に反映することができないので、画像をアップロード及び配布できる場所が必要になります。そこで今回はCloudinaryを利用します。
Cloudinaryは、画像をアップロードするとパブリックIDなるものが振られます。画像の表示は画像のURLを使うことも、パブリックIDを使うこともできそうなので、現段階ではどちらもテーブルに格納することにしましょう。
ER図
・・・・・・っていうのを、実際に実装しながらER図にまとめてみたのが以下です。
今回は作図ツールにMiroを使ってみました。緑色の_ProductToReview
は、Prismaが自動生成してくれる中間テーブルで、「そういうつなぎ方になっているんだな」とだけご理解いただければOKです。
Next.js, Prismaで使いやすいよう、自動生成以外はキャメルケースにて記載しています。
画面設計
ユーザー公開画面の設計はフェーズ3、製品やジャンルの登録はフェーズ2でやることにするので、今回はレビュー記事を投稿する管理画面の設計だけ進めます。現時点では画面遷移図を作るほど画面数も多くないので、必要になったら作ることにします。
これは初期のざっくりワイヤーフレームなので、実装中にあれこれ足し引きしたくなったら、その都度対応していく感じです。
実際の案件ではきちんとクライアントレビューしてもらってFIXしていきましょうね。火種になりかねません。
実装
DBスキーマ
前回まででNext.jsのcreate app
は終わったので、Prisma導入と、Docker上のDBとの接続、スキーマ作成を進めていきます。
Prisma公式を見ながら進めます。
create-next-app
で作成したアプリディレクトリへ移動し、以下のコマンドを入力します。
npm init -y
npm install typescript ts-node @types/node --save-dev
クイックスタートには、↓のコマンドも記載がありますが、Next.jsのcreate-next-app
の際に'tsconfig.json'が生成されているので、不要だと思います。
npx tsc --init
Prisma CLIのダウンロード
npm install prisma --save-dev
Prismaスキーマファイルの作成と接続設定
npx prisma init
ここまで進めると、Next.jsプロジェクトファイル内にprisma
フォルダが生成されています。中にschema.prisma
というファイルがあり、ここにどんどん追記していくことでDB設計をしていくことができます。
Prismaの良いところは、どのDBSを選んでもアプリ用ソースコードの記述が統一され、かつフロント側からわかりやすい記法が使えるところです。そのあたりは後々確認することにします。
まず、接続するデータベースの設定を書き換えます。初期状態だとPostgreSQLになっているので、これをMySQLに変更します。
datasource db {
- provider = "postgresql"
+ provider = "mysql"
url = env("DATABASE_URL")
}
次に、プロジェクトのルートディレクトリに作成されている.envファイルを編集します。
MySQLの場合の接続URLの形式は、mysql://USER:PASSWORD@HOST:PORT/DATABASE
という形になっています。英大文字で書かれた部分は、前回作ったDocker上のMySQLの設定に合わせて設定しましょう。ここに記載したDATABASE_URL
の値が、上述schema.prisma
のurlに反映されます。
- DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"
+ DATABASE_URL="mysql://hoge:huga@localhost:3306/database"
DBスキーマの設定
schema.prisma
にDBスキーマを追記していきます。
リレーションの記載方法は、こちらを確認しながら進めます。
※本来であれば、DB上に記録する日時は日本時間に合わせたいところなのですが、現在Prismaを使ってそれを実現するのは大変困難なため、UTCで記録することにしています。アップデート求む・・・!
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
//以下を追記します
model Review {
id Int @id @default(autoincrement())
title String? @db.Text
detail String? @db.Text
url String? @db.VarChar(2100)
isYoutube Boolean?
youtubeId String? @db.VarChar(30)
articleDate DateTime? @db.DateTime(0) // Based on UTC time locale
imgUrl String? @db.VarChar(2100)
imgPublicId String? @db.VarChar(40)
displayTop Boolean?
isDraft Boolean?
createdAt DateTime @default(now()) @db.DateTime(0)
updatedAt DateTime @updatedAt @db.DateTime(0)
//Productテーブルとの多対多関係
products Product[]
}
model Product {
id Int @id @default(autoincrement())
name String? @unique @db.VarChar(50)
isDiscontinue Boolean @default(false)
isDraft Boolean?
createdAt DateTime @default(now()) @db.DateTime(0)
updatedAt DateTime @updatedAt @db.DateTime(0)
//Reviewテーブルとの多対多関係
reviews Review[]
//Seriesテーブルとの一対多関係(一の方)
seriesId Int
series Series @relation(fields: [seriesId], references: [id])
}
model Series {
id Int @id @default(autoincrement())
name String @unique @db.VarChar(30)
displayOrder Int
createdAt DateTime @default(now()) @db.DateTime(0)
updatedAt DateTime @updatedAt @db.DateTime(0)
//Productテーブルとの一対多関係 (多の方)
products Product[]
}
この先もフィールドの追加など、DB設計が変動していく予定なので、migration
ではなくdb push
というプロトタイピング用コマンドを使用していきます。
npx prisma db push
db push
は、特定の条件下での利用は推奨されていません。以下、公式サイトの引用とDeepLでの翻訳文を掲載します。
db push is not recommended if:
- You want to replicate your schema changes in other environments without losing data. You can use db push for prototyping, but you should use migrations to commit the schema changes and apply these in your other environments.
- You want fine-grained control over how the schema changes are executed - for example, renaming a column instead of dropping it and creating a new one.
- You want to keep track of changes made to the database schema over time. db push does not create any artifacts that allow you to keep track of these changes.
- You want the schema changes to be reversible. You can use db push again to revert to the original state, but this might result in data loss.
db pushは次のような場合には推奨されません:
- データを失うことなく、スキーマの変更を他の環境に複製したい場合。db push をプロトタイピングに使うことはできますが、スキーマの変更をコミットし、他の環境に適用するにはマイグレーションを使うべきです。
- スキーマ変更の実行方法をきめ細かく制御したい。例えば、あるカラムを削除して新しいカラムを作成する代わりに、そのカラムの名前を変更したい。
- データベーススキーマに加えられた変更を時系列で追跡したい。db pushはこれらの変更を追跡するための成果物を作成しない。
- スキーマの変更を元に戻せるようにしたい。db pushを再度使用して元の状態に戻すことはできますが、その結果データが失われる可能性があります。
Prismaクライアントを生成します。これでアプリ用コード内でPrisma機能を使うことができます。
npx prisma generate
DBの同期確認
ここまでできたら、DBにスキーマが反映されているかを確認しましょう!
Docker用のディレクトリsample-db
に移動してから以下のコマンドを打つことで、コンテナ内に入ることができます。
docker-compose exec db /bin/bash
コンテナに入れたらMySQLを起動しましょう。
sh-4.4# mysql -u hoge -p -D database
Enter password: # MySQLに設定したパスワードを入れます。この場合はhuga。
以下の表示が出たら、MySQL起動OKです。
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 10
Server version: 8.3.0 MySQL Community Server - GPL
Copyright (c) 2000, 2024, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
コマンドラインがmysql>
に変わりますので、ここからはSQLを打ってテーブルを確認していきます。
mysql> show tables;
+--------------------+
| Tables_in_database |
+--------------------+
| Product |
| Review |
| Series |
| _ProductToReview |
+--------------------+
4 rows in set (0.00 sec)
きちんと指定したテーブルが出来上がっていますね!
カラムも確認していきます。
mysql> describe Review;
+-------------+---------------+------+-----+-------------------+-------------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------+------+-----+-------------------+-------------------+
| id | int | NO | PRI | NULL | auto_increment |
| title | text | YES | | NULL | |
| detail | text | YES | | NULL | |
| url | varchar(2100) | YES | | NULL | |
| isYoutube | tinyint(1) | YES | | NULL | |
| youtubeId | varchar(30) | YES | | NULL | |
| articleDate | datetime | YES | | NULL | |
| imgUrl | varchar(2100) | YES | | NULL | |
| imgPublicId | varchar(40) | YES | | NULL | |
| displayTop | tinyint(1) | YES | | NULL | |
| isDraft | tinyint(1) | YES | | NULL | |
| createdAt | datetime | NO | | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
| updatedAt | datetime | NO | | NULL | |
+-------------+---------------+------+-----+-------------------+-------------------+
13 rows in set (0.00 sec)
mysql> describe Product;
+---------------+-------------+------+-----+-------------------+-------------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+-------------+------+-----+-------------------+-------------------+
| id | int | NO | PRI | NULL | auto_increment |
| seriesId | int | NO | MUL | NULL | |
| name | varchar(50) | YES | UNI | NULL | |
| isDiscontinue | tinyint(1) | NO | | 0 | |
| isDraft | tinyint(1) | YES | | NULL | |
| createdAt | datetime | NO | | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
| updatedAt | datetime | NO | | NULL | |
+---------------+-------------+------+-----+-------------------+-------------------+
7 rows in set (0.00 sec)
mysql> describe Series;
+--------------+-------------+------+-----+-------------------+-------------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-------------+------+-----+-------------------+-------------------+
| id | int | NO | PRI | NULL | |
| name | varchar(30) | NO | UNI | NULL | |
| displayOrder | int | NO | | NULL | |
| createdAt | datetime | NO | | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
| updatedAt | datetime | NO | | NULL | |
+--------------+-------------+------+-----+-------------------+-------------------+
5 rows in set (0.00 sec)
mysql> describe _ProductToReview;
+-------+------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------+------+-----+---------+-------+
| A | int | NO | PRI | NULL | |
| B | int | NO | PRI | NULL | |
+-------+------+------+-----+---------+-------+
2 rows in set (0.00 sec)
良さそうですね!修正点がある場合は、schema.prisma
を訂正して、再度db push
をかけます。簡単ですね!
Prisma Studio
アプリフォルダに移動したあと、以下のコマンドを打つことで、http://localhost:5555
でPrisma Studioを起動することができます。テーブル操作がGUIでできるので、ダミーデータを作るときなどに使いましょう。
SQLが苦手な方は、Prisma Studioでテーブルができているか確認してください。
npx prisma studio
次回
次回はNext.jsの特徴をまとめて、実装の方針を考えます。