マイグレーションの設計
—— 「スケールしたら対応する」が実行されない理由

場当たり的な対処と、移行経路の設計は、見た目が似ている。
どちらも「今はやらない」という判断をする。
だが一方は忘れ去られ、もう一方はその時が来たら半日で終わる。

この記事で言いたいこと:スケーリングの本質は、巨大なシステムを最初から作ることではない。フェーズが変わったときに、最小の変更で次の段階に移れる「継ぎ目」を設計しておくことだ。

1. 「スケールしたら対応する」の嘘

ソフトウェア開発でもっともよく聞く先送りの言葉がある。「スケールしたら対応する」。

この言葉が発せられた瞬間、ほぼ確実に、その対応は行われない。

理由は単純だ。スケールしたとき、開発者はスケールに伴う別の問題に追われている。ユーザー対応、バグ修正、機能要望。「あのとき先送りにした技術的負債」は、優先度リストの下に沈んでいく。そして本当に限界が来たとき、緊急対応として場当たり的なパッチが当てられる。

これが「場当たり的」の正体だ。先送りそのものが悪いのではない。先送りにした課題の「戻り方」を設計していないことが問題なのだ。

2. 場当たり的と移行設計の違い

場当たり的な対処と、移行経路の設計は、表面上は同じに見える。どちらも「今はやらない」という判断をしている。

違いは一点だけだ。

移行設計は、「今はやらないが、やるときに何を変えればよいか」が明確になっている。

具体的には、次の条件が揃っていることを指す。

この三つが揃っていれば、「今はやらない」は怠慢ではなく設計判断になる。

3. 一つの関数に集約する

TokiQRの運用では、Google Apps Script(GAS)からメールを送信している。GASの無料枠はGmail経由で1日100通。個人運営の初期段階では十分だが、注文が増えれば上限に達する。

将来的にはAmazon SES(Simple Email Service)に移行すれば、月62,000通が無料になる。だが今SESを組み込む必要はない。注文はまだ少ないし、AWSアカウントの設定やドメイン認証といったセットアップは、必要になってからで十分だ。

では何をしたか。メール送信のコードを一つの関数に集約した。

function sendEmail(recipient, subject, body, options) {
  MailApp.sendEmail(recipient, subject, body, options || {});
}

コード全体で14箇所あったMailApp.sendEmailの直接呼び出しを、すべてこの関数経由に変えた。SESに移行するときは、この1関数の中身を書き換えるだけだ。

これは過剰設計ではない。関数一つ追加しただけだ。だがこの一つが、将来の移行コストを「14箇所の書き換え+テスト」から「1箇所の書き換え+テスト」に変える。

4. 三段階のロードマップ

メール送信だけでなく、TokiQRの運用全体も同じ原則で設計されている。

フェーズ1:自分で作る(現在)

印刷、UVラミネート加工、梱包、発送。すべて一人で行う。GASとGmailで運用は完結し、インフラコストはゼロ。1日50件程度まではこの体制で回る。

フェーズ2:国内外注

1日50件を超えたら、ラミネート生産を国内の印刷業者に委託する。GASから注文データを業者に連携し、印刷・加工・梱包・発送を一括で任せる。TokiStorage側は在庫ゼロ、作業ゼロの完全デジタル運営になる。メール送信はSESに移行。

フェーズ3:海外展開

海外注文が増えたら、顧客の国に応じて現地の生産拠点に振り分ける。注文データにはすでに国情報が含まれているので、ルーティングロジックの追加だけで対応できる。

各フェーズの移行で必要なコード変更は最小限だ。フェーズ2ではsendEmail関数の書き換えと業者連携の追加。フェーズ3では国別ルーティングの追加。いずれも既存のデータ構造を壊さない。

コードだけでは終わらない

フェーズの移行で変わるのは、コードだけではない。

生産を外注するということは、顧客の氏名と住所を第三者に渡すということだ。プライバシーポリシーに業務委託先への情報提供を明記し、特定商取引法の表記には引渡時期を外注先のリードタイムに合わせて更新する。利用規約のデータ取り扱いセクションも改訂が必要になる。

外注先との間ではNDA(秘密保持契約)を締結し、顧客情報の取り扱い範囲を定める。生産リードタイムや品質基準のSLA(サービスレベル合意)も取り決める。

これらは「スケールしたら考える」ではなく、今の段階で「何を更新する必要があるか」のリストとして手元に持っておく。移行設計の原則は、コードに限った話ではない。法務も、契約も、ポリシーも——変更が必要な箇所と手順を事前に書き出しておくことが、場当たり的にならないための条件だ。

お金の自動化は慎重に段階を踏む

TokiQRにはパートナー還元の仕組みがある。紹介経由の注文に対し、売上の10%をパートナーに支払う。現在は月次で自動集計し、Wiseの支払いリンク付きレポートを管理者に送信している。送金自体は手動だ。

パートナーが増えれば、この手動送金も移行対象になる。だがお金の自動化は、メールやファイルの自動化とは本質的に異なる。バグが即座に実害になる。

だから段階を刻む。まずはバッチCSVの自動生成。管理者がダウンロードし、Wiseにアップロードして一括送金する。金額を目視確認してからの実行なので、誤送金リスクがない。その次に、APIによる送金自動化。ただし金額上限を設け、超過時は承認待ちにする。

移行設計の原則はここでも同じだ。「今は手動でよい。だが次の段階で何をすればよいかは書いてある。」リスクの大きさに応じて段階の刻み方を変える——それも設計だ。

5. 過剰設計との境界

移行設計には、過剰設計に陥る誘惑がつきまとう。「将来のために」とあらゆるケースを想定し、使われない抽象化を積み重ねる。

その境界はどこにあるのか。

今の段階で動くコードに、一行も余計なものを足さない。
ただし、将来の変更が一箇所で済むように、責務を分離しておく。

sendEmail関数は、今この瞬間はMailApp.sendEmailをそのまま呼んでいるだけだ。ラッパーとしてのオーバーヘッドは実質ゼロ。だが将来の変更点を一箇所に局所化している。

これが「移行設計」と「過剰設計」の分水嶺だ。今の動作に一切影響を与えず、将来の移行コストだけを下げる。足すのは構造であって、機能ではない。

6. 継ぎ目のある設計

建築には「エキスパンションジョイント」という概念がある。地震や温度変化で建物が膨張・収縮したとき、構造全体が壊れないように設けられた継ぎ目だ。普段は何の役割も果たさない。だが力がかかったとき、その継ぎ目が建物を守る。

ソフトウェアにおけるマイグレーション設計も同じだ。関数の抽象化、データ構造の分離、設定の外出し。これらは平時には何もしない。だがフェーズが変わったとき、変更の衝撃を吸収する。

マイグレーションとは、巨大なシステムを事前に作ることではない。
小さな継ぎ目を、正しい場所に仕込んでおくことだ。

7. 場当たり的であることの本当のコスト

場当たり的な対処のコストは、技術的負債だけではない。

本当のコストは「判断の劣化」だ。緊急対応を繰り返すうちに、チームは「どうせまた急場しのぎになる」と学習する。設計への信頼が失われ、「ちゃんと作る」という選択肢が心理的に遠のく。

逆に、移行経路が設計されている組織では、「今はやらない」という判断に不安がない。やるときの手順が見えているから、安心して先送りにできる。先送りが罪悪感ではなく、優先順位の管理になる。

TokiQRでは、SES移行の手順書がgas/scaling-roadmap.mdに残されている。Lambda関数のコード、IAMロールの設定、GAS側の書き換え方法。すべてが書かれている。だから「今はやらない」と言える。やるときに何をすればよいか、わかっているからだ。

8. 個人開発におけるマイグレーション

この設計原則は、大規模なチーム開発だけのものではない。むしろ個人開発にこそ必要だ。

チームなら、誰かが技術的負債を覚えていてくれるかもしれない。だが個人開発では、3ヶ月前の自分が何を考えていたかすら忘れている。「スケールしたら対応する」と言った自分は、もういない。

だからこそ、移行経路をコードの近くに残す。手順書を書く。トリガー条件を明記する。未来の自分は他人だ。その他人が迷わず作業できるように、今の自分が道を敷いておく。

場当たり的とは、道がないことではない。
道があったことを、忘れてしまうことだ。