0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【予約システム】#2 — 73コミットのうち20件が「基盤との格闘」だった

0
Posted at

「作り直したい」は判断材料にならない

個人で作った小さな予約システムを、数年動かしている。フロントはSPA、バックエンドはサーバーレス関数、データストアはドキュメントDB。いわゆるBaaSに全部載せた構成だ。

去年あたりから、作り直したいとずっと思っていた。ただ、その動機を正直に分解すると「新しいスタックを触りたい」がかなりの割合を占めている。動いているものを壊すには、これでは弱すぎる。

なので、判断できるものだけを計測した。やったのは3つ。

コミットログのうち、何割が「基盤との格闘」か

いちばん効いたのがこれだった。コミット履歴を頭から読んで、機能開発ではないコミットを数える。

全73コミットのうち、20件以上がこの種類だった。件名を並べるとはっきりする。

fix: 関数をCIからデプロイできるようにする
fix: 関数のエントリポイントをビルド出力に合わせる
fix: 関数を外部から呼び出し可能にする
fix: 一覧用の複合インデックスを追加
fix: 関数側にも型定義ライブラリを追加(ビルドエラー修正)
fix: tsconfig にパスマッピングを追加(3ファイル)
fix: バンドラの設定にもエイリアスを追加
デプロイ時に環境変数をファイル経由で関数に渡す
ステージングのシークレット検査を fail-fast から警告に緩和
どのシークレットが欠けているかを表示する診断を追加

約27%。4回に1回のコミットが、利用者に何も届けていない。バグ修正ですらなく、プラットフォームとの帳尻合わせだ。

この比率、相性を測る定量指標としてけっこう使えると思う。10%なら許容範囲だろう。30%近いなら、自分が遅いんじゃなくて構成に摩擦がある。自分のリポジトリで一度数えてみると、思っているより多いはずだ。

業務上の不変条件を、DBが保証できているか

ふたつめは、もっと本質的な話だった。

この予約システムには「定員を超えて予約を受け付けない」という不変条件がある。ドキュメントDBではこれをDB側で保証できないので、アプリケーションコードで守るしかない。

実際に並列で予約を投げるテストを書いたら、定員超過が通った。これはバグだが、直したところで同じ種類のバグがまた生える。守るべき条件がアプリ側にしかないので。

リレーショナルDBなら、これは制約1行で終わる話だ。

-- 定員は DB が保証する。アプリが何をしようと破れない
ALTER TABLE reservations
  ADD CONSTRAINT capacity_within_limit CHECK (headcount <= capacity);

-- 二重送信も同様。アプリのリトライに耐える
ALTER TABLE reservations
  ADD CONSTRAINT uniq_idempotency UNIQUE (facility_id, idempotency_key);

「アプリで気をつける」はレビューでは通るが、本番では破れる。不変条件をDBに置けるかどうかは、データストアを選ぶときの第一の基準にしていいと思う。

素朴な操作に、いくら払っているか

みっつめは読み取り量。「自分の予約一覧を見る」という、いちばん素朴な操作に何が起きているかを数えた。

クエリ能力が足りないぶんを「多めに取ってアプリ側で絞る」で埋めていたので、1回の照会で1,000件前後、条件によっては1,500件のドキュメント読み取りが発生していた。

規模が小さいうちは、これでもちゃんと動く。動いてしまうから気づかない。数えて初めて、この構成が払っているコストが見えた。

作り直さない理由も、同じ熱量で書く

ここが大事なところだった。設計書には、現行システムを擁護する節をわざわざ作っている。

現行の実装は、設計として素直だ。ドメインロジックはちゃんと分離されているし、型定義はフロントとバックで共有されているし、CIで型チェックとテストが回って、監査ログまである。小規模プロジェクトとしては十分に良い作りだ。

つまり悪いのは実装じゃない。「良い構造が、ランタイムの制約で本来の価値を出せていない」という状態だ。この区別をしておかないと、リアーキが過去の自分の否定になって、判断が感情に寄ってしまう。

コストも比べた。結果はほぼ同等で、移行の投資はランニングコストでは回収できない。回収するとしたら開発時間のほうだ。これも正直に書いた。

そのうえでの結論はこうなる。いま摩擦を感じていないなら、やらなくていい。自分の場合は27%という数字が出たのでやる、というだけの話だ。

置き換えじゃなくて、並行させる

実装フェーズで最初にやったのは、既存のコードを1行も消さないことだった。

新しいスタックはモノレポの別パッケージとして追加して、既存の実装はそのまま残す。フェーズごとに切り戻し手段を用意して、両系統を並行稼働させてデータを突き合わせる前提で計画した。

apps/web/      新しいアプリケーション
packages/core/ DB・フレームワークに依存しない純関数(時刻計算・空き判定・定員判定)
packages/db/   スキーマ・マイグレーション・行レベルセキュリティ・クエリ
functions/     既存の実装(そのまま残す)
frontend/      既存のSPA(そのまま残す)

ビジネスロジックを core に純関数として抜き出したのは、移行のためだけじゃない。DBにもフレームワークにも依存しない層があると、次に基盤を替えるときのコストが下がる。今回の教訓をそのまま構造にしたものだ。

作り直しの最大のリスクは、技術的な失敗じゃなくて、途中で力尽きて両方が中途半端になることだった。だから各段階に「ここで止めても壊れない」状態を用意している。

感覚で決めるのをやめる

「動いているものには触るな」は正しい教えだが、触らない理由を毎回感覚で決めていると判断の質が上がらない。コミットログを数えて、不変条件をDBに置けるか確かめて、素朴な操作のコストを測る。それだけで、やる・やらないのどちらに転んでも説明できるようになった。

作り直さない理由を同じ熱量で書けないうちは、動機がまだ感情の側にある。今回いちばん役に立った自己チェックはこれかもしれない。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?