身の回りにセキレイがたくさんいるので、 それをモチーフにしたTo-Do ListをNext.jsで作ります。
実現したい機能は以下です: [ ] localStorageとClient Componentを適切に使用し、ある程度のオフライン使用に耐える [x] セキレイのかわいいアイコンをメインに配置して、リアルに尻尾を振る [x] Server Componentも使用し、Client ComponentとのCompositionを試みる [x] drizzle orm によるデータベースとのやり取りを行う
他のプロジェクト開発で使ったノウハウを反映してみます データベース関連はTypeScriptのみで管理できそうなので、 いつも同じ開発用データを保持したtmpfsを用いた設定に切り替えてみます。 ディレクトリ構成もこの際に直しておきます。
X OAuthでは本番用と開発用のリダイレクト先が同じだったので
特別な設定なくnext dev
でもnext build && next start
でも認証が機能しましたが、
sekirei-todoではそうはいかないらしい、少し考えます...
→.envファイルはgit管理範囲外なので、適宜編集することにしました。 本番環境は本番環境用の.envファイルが別に用意されているので問題なさそうです。
これまでルートURLを認証必須として各ユーザのタスクを表示していましたが、 WebアプリのURLを貼って紹介する際にogimageの取得が難しいことに気付きました。 サービスの説明自体も欲しいですし、ルートURLはSekirei Todo自体の説明として 使ってみようと思います。
next build && next start
ではNODE_ENV=production
が定義されている
next dev
では何もない
.env.* はNext.js環境に自動でロードされて、
*部分にはNODE_ENV
が入るらしい、
NODE_ENV=development
とすれば、.env.developmentが、
NODE_ENV=production
とすれば、.env.productionが読み込まれるそうなので
これを活用してみたい
@startuml SPAのデータのやりとり
title SPAのデータのやりとり
Client -> "Web Server": WebアプリのURLにアクセス
note over Client
ロード画面を表示
end note
"Web Server" --> Client: HTML + JavaScriptを返す
== 1往復目のHTTP通信完了 ==
note over Client
ユーザ毎のデータ以外の枠組みを表示
end note
Client -> "Web API Server": JavaScriptがWeb APIにアクセス
note over Client
ユーザ毎のデータのロード中...
end note
' database "データベース" as Database
"Web API Server" -> Database: Web API サーバーがデータを要求
Database --> "Web API Server": データベースがデータを返す
"Web API Server" --> Client: JSON等の形式でデータを返す
== 2往復目のHTTP通信完了 ==
note over Client
**SPA+ユーザ毎のデータが表示される**
end note
@enduml
@startuml
title Next.js指向のデータのやりとり
Client -> Next.js : WebアプリのURLにアクセス
Next.js -> Database : Next.jsがデータを要求
note over Next.js
ユーザ毎のデータのロード中...
end note
Next.js <-- Database: データベースからデータを返す
Client <-- Next.js: ユーザ毎のデータに基づいた\nHTML+JavaScriptを返す
== 1往復目のHTTP通信完了 ==
note over Client
**画面にデータが表示される!**
end note
@enduml
Headerのログアウトボタン及びユーザ名表示がdynamicである必要があったため 下の問題が起きた様なきがする。
クライアントコンポーネントからもログイン、ログアウトできるので これを試してみる。
ユーザ情報をヘッダコンポーネントに表示するようにしていたが、 これはdynamic server rendering (ビルド結果にfがつくやつ)になっている
layout.tsxがdynamicになってしまっているせいか、 その下のすべてのページがdynamicになっている...?
これを解決したい
middlewareからPoolConnectionを呼び出そうとすると PoolConnection is not a constructorというエラーが出る
await mysql.createConnectionすると大丈夫なので、 jestのテストをどうするか(このためにもPoolConnectionにしていた)は 再考することにしたい
事前の設定やコーディングがある程度必要だが、1時間もあれば導入できた。 使い心地はストレスフリーでとてもよい
データベースが毎回まっさらからスタートだとテストしづらい気が... どこかのタイミングでパスワードなどハッシュ化したうえで記録したいが...
- MySQLマイグレーション時に、テスト中か否かのフラグを読み取って
場合に応じて書き込み処理を行う。
- パスワードのハッシュ化などを適切に関数化しておけば、 これも適切に行えそう
- MySQLマイグレーション後にテストデータを追加する。
これはsqlスクリプトとしてinsert文を手書きして行う。
- ハッシュ化した値は何らかの方法でコピペしないといけないかも
- Next.jsのServer Action等として、テスト用データの追加コードを動かす
- 環境変数やテスト用ボタンを押すことで、テスト用データが書き込まれる。 その場合2重に書き込んだ場合や、テストの手間を考える必要がある
技術的に少し難易度が高くなりそうだが、最初の案を実装してみる
これまでの開発環境では、MySQL Docker Imageを使っていた
マルチステージビルドとしてDB設定関連のファイルを先に生成して、 実行用のイメージに移すことで起動時間を短縮するものも使った
現在は...
- 素のMySQLコンテナを生成
- 開発環境用のNext.jsコンテナを生成
- 開発環境用のNext.jsコンテナからDrizzleを実行し、MySQLコンテナにSchemaに沿ったDBを生成
という順番で行っているが、いくつか問題がありそう
- テスト用のデータを追加するために/docker-entrypoint-initdb.dに ファイルを追加する方法が上手くいかない
- drizzle-kit push を使う場合にはすでにDBにデータがある場合失敗する
...なので、感覚としては
- マイグレーションのためだけのコンテナを用意し、 MySQLデータベースにマイグレーションとテストデータ投入を行う
- 開発環境起動時にはそこで得られたデータベース内容を元に起動する
...といったことをしたい npmのmonorepo機能、workspaceが使える?試してみるか... → OK. monorepo機能を用いたマイグレーション機構を追加、mainブランチに取り込み
- タスクをプロジェクト毎に管理できるようにする
- (他のユーザはいないと思うが)ユーザごとに確実にデータを分ける
hide circle
skinparam linetype ortho
entity Users {
* id: varchar(128) <<unique username>>
* passWithSalt: binary(256)
--
}
entity Projects {
* id: varchar(256) <<Project name, unique per user>>
* userId: varchar(128)
}
entity Tasks {
* id: bigint autoincrement
* userId:
}
Users ||--o{ Projects
Users ||--o{ Tasks
Tasks }o--o| Projects
- Userから見ると...
- 関連するTasksは0個かもしれないし、多数かもしれない
- 関連するProjectsは0個かもしれないし、多数かもしれない
- Tasksから見ると...
- 関連するUsersはただ1つのみ
- 関連するProjectsは0個かただ1つのみ
- Projectsから見ると...
- 関連するUsersはただ1つのみ
- 関連するTasksは0個かもしれないし、多数かもしれない
...つまり、複数のユーザがタスクやプロジェクトを共有しようとすると、 この設計ではうまくいかないことになる...(が、最初はこれでいいか...)