ルート、サービス、DB、UI を段階的に整理し、各変更を小さくテスト可能でロールバックしやすく保つことで、プロトタイプを安全にモジュールへ移行する方法。

プロトタイプはすべてが近くにあるため速く感じられます。ルートがDBに触れ、レスポンスを整形し、UIがそれを描画します。その速さは本物ですが、代償を隠します: 機能が増えると、最初の「簡単な経路」がすべてが依存する経路になってしまいます。
最初に壊れるのはたいてい新しいコードではありません。古い前提です。
ルートに対する小さな変更が静かにレスポンス形状を変え、二つの画面を壊すことがあります。三箇所にコピーされた「一時的」なクエリが微妙に異なるデータを返し、どれが正しいか誰も分からなくなることもあります。
これが、大きな書き換えが善意でも失敗する理由です。構造と挙動を同時に変えると、バグが出たときに原因が新設計なのか基本的なミスなのか判別できません。信頼は下がり、スコープは膨らみ、書き換えは長引きます。
リスクの低いリファクタは、変更を小さく可逆に保つことです。どのステップでも止めて、動作するアプリが残るべきです。実践的なルールはシンプルです:
ルート、サービス、DBアクセス、UIは、各層が他の層の仕事をし始めると絡み合います。ほどくことは「完璧なアーキテクチャ」を追うことではありません。一度に一本の糸を動かすことです。
リファクタは改装ではなく引っ越しのように扱ってください。挙動は同じに保ち、後で構造を変えやすくすること。再編の間に機能を「改善」すると、何が壊れたか分からなくなります。
まだ変えないものを紙に書いてください。一般的な「まだやらない」項目: 新機能、UI再設計、DBスキーマ変更、パフォーマンス作業です。この境界がリスクを低く保ちます。
「ゴールデンパス」になるユーザーフローを一つ選んで守ってください。人が日常的に行うものを選びます。例:
sign in -> create item -> view list -> edit item -> save
このフローを各小さなステップの後に再実行します。同じ振る舞いなら作業を続けてよいでしょう。
最初のコミットの前にロールバック方法に合意してください。ロールバックは退屈であるべきです: git revert、短命の機能フラグ、あるいは復元できるプラットフォームのスナップショット。Koder.ai を使っているなら、スナップショットとロールバックが整理しながらの安全網になります。
各ステージごとに小さな完了定義を持ってください。大きなチェックリストは不要で、ただ「移動+変更」が忍び込むのを防ぐ程度で十分です:
もしプロトタイプがルート、DBクエリ、UI整形をすべて扱う1ファイルなら、いきなり全部分割しないでください。まずはルートハンドラだけをフォルダに移し、ロジックはそのまま(コピーでも可)にします。それが安定したら、後のステージでサービスやDBアクセスを抽出します。
始める前に現状をマップしておきます。再設計ではなく、安全な小さな移動を可能にするための下準備です。
全てのルート/エンドポイントを書き出し、何をするかを一文で書きます。UIルート(ページ)もAPIルート(ハンドラ)も含めます。チャット駆動のジェネレータでコードを出力していれば同様に扱ってください: インベントリはユーザーが見るものとコードが実際に触るものを一致させます。
使いやすい軽量インベントリ例:
各ルートについて短い「データパス」メモを書くと良いです:
UIイベント -> handler -> logic -> DB query -> response -> UI update
進めながらリスクのある箇所にタグを付けて、周辺コードを掃除する際に誤って触らないようにします:
最後に、シンプルなターゲットモジュールマップを描きます。浅めに保ってください。目的地を選ぶのであって新しいシステムを構築するわけではありません:
routes/handlers, services, db (queries/repositories), ui (screens/components)
あるコードがどこに属するか説明できなければ、その部分は自信が付くまで後回しにしてよい候補です。
まずルート(またはコントローラ)を境界として扱い、コードを改善する場所とは考えないでください。目標はエントリごとの振る舞いを同じに保ちながら、エンドポイントを予測可能な場所に置くことです。
users, orders, billing のような機能領域ごとに薄いモジュールを作ります。「移動しながらきれいにする」は避けてください。リネーム、ファイル再編成、ロジック書き換えを同じコミットでやると、原因追跡が難しくなります。
安全なシーケンス:
具体例: POST /orders がJSON解析、フィールドチェック、合計計算、DB書き込み、返却を1ファイルでやっているなら、書き直さないでください。ハンドラを orders/routes に抽出し、古いロジックを createOrderLegacy(req) のように呼び出します。新しいルートモジュールは玄関口になり、レガシーロジックは当面そのままです。
生成コードを扱う場合でも(例えば Koder.ai で出力されたGoバックエンド)、考え方は同じです。各エンドポイントを予測可能な場所に置き、レガシーロジックをラップして共通リクエストが成功することを証明してください。
ルートはビジネスルールの良い居場所ではありません。すぐ成長し、関心事が混ざり、変更が危険に感じられます。
ユーザー向けアクションごとに1つのサービス関数を定義してください。ルートは入力を集め、サービスを呼び、レスポンスを返すだけにします。DB呼び出し、価格ルール、権限チェックはルートの外に置きます。
サービス関数は一つの仕事、明確な入力、明確な出力があると考えやすくなります。 "…もしかして…" を増やしているなら分割してください。
一般的に機能する命名パターン:
CreateOrder(input) -> orderCancelOrder(orderId, actor) -> resultGetOrderSummary(orderId) -> summaryルールはサービス内に置き、UIではなくサービスが守るべきです。例えば「プレミアムユーザーは10件まで作成できる」というUIの無効化に頼るのではなく、そのルールをサービスで強制します。UIは親切なメッセージを表示できますが、ルールは一箇所にあります。
次に進む前に、変更を元に戻しやすくするために最低限のテストを追加してください:
Koder.ai のようなツールで素早く生成や反復をする場合、サービスがアンカーになります。ルートとUIは進化できますが、ルールは安定してテスト可能です。
ルートが安定し、サービスができたら、DBが「どこにでもある」状態を止めてください。生のクエリを小さく退屈なデータアクセス層に隠します。
小さなモジュール(repository/store/queries)を作り、GetUserByEmail、ListInvoicesForAccount、SaveOrder のような明確な名前の関数だけを公開します。ここでエレガンスを追いかけないでください。各SQL文字列やORM呼び出しのための一つの明白な居場所を目指します。
このステージは構造に関することだけに厳密に留めてください。スキーマ変更、インデックス調整、または「ついでに」マイグレーションは避けてください。それらは別途計画してロールバックを用意するべきです。
プロトタイプ臭の出る典型はトランザクションが散らばっていることです: ある関数がトランザクションを開始し、別の関数が黙って自分用を開き、エラーハンドリングがファイルごとに異なる。
代わりに、コールバックをトランザクション内で実行する1つのエントリポイントを作り、リポジトリがトランザクションコンテキストを受け取れるようにします。
小さな移動を心がけてください:
例: “Create Project” がプロジェクトを挿入してデフォルト設定も挿入するなら、両方を1つのトランザクションヘルパーでラップします。途中で何か失敗したら、設定のないプロジェクトが残ることを防げます。
サービスが具体的なDBクライアントではなくインターフェイスに依存するようになると、多くの挙動を実際のDBなしでテストでき、恐怖が減ります。これがこの段階の目的です。
UIの整理は見た目を良くすることではありません。画面を予測可能にして副作用を減らすことです。
UIコードを技術的な種類ではなく機能ごとにグループ化します。機能フォルダはその画面、小さなコンポーネント、ローカルヘルパーを含めることができます。同じマークアップ(同じボタン行、カード、フォームフィールド)を見つけたら抽出しますが、マークアップとスタイルは同じに保ってください。
props は単純に保ちます。コンポーネントが必要とするものだけ(文字列、id、真偽値、コールバック)を渡してください。大きなオブジェクトを「念のため」渡すなら、小さな形に定義し直します。
UIコンポーネントからAPI呼び出しを取り出してください。サービスレイヤがあっても、UIにはフェッチロジック、リトライ、マッピングが残りがちです。機能ごと(またはAPI領域ごと)の小さなクライアントモジュールを作り、画面が使いやすいデータを受け取るようにします。
読み込みやエラー処理を画面間で一貫させます。予測可能な読み込み状態、再試行アクションがある一貫したエラーメッセージ、次のステップを説明する空状態を選んで再利用してください。
抽出ごとに触った画面を素早く目視でチェックします。主要な操作をクリックし、ページをリフレッシュし、一つのエラーケースを起こしてみてください。小さなステップは大きなUI書き換えに勝ります。
小さなプロトタイプを想像してください: サインイン、アイテム一覧、アイテム編集の3画面があります。動くが各ルートが認証チェック、ビジネスルール、SQL、UI状態を混ぜています。目的はこの機能だけをクリーンなモジュールに変え、元に戻せる変更にすることです。
以前は “items” ロジックが散らばっているかもしれません:
server/
main.go
routes.go
handlers.go # sign in + items + random helpers
db.go # raw SQL helpers used everywhere
web/
pages/
SignIn.tsx
Items.tsx # fetch + state + form markup mixed
後では、振る舞いは同じだが境界が明確になります:
server/
routes/
items_routes.go
handlers/
items_handler.go
services/
items_service.go
repositories/
items_repo.go
web/
pages/
Items.tsx
components/
ItemForm.tsx
まずロジックを変えずにコードを移動します。関数名と戻り形を同じに保ち、ロールバックは主にファイル移動で済むようにします。
まず1つのルートをリファクタします。ハンドラはHTTPだけ扱い、ルールやSQLは扱いません:
// handlers/items_handler.go
func (h *ItemsHandler) UpdateItem(w http.ResponseWriter, r *http.Request) {
userID := h.auth.UserIDFromRequest(r) // same auth logic as before
itemID := mux.Vars(r)["id"]
var in UpdateItemInput
json.NewDecoder(r.Body).Decode(&in)
item, err := h.itemsService.UpdateItem(r.Context(), userID, itemID, in)
if err != nil { writeError(w, err); return }
writeJSON(w, item)
}
次にビジネスルールをサービスに移します。ここに「ユーザーは自分のアイテムだけ編集できる」や「名前は空にできない」といったチェックを置きます。サービスはDB作業のためにリポジトリを呼びます:
// services/items_service.go
func (s *ItemsService) UpdateItem(ctx context.Context, userID, itemID string, in UpdateItemInput) (Item, error) {
if strings.TrimSpace(in.Name) == "" { return Item{}, ErrInvalidName }
return s.repo.UpdateItem(ctx, userID, itemID, in)
}
その後、DBアクセスをリポジトリ関数に分離します:
// repositories/items_repo.go
func (r *ItemsRepo) UpdateItem(ctx context.Context, userID, itemID string, in UpdateItemInput) (Item, error) {
row := r.db.QueryRowContext(ctx,
`UPDATE items SET name=$1 WHERE id=$2 AND user_id=$3 RETURNING id, name`,
in.Name, itemID, userID,
)
var it Item
err := row.Scan(&it.ID, &it.Name)
return it, err
}
UI側ではページレイアウトを保ちながら、繰り返し出てくるフォームマークアップを新しい共通コンポーネントに抽出します。
pages/Items.tsx はフェッチとナビゲーションを維持components/ItemForm.tsx は入力フィールド、バリデーションメッセージ、送信ボタンを担当Koder.ai(koder.ai)を使っているなら、深いリファクタ前にソースエクスポートを取ると便利で、移動がうまくいかなかったときにスナップショット/ロールバックで素早く復旧できます。
最大のリスクは「移動」と「変更」を混ぜることです。ファイルを移動しつつロジックを書き換えると、騒がしい差分にバグが隠れます。移動は退屈に: 同じ関数、同じ入力、同じ出力、新しい居場所。
もう一つの罠は振る舞いを変える掃除です。変数名を変えるのは問題ないですが、概念の名前を変えるのは別です。もし status を文字列から数値に変えたら、それはコードの整理ではなくプロダクトの変更です。それは後で明確なテストと意図的なリリースで行ってください。
早い段階で将来のために大きなフォルダツリーや多重レイヤを作る誘惑がありますが、これはしばしば作業を遅くし、実際の作業箇所を見えにくくします。最小限の有用な境界から始め、次の機能が必要に迫った時に拡張してください。
また、UIが直接DBに触れたり(またはヘルパー経由で生のクエリを呼んだりする)ショートカットにも注意してください。速く感じますが、各画面が権限、データルール、エラーハンドリングを担うことになり責任が増えます。
避けるべきリスク増大要因:
null か一般的なメッセージになる)しまうこと小さな例: ある画面が { ok: true, data } を期待しているのに新しいサービスが { data } を返してエラー時は例外を投げるようになると、アプリの半分がフレンドリーなメッセージを表示しなくなります。まず境界で古い形を保持し、呼び出し側を一つずつ移行してください。
次に進む前に主要な体験を壊していないことを証明してください。ゴールデンパス(サインイン、アイテム作成、表示、編集、削除)を各ステップで必ず実行します。整合性が小さな回帰を見つけやすくします。
各ステージ後の簡単なゴー/ノーゴーゲート:
もしどれかが失敗したら、その上に構築する前に止めて修正してください。小さなひびが後で大きくなります。
マージ直後に5分だけかけてバックアウトできるかを確認します:
勝利は最初の掃除ではありません。勝利は機能を追加しても形が保たれることです。完璧なアーキテクチャを追うのではなく、将来の変更が予測可能で小さく、元に戻しやすくすることです。
次のモジュールは「影響」と「リスク」に基づいて選んでください。イライラする箇所ではなく、ユーザーがよく触れる場所や振る舞いが既に理解されている箇所が良い候補です。テストやプロダクト上の答えが不明瞭な箇所は、より良い準備ができるまで後回しにします。
シンプルなリズムを保ちます: 1つのことを移す小さなPR、短いレビューサイクル、頻繁なリリース、そしてスコープが膨らんだら分割して小さい部分だけを出すというルール。
各ステージの前にロールバックポイントを設定してください: gitタグ、リリースブランチ、または動作することが分かっているビルド。Koder.ai を使っているなら、Planning Mode は意図せずに三層同時にリファクタする事故を防ぐのに役立ちます。
モジュール化アプリアーキテクチャの実用ルール: 新機能は同じ境界に従うこと。ルートは薄く、サービスはビジネスルールを所有し、DBコードは一箇所に集め、UIコンポーネントは表示に集中する。新機能がこれらのルールから外れるなら、変更が小さいうちに早めにリファクタしてください。
既定: リスクとして扱ってください。小さなレスポンス形状の変更でも複数の画面を壊すことがあります。
代わりに行うこと:
人が毎日行うフローで、コア層(認証、ルート、DB、UI)に触れるものを選んでください。
デフォルトの良い例:
繰り返し実行できるくらい小さくし、一般的な失敗ケース(例: 必須フィールドの欠如)も一つ加えて、エラーハンドリングの回帰を早く見つけられるようにします。
数分で実行できるロールバックを使ってください。
実用的な選択肢:
早い段階で一度実際にロールバックして検証しておくと、机上の計画で終わりません。
安全なデフォルトの順序は次の通りです:
この順序は影響範囲を減らします: 各層が次に触る前に明確な境界になります。
「移動」と「変更」を別タスクにすること。
助けになるルール:
挙動を変える必要があるなら、後でテストと意図的なリリースを伴って行ってください。
はい。生成コードも他のレガシーコードと同様に扱います。
実用的な方法:
CreateOrderLegacy)生成コードは外部挙動を一貫して保てれば安全に再構成できます。
トランザクションを集中化して退屈にしてください。
デフォルトのパターン:
これにより部分的な書き込み(設定のないプロジェクトが作られる等)を防ぎ、障害を追いやすくします。
リファクタを安全にするための最低限のテストは次の通りです:
目的は恐怖を減らすことであり、一晩で完璧なテストスイートを作ることではありません。
まずはレイアウトとスタイルを変えず、予測可能性に注力してください。
安全なUIの整理手順:
抽出ごとに簡単な視覚チェックと一つのエラーケースのトリガーを行ってください。
プラットフォームの安全機能を利用して、変更を小さく可逆に保ちましょう。
実用的なデフォルト:
これらの習慣は主目的をサポートします: 小さく、取り消せるリファクタで着実に自信を作ること。