NestJSで個人開発頑張るぞログ
これはなに
NestJSのアプリケーションを作ります。資料として残すとちょっと色々やる気が出るので、残す。
ざっくりとした企画と要件定義
企画
文化を作るコミュニティ型小説投稿サイト
早い話がTwitterに小説探すタブがついてて、小説にコメントするとタイムラインに流れるようなもの
要件定義
機能要件
- 概要
- 小説投稿機能、コミュニティ機能、エディタ機能、分析機能
- 小説投稿
- 概要
- 投稿できる、読める
- 機能
- 投稿
- 作品、章、エピソード
- 分類
- あらすじやPRなど
- 概要
- コミュニティ機能
- コメント、推薦文
- フォロー、お気に入り
- スパチャ
- なんかマイナーな作品が強い推しで有名になって欲しい
- 引用機能
- エディタ機能
- サクサクかけるやつ
- 執筆の履歴
- 草生やす
- エクスポート
- 分析
- 感情曲線
- 既読
- 滞在時間
- 読んでいる人の分析
- まとめ
- 一旦投稿とコミュニティだけで進める
非機能要件
- 99.5%か〜まあいいかな
- ユーザー規模
- 良い作品が100欲しい
- となると全作品は1万作品
- 書き手は5000人とする
- アクティブな書き手はうーんまあパレートの法則で1000人とする
- ユーザー自体は5万人
- アクティブなユーザーは5000人くらいか? これは読み手の話
- データ容量
- Write
- ピーク時のコメント
- 5分で読み終えてコメント1分で書くとして、1時間に10件書き込める
- 読者が5000人いるとして
- ピーク時(一日4時間)1時間で5万コメント…
- までもたぶん全員は書き込まないのと、章の区切りでとかもあるだろうしな
- けど引用を使うなら毎回コメントは全然あるな
- 非ピーク時のコメント
- 1時間に5000としよう
- その他の書き込み
- 種類
- 作品投稿
- レビュー
- プロフィール更新
- Twitter的な使い方
- 種類
- 合計
- それぞれ
- コメント: 300k
- 作品: 30
- レビュー: 30
- プロフィール更新: 500人
- 単なるポスト: 240k(1000人が毎時10ポストする)
- 合計
- 540k
- 毎日54万行…!?
- 年間で2億件
- ヒエ…
- 全部持つのはきついし、そのなかからフォローしてる人のコメントだけ抜き出すのはかなりきついはず、、
- キャッシュに入れとくのがいいか
- それぞれ
- ピーク時のコメント
- Write
- アクセス
- Read
- 前提
- ユーザーは5万人でアクティブユーザーは5000人
- 1万作品、よく読まれるのは100作品
- ピーク時が一日に4時間ある
- 機能としては、
- タイムライン形成
- 作品一覧
- 作品のエピソード一覧
- エピソードのコメント一覧
- タイムライン形成
- ここはPush型でキャッシュに持たせる
- 5000人のキャッシュを管理する、までもこれはうーん、その、非同期でキューつんでいけばよくて、なら台数ふやせば追いつけるはず…?
- 作品一覧
- ランキング、トレンド作品、人気タグ?
- ランキングとかの共通のやつは毎時集計してとかでもよい、ユーザーが表示するたびにクエリ叩いて表示させるのはきつそう
- おもしれえなあ!! Readならクエリ叩きますじゃないんだ。Redisとかに用意しといて、それを見せるだけ
- エピソード一覧
- これはキャッシュじゃなくても良い?
- 5000人がアクセスしてもいいのかな?
- エピソードのコメント一覧
- まあいいかな?
- 前提
- Read
システム設計
初期
- 初期はもうなんかアプリケーションサーバーとDBサーバー置いてあればそれでええやろ。PaaS使って進める。
環境立ち上げ
ここは思い出しながらなのでちょっと適当
docker-compose.yml
version: '3.7'
services:
app:
build: .
tty: true
ports:
- 3005:3005
- 5555:5555
volumes:
- .:/app
- /app/node_modules
depends_on:
- db
db:
image: mysql:8.0
volumes:
- db-store:/var/lib/mysql
- ./logs:/var/log/mysql
- ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
- ./docker/init:/docker-entrypoint-initdb.d
environment:
- MYSQL_DATABASE=${DB_NAME}
- MYSQL_USER=${DB_USER}
- MYSQL_PASSWORD=${DB_PASS}
- MYSQL_ROOT_PASSWORD=${DB_PASS}
- TZ=${TZ}
ports:
- ${DB_PORT}:3306
volumes:
db-store:
立ち上げ方
docker compose up --build
- そんで確かコンテナ内でnodeとnestjsをインストールする
- ここはdockerfileに書いておくのが一般的なのかな? 知らん
MySQLの接続確認
- 事前準備
- .envを用意してdocker-compose.ymlで使っている環境変数を定義しておく
- その変数で接続を試す
prisma
prismaインストール
- ローカルで実行
-
docker compose run app npm install --save-dev prisma
- これでインストール
-
docker compose run app npx prisma init
- これでprismaのフォルダができる。中にスキーマ定義をするファイル
schema.prisma
ができている- スキーマ定義のファイルは基本的には一つだけっぽい。巨大なシステムを作るときはprisma使えるんだろうか。まあサードパーティ製のなんか分割するやつはあるらしいが。
- これでprismaのフォルダができる。中にスキーマ定義をするファイル
-
schema.prisma
の記述
- 書き方全く知らんので、調べながら書いていく
- schema.prismaの構成
- データソース
- 接続先。DATABASE_URLってのが環境変数から引っ張ってこられていて、そんなの心当たりない
- と思っていたら自動的に書き込まれていた。
- MySQLの接続先は
- ジェネレータ
- クライアントを指定とあるけど、いまいちピンときてない。クライアントはNestJSアプリじゃないの? わからん
- データモデル
- はい
- これがスキーマ定義にあたるところだね
- データソース
- prisma CLIのコマンド(一部)
-
prisma generate
- これがデータソースとクライアントの生成
- ここ意味がわかってない。生成すんの?
- これがデータソースとクライアントの生成
prisma migrate dev
-
接続エラー
- 有益。既存のデータソースからスキーマ定義を引っ張ってこれるらしい。やったぜ。
- が、やってみてもこのエラー
Error: P1001 Can't reach database server at `127.0.0.1`:`3306` Please make sure your database server is running at `127.0.0.1`:`3306`.
- 以下のようなdiscussionsがあるが、接続情報が間違ってるよ、としか言われてない
- この記事は良さそう
- この記事で解決
- https://for.kobayashiii.dev/articles/kq9p5yzgv7d4
- Dockerって誰の目線でコマンドを打ってるのかを意識しないと動かないな
prismaでスキーマ定義したものを使う
考える
えー、スキーマ定義があって、かつprisma clientでデータの出し入れができるはずなので、これをサービスクラスから使ってやればいいんだよな、しかし、TypeORMでやっていた頃の名残としてdtoがあったりするし、modelクラスがあったりもする、あとentitieか。とりあえずこいつらがどこで使われているのかを整理して、削除しても良いのかそれとも削除せずこの意図を活かした形で組み込むのがいいのかを考えよう。
こういうの仕事やってた頃は毎日やってたなあ。やっぱ休職してるとちょっと頭の使い方を忘れるな。
構成を考える
- というわけで改めて整理すると、
- TypeORMで使ってたファイル群を洗い出す
- どこで参照しているのか調べる
- どういう意図で使っていたのか考える
- prismaに置き換えるならどこで切り分けるのか考える
- ではやろう
整理
- 関係ありそうなファイルは、
- database.module.ts
- 参照元:
- app.module.ts
- 用途:
- なんだろ? まあ接続に使うよってことかな?
- なのでprismaの同じ立ち位置のものと入れ替える必要あり? schema.prismaとかと。
- 参照元:
- entities
- work.entity.ts
- 参照元:
- database.module.ts
- 用途:
- これをTypeORMの接続情報とか載ってるところから参照してて、おそらくこのentitiesを使ってマイグレーションファイルとかを吐かせている
- こいつは単純に消してよさそう。同じ内容はschema.prismaに記載ある
- 参照元:
- work.entity.ts
- works
- work-status.enum.ts
- 参照元:
- entities/work.entity.ts
- works/work.model.ts
- works/works.service.ts
- 用途:
- こいつ自体はリストというかそういうのを定義しているだけっぽい。使いたい奴らが使ってる
- 残して問題なさそう
- 参照元:
- work.model.ts
- 参照元:
- works/works.controller.ts
- works/works.service.ts
- 用途:
- 意外と使ってないな。こいつ自体はコントローラとかサービスで生成したオブジェクトを入れておく箱みたいな立ち位置な気がする
- なのでたぶん、残しておいて良い...ただし、定義が変わってるので書き直す必要がある
- 参照元:
- dto
- create-work.dto.ts
- 参照元:
- works.controller.ts
- works.service.ts
- 用途:
- workを生成する時に受け付ける変数をこう定義してる
- おそらくこれはこのまま必要? ただテーブル構成変わってるので修理は必要
- 参照元:
- create-work.dto.ts
- migrations
- 34243223-CreateWorkTable.ts
- いらんいらん!!
- 34243223-CreateWorkTable.ts
- work-status.enum.ts
- database.module.ts
やること
- 現時点でアプリが動くことを確認
- まだDB繋いでない状態で使えることの確認
- work.model.ts、create-work.dto.tsを書き直す
- DB繋がず、modelの定義だけ書き換えて動くことの確認
- schema.prismaとapp.module.tsの関係を整理する
- DBと繋いでみてエラーにならないか確認
- prisma client使ってcontrollerから永続化を試す
動かんな
なにも触ってない状態でアプリ起動してみたが、動いてない
やったこと
yarn start
appのコンテナに入り、上記を実行。package.jsonのscriptにこれを読み替えてnest startにせよと書いてある。が、このコンテナはnestコマンドが使えないんだとか。じゃあ動かんね
NA
nestコマンドどうしてたのか調べる。過去の記事とか読んで
一応解決
nestjs/cliとnestjs/commonがインストールされてなかった。そんなわけないと思うし実際package.jsonには記載あったんだけど。とりあえず二つともインストールして解決。
modelの定義を書き換える
model
で、スキーマ定義通りにmodelを書き換える。
ただ、TSでどうやって定義していくのかわからん。というか型がわからんな。プリミティブ型がわからん。
intってnumberなのか。てか、プリミティブで定義するのがいいのかな。
enumとかで定義してるやつとかあるからな、なんか自前で定義したいのがあるならここで定義するってことかな。つまり、うーんドメインを定義するならこのmodelでやるのか? てかさあ前々から思ってたけどフレームワークがある程度こうあの〜〜フォルダの分け方とかどこになにを書くとか決めてるやつってDDDやる隙あんの?? 別に決めてないの?? やりてえならやれってことか? ヒイヒイ
DDDなあ、DDDなあ、やった方がいいんだろうなあ、けどまあ〜〜〜〜〜〜〜〜〜〜はあ〜〜〜まあ別にいま興味ないんだよなあ、まあええわ
ほじゃまあとりあえず、えー、プリミティブ型で一旦進めよう。まだドメインがどうなってんのかわからんし。
dto
dtoでバリデーションかけてる。ここでzodとかいうのが必要になるっぽいな。。。
import { z } from 'zod'
// 文字列のスキーマの作成
const stringScheme = z.string()
stringScheme.parse("hoge")
基本はこの形でバリデーションをかける。ただ、元々class-validatorってライブラリ? でやってたバリデーションは@デコレータでバリデーションの内容を定義してあげて、そんでコントローラの引数かなんかで渡すことでバリデーションしてた。それをここで同じようにやろうとするときどうするのか。てかコントローラでの渡し方意味わかってないわ。
思ったよりややこしいな
NestJS Prisma Zod OpenAPI で型を堅牢にする
こちらは結構よさそう
中断
ここらへんの組み込み方をこう、考えたいというモチベーションは今ないので、一旦NestJSでのアプリケーション開発はやめよう。業務でここらへんはすでに枠組決まってると思うから。
今はprismaの触り方を勉強して終わりにしよう。
prismaに取り組む
やること
- スキーマ定義の仕方を洗っとく
- clientの触り方を洗っとく