フラミナル

考え方や調べたことを書き殴ります。IT技術系記事多め

【prel】1からWebアプリ(OSS)を作ってみた振り返り

作ったもの

github.com

Google Cloud の IAM role を期間限定でユーザに付与するためのワークフローアプリ。

技術スタック

  • Backend / Front: Go (template)
  • プロトコル:HTTP + OpenAPI v3(ogen)
  • DB: Posgresql

他にも新しく使ったものをすべてかく

tagpr / Playwright / HoberFly / sqlc / postgres / cockroachdb/errors / cobra & viper / oauth2 / iap(header) / CloudSQL / CloudSQL Connector / pgx / dockertest / chatgpt による画像生成

学び

Go template ですべてのページ生成をまかなう

最小限にしたかったのでレンダリングも Go でやった。

最初は良かったがだんだん動的なページをつくりたくなって MPA から CSR をやるページがちらほらでてきた。 Go の template で書くと copilot もうまく効かないし、フォーマッタや Linter も動かなくて相当やりづらかった。(ほぼ ChatGPT に作ってもらった)

やりたいこと完全に別だわということを思い、「だからフロントエンドって独立させたのか」 とここで気づいた。

次やるならフロントエンド作る・・・?とおもったけど個人開発で分けるのはめんどくさいなあ・・・。逆にフロントエンドにバックエンドを寄せる方が可能性があるのだろうか?

1から openapi schema を書くということ

1から書いた経験がなかったのでよかった。

すべてのエンドポイント(HTMLページ / API )について記載をしていたのだが、途中で API エンドポイントだけでいいんじゃないかと気づく。そして気づいた時にはすでに遅し。

静的ページも返すようにしてしまったので /request が静的ページ、/api/requests がAPIというややこしい感じになってしまったし、静的ページのレスポンスとして 404 ページの text/html を返したりしてしまったので application/json だけを返す思想からはずれてしまった。

次は openapi では定義しないようにします・・・。

ogen が便利

blog.framinal.life

コードの書き方

普段の癖で DBスキーマから書いて行ったのだがその後フロント側の修正に合わせてどんどん変わっていった。

実際の使われ方次第でドメイン設計が変わることに気づいたので、次からは 先に使われ方を設計してから画面を考えてやり取りするデータを決め、じゃあそれをどう管理する かという流れにしたい。

インフラ(主にDB)の再設計

Cloud Run + SQlite で行こうと考えて途中まで実装していたのだが、Cloud Run から参照する SQlite 置き場は以下の選択肢があるがいろんな理由で全部無理となった。

  • sqlite on GCSFUSE: バックエンドとしての利用が公式として非推奨、複数の書き込みがあると最後のが勝つ
  • sqlite with Litestream: データが欠損の恐れあり。ユースケース的に微妙では
  • sqlite on Filestore: 最低1TBからでとても高い

全部無理となった後に Postgresql (Cloud SQL) に置き換えたので1日かかってしまった。 とはいえクリーンアーキテクチャと sqlc でだいぶ抽象化していたのでめちゃくちゃ大変ではなかった。

次はもっとDB選定を気をつけよう。

sqlc

クエリからコードを生成できるツール。便利便利と聞いていたが思ったより便利。たまにDBごとの独自の書き方をしないと動かないケースもあるが、大体そういうのは issue にあがってたりするので見れば解決する。

途中から E2E テストのために TypeScript を導入しそこから DB を参照したかったので sqlc with TypeScript を使ったが、これも簡単に使えてとても良かった。

セッションに JWT を使わない

セッション維持のために最初は JWT を使っていた。

  • DB管理しなくていい
  • expired を JWT だけで実装できる

しかし、jwt key がわかると簡単にセッションが生成できセキュリティリスクがあることに気づき実装を丸ごと入れ替えた。"セッション jwt" で調べると警鐘を鳴らす記事がたくさん出てくるので見ると良い。

Google OAuth2 に初挑戦 / IAP

やったことないからくる、OAuth2 に苦手意識があったが今回初挑戦。Client ID / Client Secret を発行しあとはコールバックエンドポイントを用意すれば、idtoken が発行されてはいおしまいだけの簡単なものだった。

これをきっかけに idtoken のことを少しわかったり、Identity-Aware Proxy のこともひきづられて理解度が上昇したのでとてもよかった。

E2E テストの大変さと安心感

バックエンドのみなら API を複数組み合わせたテストで安心できるのだが、ブラウザ操作については全く安心できないということで初めて Playwright 入れてみた。

最初はどう書くかよくわからなかったけど相当楽(まだしっかりかけないけど)

テストもグラフィカルで見ていて楽しい。また変更してもテストがあるのでとても安心感があってよい。 ただし Flaky なテストも生まれやすいので取り扱いには注意。

hoverfly による外部通信のエミュレート

Google API の呼び出しができないのでこれでシュミレートした。

使い方は単純で外部通信を読み取った後 json に dump してそれを使い回すだけ。Google からのレスポンスは gzip + base64 encode されていてめんどくさかったけどまあなんとかなった。いいツールなのに日本語情報少ないのはなんでだろ?

ただリクエストに差がないけど明示的に違うレスポンスを返したいケースはどうにもできなかったので、それだけが心残り。

dockertest

DBテストの際につかった。テストのたびにコンテナを生成して消すというもの。

ただちょっと変えて、テスト時にコンテナ起動→以後のテストはDBをつくるだけ→全てのテストが終わったらコンテナ削除 という感じにした。こっちの方がコンテナ起動停止で遅くなることもない。

tagpr

タグのアップデートとか考えなくていいのがとても楽。

cockroachdb/errors

エラーページ出しわけのためにカスタムエラーを使いたかったのとスタックトレースだしたかったので採用。 本来の使い方としてあってるかは甚だ怪しいが WithDetail でカスタムエラーをくっつけて、あとで Detail の有無をチェックしてHTTPステータスコードにマッピングさせるという処理をした。

手軽にできて使用感もよく便利だったのでこれでいいんじゃないかと思う。

slog

標準のロギングライブラリ。とても便利なのでこれでいい。 ただし Google Cloud Logging 対応をするために独自のロジックが必要になる点に注意。

【Go】 Cloud Logging with log/slog package

アプリ作成時に自分の経験が生きてるなとおもった

  • セキュリティの話
  • 監査、ガバナンスの話
  • 運用の話(ログ、エラー、デプロイの容易性、費用)
  • CI / CD
  • テスト
  • アーキテクチャ

などなど、今の自分が知っている知識や経験を総動員して作れた。また知識の棚卸しにもなったし、あのアプリそういえばこうしてたなとか今までそんなこと気にしなかったなということにも気付けたりした。少し視野が広がった。

他の人に見てもらってわかったこと

  • こういうケースがあるからこの機能があった方がいい
  • こっちの方が使いやすい

などアドバイスをもらった。その通りだと思ったし、他の人に見せないとわからないことあるね。

プロダクトオーナーと言われたこと

自分が1から考えて全部自分で作った(AIの力もあるけど)ので、どの機能を入れるのか入れないのかなどの方針決めは全部自分がやるということを改めて認識できた。

そりゃあ方針がかちあったら分裂するよなあ。これまでは外部からみてたのでなんて面倒なことをと思ってたけど、主義主張が違うならしょうがないよね・・・。

振り返り記事を書いてみて

項目自体は元々ならべていたんだけど、改めて文字に起こすといろんなことを考えたり悩んだりしたんだなと思った。

またプロダクトを作る経験がとても上質なもので感慨深い。なかなか仕事だとこうならないのはなんでだろうか・・・?