コンテンツにスキップ
検索語を入力してください

    段階的導入

    レガシー Scalaコードベースを一括置換すると、境界・エラー・永続化の穴が同時に広がる。Kamaeでは触れたワークフローごとに、DTOパース → 型付き状態 → ポート分離 → 原子性永続化の順で段階的に締める。

    各段の詳細は 境界防御アプリケーション配線ORM アダプタ を参照する。

    Kamaeはまず新しいコードパスに適用する。既存コードは、機能追加やバグ修正で触る箇所から段階的に引き締める。ドメイン全体の書き直しでリリースを止めない。

    レガシーな慣習と衝突する場合、触っていないコードはローカルな慣習に従い、新旧の境界では新しい境界を明示的に文書化する。

    Scalaサーバーコードベースでよくある出発点:

    • 貧血case classとservice objectやimplicit extensionの乱立
    • Slick / doobieの行型をドメインエンティティとして使う
    • opaque typeの代わりに String のIDやstatus文字列
    • ビジネスロジック中の throw.get、素の Exception
    • controllerやrouteがJDBC / HTTPを直接呼ぶ

    これらは移行の出発点であり、失敗ではない。次に起きそうなバグを取り除く最小の変更を選ぶ。

    一度に一段ずつ進める。各ステップは単独でレビューできること。

    Step変更典型的な触りどころリスク
    0. 境界のみDTO/row → 新エンドポイント向け Either パースhttp4s / Pekko route、consumer
    1. ID と値オブジェクトRequestIdMoneyOccurredAt などの opaque type変更フローで使う models
    2. ドメインエラー新ユースケースでの error ADTapplication layer
    3. 型付き状態重要な集約 1 つ分の sealed statedomain module
    4. ポート新ユースケースの背後に小さな repository traitapplication + infrastructure
    5. トランザクションとバージョン原子的 save、outbox、楽観的バージョンチェックpersistence adapter中〜高

    コードベースがすでに満たしているステップだけスキップする。

    サービス全体ではなく機能で絞り込む(Strangler Fig)

    Section titled “サービス全体ではなく機能で絞り込む(Strangler Fig)”

    レガシーモジュールに対して:

    1. 変更したワークフロー用に新しいuse-caseクラスを追加する。
    2. 新パスが実証されるまで、旧エントリポイントはレガシーコードを呼び続ける。
    3. 新APIバージョン、フラグ、コマンドを新ユースケースへルーティングする。
    4. パリティテストが通ったら旧パスを削除する。
    legacy route -> legacy service -> JDBC
    new route -> AssignDriver use case -> port -> adapter -> JDBC

    移行スライスは集約1つ、またはエンドポイント1つを優先する。

    レガシー移行の段階的ロードマップ

    Section titled “レガシー移行の段階的ロードマップ”

    例: http4s + doobieのモノリスサービスで POST /requests/{id}/assign を移行する。

    Phase 1 — 挙動を固定し、テストを追加(1 週目)

    Section titled “Phase 1 — 挙動を固定し、テストを追加(1 週目)”
    1. 統合テストで現行HTTP契約を記録する(ステータスコード、JSON形状)。
    2. レガシーパス周辺にlogging / metricsを追加し、トラフィックを計測する。
    3. まだ挙動は変えない。
    1. interfaces モジュールに AssignDriverBodyAssignDriverDto を導入する。
    2. routeの直接フィールドアクセスを AssignDriverCommandEither パースに置き換える。
    3. レガシーサービスはまだ文字列を受け取る。検証は境界に移る。
    4. 同じrouteのまま出荷する。テストは緑のまま。

    境界防御 を参照。

    Phase 3 — 触った ID の opaque type(2 週目)

    Section titled “Phase 3 — 触った ID の opaque type(2 週目)”
    1. domain モジュールに RequestIdDriverId opaque typeを追加する。
    2. 境界パースをopaque type構築に変更する。レガシーサービスは境界で .value を受け取る。
    3. 新しい domain モジュールだけに厳格なscalafix / scalacオプションを有効化する。

    ドメインモデリング を参照。

    Phase 4 — ユースケース抽出(3 週目)

    Section titled “Phase 4 — ユースケース抽出(3 週目)”
    1. レガシー SQLをprivateメソッドにインラインした AssignDriver を作成する。
    2. ハンドラは useCase.execute(cmd) のみ呼ぶ。
    3. このパスの例外を AssignDriverError ADTと Either に置き換える。

    エラーハンドリング を参照。

    Phase 5 — 1 集約の型付き状態(3〜4 週目)

    Section titled “Phase 5 — 1 集約の型付き状態(3〜4 週目)”
    1. WaitingRequestEnRouteRequest をモデル化し、割当を WaitingRequest.assignDriver に移す。
    2. DBのレガシー status: String は残す。adapterがrow ↔ state typeをマップする。
    3. HTTPなしで遷移の単体テストを追加する。

    状態遷移ORM アダプタ を参照。

    Phase 6 — リポジトリポート(4〜5 週目)

    Section titled “Phase 6 — リポジトリポート(4〜5 週目)”
    1. TaxiRequestRepository[F] と関連port traitを定義する。
    2. SQLをユースケースから DoobieTaxiRequestRepository に移す。
    3. ユースケースはtraitのみに依存する。main またはコンポジションルートで配線する。

    永続化、集約、イベントアプリケーション配線 を参照。

    Phase 7 — トランザクション、バージョン、outbox(5〜6 週目)

    Section titled “Phase 7 — トランザクション、バージョン、outbox(5〜6 週目)”
    1. version 列と条件付き UPDATE を追加する。
    2. state saveとoutbox insertを1トランザクションに包む。
    3. リトライクライアント向けにidempotency keyを追加する。

    永続化、集約、イベント を参照。

    Phase 8 — レガシーパス削除(6 週目以降)

    Section titled “Phase 8 — レガシーパス削除(6 週目以降)”
    1. feature flagまたはrouteトラフィックが新パス100% であることを確認する。
    2. レガシーサービス関数と死んだ status 文字列チェックを削除する。
    3. 移行モジュールに kamae-scala-review を実行する。

    ペースはチーム規模に合わせて調整する。可能なら各フェーズを独立PRにする。

    • 避けられる限り、機械的リファクタと挙動変更を1 PRに混ぜない。
    • 旧パスを削除する前に、新境界にテストを追加する。
    • 触ったフィールドだけopaque typeとDTO変換を導入し、後で広げる。
    • 強化するモジュールで追加scalafixルールを有効化する。
    • 新旧の意味論が異なる場合のみ、短いコメントまたはADRを残す。

    ラダーを登り止めるタイミング

    Section titled “ラダーを登り止めるタイミング”

    すべてのstructにstate machineやrepository traitは不要。次の場合は現段階で止める:

    • コードが安定し、低リスクで、めったに変わらない
    • 集約に意味のあるライフサイクルや不変条件がない
    • チームがpersistenceや並行性の挙動をまだ十分にテストできない

    バグ、コンプライアンス要件、並行性が現状の形では弱すぎると示したら、一段上げる。

    エージェントとレビュアーの期待

    Section titled “エージェントとレビュアーの期待”

    移行時:

    • スコープ判断に 段階的導入 を読み込む
    • 実装する段のトピックガイドを読み込む
    • 周囲がレガシーでも、変更パスに kamae-scala-review を使う
    • サービス全体が移行済みのふりをせず、残るレガシーリスクを明示する