最先端に疲れたTSerのための時代に逆行するRESTっぽいテンプレートCATAPULT
背景
React/TypeScriptの進歩が速すぎて新しい機能・設計・概念・ライブラリに追従するのが辛くなってきたベテランは私以外にもいるのではないでしょうか。
技術だけを極めれば良かった若いころは勉強という意識もなく楽しめたのですがおじさんにもなると技術以外にも必要な知識や責任が増えて”最先端のスキルセット”を売りにすることが出来なくなってしまいました。
若手のコードレビューをしていると新機能を使いこなすことよりも、余計なuseEffectを使わないようにするとかページ内に閉じた状態をcontextやJotaiに突っ込まない、あたりの基礎だけ理解してくれれば十分なんだけどなあと思ったりしています。
そんな疲れたおじさんが自治体向けの業務システムを開発する過程で妥協点を探りながら改良してきたFullStackTSテンプレートがCATAPULTです。
aspidaを気に入ってくれている人と、REST APIが好きな人に刺さることを願っています。frourioを知っている人向けに説明すると、next-frourio-starterの後継となるテンプレートです。
設計
- フロント(Next.js)とバックエンド(frourio)の型は古き良き(?)シンボリックリンクで共有
- モノレポだけどWorkspace使わず基本的なnpm installコマンドのみで管理
- RESTっぽいけどRESTのルールを厳守せず欲しい時に欲しいエンドポイントを増やす
- OpenAPIはaspida定義から自動生成
- aspidaのディレクトリ以外は構成自由
- クラスはライブラリに強制される場合以外に使わない
- 初期構成はDDDっぽいけど記述量を大幅に減らしてもはや戦術的DDDですらない手抜き
- フロントのテストは書いてもいいけどPJが利益を出すまでは書かないことを推奨
- バックエンドのテストはシナリオ考えるのを止めてカバレッジを100%維持するだけ
- 受入テストや本番でバグが見つかったときにそのケースをテストに追加
- 形骸化したレビューでも可読性と品質がそこそこ担保されるようにLintと型検査は厳しめ
- ヒットするまではコンテナ一つにフロントとバックエンドを詰め込んでビルド工程を簡略化
- Node.jsとDockerがあればオフラインで開発できる
主要スタック
- Next.js + aspida
- frourio + fastify
- AWS Cognito
- AWS S3
- PostgreSQL
- Prisma
- ESLint + Prettier
セットアップ
本番デプロイはまあまあ辛いけど、ローカル開発はGitHubからCloneしてnpm installとDocker起動だけなので苦労しないはず。コミットを引き継がないようにgitの初期化もするとよい。
$ git clone https://github.com/frouriojs/catapult.git
$ cd catapult
$ rm -rf .git # 既存のコミット履歴を削除
$ git init
Lernaとかが流行り始めた時代からモノレポ管理のツールを色々使っていたけど半年くらいでセットアップ方法自体を忘れる歴史を繰り返してきたのでnpm installだけに回帰しました。
package-lock.jsonがあるディレクトリで普通のnpm installをしてください。prefixオプションを使うとディレクトリ移動しなくていいのでちょっとラク。
$ npm i
$ npm i --prefix client
$ npm i --prefix server
ローカル開発なら環境変数はexampleそのままcopyして使えます。
$ cp client/.env.example client/.env
$ cp server/.env.example server/.env
Docker起動。S3/Cognito/SMTPサーバーあたりのモックが含まれている。
$ docker compose up -d
開発サーバー起動。ターミナルが散らかるので整理のためにnotios使ってます。
$ npm run notios
notiosの使い方はここ
http://localhost:3000 を開くとこんな感じの画面が表示されるはず。
開発フロー
バックエンド
DBから取り出す際は毎回権限チェックしてDTOに詰める。DTOはそのままフロントに返しちゃってもいい前提にすることで漏洩対策になるしGETのcontrollerを書くのがずいぶん楽になる。
内部データはDTOとともに別途Methodに渡してEntityに変換、Commandで保存してやはりDTOが返ってくる。
DTOは読み取り専用、EntityはDBに書き込みするためのDAOみたいな位置づけにしてしまう。
DTO型定義
⇒Entity型定義
⇒Method実装
⇒Prisma schema定義
⇒Prisma Client生成
⇒Command実装
⇒useCase実装
⇒Query実装
⇒DTO実装
⇒型検査
⇒REST API型定義
⇒Controller実装
⇒Prisma migrate作成
prismaのマイグレーションとserver/prisma/seed.tsはサーバー起動のたびに実行されるので特にseed.tsは適用済みかどうかをチェックする処理を先頭に書いておく。
バックエンドテスト
Lintチェック
⇒型検査
⇒APIエンドポイント全て通るテスト書く
⇒カバレッジ100%までテスト追加
型定義や自動生成を前提にしているのでテスト駆動開発はしない。
辛かったらカバレッジの閾値を下げてもいいけど、100%のままifやforを減らす努力をした方が予後が良好になる。
フロント
カスタムフックやコンポーネント分割のルールが曖昧でも1ファイル200行や循環的複雑度5以下などのESLintルールを守ってくれればバグ修正対応時に他人のコードを読めなくて詰まることがだいぶ減る。
デプロイ
テンプレートのDockerfileそのままでRenderとRailwayにデプロイし続けています。
S3とかCognitoは別途用意してください。
まとめ
丁寧な技術記事を書く体力も残っていません。細部はTwitterかDiscordで質問してくれると答えやすいです。GitHubのIssueは後回しになりやすい・・・
私と似たような境遇のエンジニア達がこれで大金を稼いでくれることを願っています。私自身もCATAPULTの派生物で稼いでいるので特に人月商売の世界に迷い込んだゴリラが5~10万行規模の業務システムを一人で作り切る場合に凄まじい生産性を発揮してくれることでしょう。
Discussion