LayerX エンジニアブログ

LayerX の エンジニアブログです。

既存サービスからお客様影響なしで機能を分離するための段階的マイグレーション戦略について

はじめに

こんにちは、バクラクのソフトウェアエンジニアをしている id:wataru_lx です。

バクラクシリーズでは今回運用中の複数のプロダクトでお客様の銀行口座の情報を活用する必要があり、それらの共通基盤として複数のバクラクプロダクトをまたいでお客様の銀行口座の情報を一元管理する口座管理サービスを新たに設計・実装しました。

この記事では、既存のプロダクトから一部の機能を再利用可能な形で分離し、新しいサービスとして実装するまでの過程と、その際に考慮した設計ポイントについて紹介します。

背景と課題

口座アグリゲーションサービスとは

口座アグリゲーションサービスとは、複数の金融機関の口座情報を一元的に取得・管理できるサービスです。

利用者の同意のもと、定期的に各金融サービスのサイトにログインし明細情報などのデータを取得し、一元管理することで利用者の手間を省くことができます。

基本方針の決定

まず、どのような方針でアプローチするかを検討しました。

検討した選択肢

  1. 新しい口座アグリゲーションサービスの採用

    • バクラク債権管理で独自に新しい口座アグリゲーションサービスを利用
    • 既存の Moneytree LINK とは独立したシステム構築
  2. 既存 Moneytree LINK の継続利用

    • バクラクビジネスカードで実績のある Moneytree LINK を債権管理でも活用
    • 技術的な知見とノウハウの再利用

選択した方針

バクラクとして、新規の口座アグリゲーションサービスを利用せずに、Moneytree LINK を引き続き利用する意思決定をしました。

この方針を選択した理由

  • 既存の Moneytree LINK での運用実績と安定性
  • 新しいサービス導入に伴うリスクの回避
  • 技術的知見の蓄積と再利用
  • 既存のお客様の追加アクションが不要: バクラクビジネスカードで既に Moneytree LINK と連携済みのお客様が再度別の口座アグリゲーションサービスと連携する必要がないことを意味します。これは、お客様の利便性を大きく向上させ、サービスの利用ハードルを下げることにつながります。

バクラクの状況

この方針により、以下の課題が明確になりました:

バクラクビジネスカードでは、Moneytree LINK を利用して口座情報を取得する機能が、バクラクビジネスカードサービス内部に密結合して実装されていました。新しく開発中のバクラク債権管理でも口座情報が必要になったことで、この密結合した機能を安全に分離し、複数のプロダクトで共有できる形にする必要が生じました。

graph LR
    subgraph current ["現在の状況"]
        A[バクラクビジネスカード] --> B[Moneytree LINK]
        C[バクラク債権管理] -.->|新たに必要| D[口座管理サービス]
        D -.->|新たに必要| B
    end

重要なポイント: Moneytree LINK 継続利用の方針により、既存システムからの機能分離と複数プロダクトでの共通化を実現する口座管理サービスが必要になりました。

マイグレーション戦略の検討

現状のアーキテクチャから目指すアーキテクチャへのマイグレーション戦略を検討するにあたり、いくつかの選択肢を比較検討しました。

graph TD
    classDef moneytree fill:#f9f,stroke:#333,stroke-width:2px

    subgraph asis ["現状のアーキテクチャ"]
        A[バクラクビジネスカード] --> B[ビジネスカードDB]
        A --> ML1[Moneytree LINK]
        ML1 --> B
    end

    subgraph tobe ["目指すアーキテクチャ"]
        D[バクラクビジネスカード] --> E[口座管理サービス]
        F[バクラク債権管理] --> E
        E --> G[口座管理サービスDB]
        E --> ML2[Moneytree LINK]
    end

    class ML1,ML2 moneytree

移行対象となるデータは主に以下の 2 種類です:

  1. 認証情報

    • Moneytree LINK の Refresh Token
    • 認証関連のメタデータ
  2. 口座情報

    • 銀行口座の基本情報
    • 取引明細データ

これらのデータをどのように移行するか、以下の選択肢を検討しました:

選択肢 1: 全データベースの一括移行

Pros:

  • 新サービスが完全に独立したデータベースを持てる
  • 既存システムからの完全な分離が可能
  • データの整合性管理が単純

Cons:

  • 大量のテーブルとデータの移行が必要
  • ビジネスカード固有のロジックも含まれる
  • 無停止での移行が困難
  • ユーザー体験への影響: 移行中にサービスが停止する可能性があり、お客様の利用に支障をきたす

選択肢 2: 新サービスからビジネスカード DB を直接参照

Pros:

  • データの移行が不要
  • 既存データをそのまま活用可能
  • 開発工数を最小限に抑えられる

Cons:

  • ビジネスカード専用のテーブル構造に依存
  • 債権管理で利用するテナントが既存のテーブル構造と合わない
  • ユーザー体験への影響: 既存システムの変更が新サービスに直接影響し、安定性が損なわれる可能性がある

選択肢 3: 段階的な機能別移行

Pros:

  • 無停止での移行が可能で影響範囲を最小限に抑えられる
  • 既存システムの安定性を維持しながら移行可能
  • ユーザー体験への影響を最小限に抑えられる: お客様は通常通りサービスを利用し続けることが可能

Cons:

  • 移行期間中は新旧システムの並行運用が必要
  • 一時的に複雑なアーキテクチャになる
  • 移行完了までに時間がかかる

これらの選択肢を比較検討した結果、選択肢 3 の段階的な機能別移行を採用することにしました。この戦略の大きなメリットは、システムを無停止で、お客様の影響なく新システムへの移行が進められることです。一時的な移行期間が必要になりますが、お客様影響が出ないことを優先しました。

graph TD
    subgraph strategy ["段階的移行戦略"]
        A["Phase 1: 認証の共通化"] --> B["Phase 2: 移行期間"]
        B --> C["Phase 3: 完全分離"]

        subgraph phase1 ["Phase 1の理由"]
            D["認証は絶対に共通化が必要"]
            E["Refresh Tokenは複製不可"]
            F["最小限の影響で開始可能"]
        end

        subgraph phase2 ["Phase 2以降"]
            G["段階的なデータ移行"]
            H["新旧システムの並行運用"]
        end
    end

アーキテクチャ設計と段階的移行戦略

バクラクビジネスカードに組み込まれた口座管理機能を口座管理サービスに分離するため、3 つのフェーズに分けた移行戦略を実施しました。

Phase 1: 認証の外部化とハイブリッド運用

バクラクと Moneytree LINK の接続では、OAuth2 認証を使用しています。具体的には以下のような流れで認証が行われます:

  1. お客様がバクラクビジネスカードで口座連携を開始
  2. バクラクが Moneytree LINK の認証画面にリダイレクト
  3. お客様が Moneytree LINK の画面で認証情報を入力
  4. Moneytree LINK が認証に成功すると、バクラクの指定した URL にリダイレクト
  5. バクラクは Moneytree LINK から受け取った認証情報(Access Token, Refresh Token)を保存

この認証フローにより、お客様は一度の認証で、バクラクが代理でお客様の複数の銀行口座情報を取得できるようになります。

認証情報の移行を最初のフェーズで行った理由は、Refresh Token の特性にあります。Refresh Token は複製できず、一度使用すると無効化されるため、複数の DB で別々に管理することができません。

例えば、バクラクビジネスカードの DB と債権管理の DB で別々に管理すると、どちらか一方で利用した場合、他方のトークンが無効化されてしまいます。 無効化されたトークンでは口座情報の取得ができなくなり、結果としてお客様に再認証を強制することになってしまいます。

このため、認証情報は必ず一箇所(口座管理サービス)で管理する必要がありました。この実現のために、以下のような認証フローを設計しました。

sequenceDiagram
    participant 債権管理 as バクラク債権管理
    participant アグリゲーション as 口座管理サービス
    participant アグリDB as 口座管理サービスDB
    participant カード as バクラクビジネスカード
    participant Moneytree as Moneytree API

    Note over 債権管理,Moneytree: 🔒 Refresh Token一元管理の実現

    債権管理->>アグリゲーション: GET /auth (tenant_id)

    alt 口座管理DB にデータあり
        Note over アグリゲーション: ✅ 新システムで管理中
        アグリゲーション-->>債権管理: トークンを返す
    else 口座管理DB にデータなし
        Note over アグリゲーション: 🔄 既存システムから安全に移行
        アグリゲーション->>カード: GET /auth (tenant_id)
        alt ビジネスカードにデータあり
            カード-->>アグリゲーション: ⚠️ Refresh Token(一度だけ使用可能)
            アグリゲーション->>Moneytree: Refresh Token使用
            Moneytree-->>アグリゲーション: 新しいToken発行
            アグリゲーション->>アグリDB: 💾 新システムで管理開始
            アグリゲーション-->>債権管理: トークンを返す
        else ビジネスカードにもデータなし
            アグリゲーション-->>債権管理: 認証情報なしエラー
        end
    end

この段階での技術的な要点

  1. Refresh Token の安全な移管: 既存システムから新システムへの Token 移管
  2. 二重管理の回避: 同じ Token が複数システムで管理されることを防ぐ
  3. ユーザー体験の維持: 認証フローの変更をお客様に意識させない

Token 移管の疑似コード

// 以下は疑似コードです
// Refresh Tokenの安全な移管処理
func (m *Moneytree) GetAuthToken(ctx context.Context, tenantID string) (*AuthToken, error) {
    token, err := m.db.GetAuthToken(ctx, tenantID)
    if err != nil {
        // Tokenが口座管理サービスにあればそれを利用
        return token, nil
    }

    // Tokenが見つからない場合はビジネスカードから取得
    res, err := m.cardClient.GetAuthToken(ctx)
    if err != nil {
        return nil, err
    }

    // ビジネスカードから取得したTokenを保存しておく
    err = m.db.Transaction(func(tx db.Tx) error {
        return m.db.SaveAuthToken(ctx, tx, token)
    })
    if err != nil {
        return nil, err
    }

    return token, nil
}

Phase 2: 無停止でのデータ移行(現在進行中)

このフェーズはデータの移行期間として設けています。以下のような仕組みで移行を進めています:

  1. 自然なデータ移行:

    • お客様がバクラクビジネスカード、もしくはバクラク債権管理のどちらかを利用すると認証情報が口座管理サービスに移行
    • ビジネスカード利用ユーザーの口座情報は引き続き旧 DB に書き込み
    • 債権管理を利用しているか、認証情報が移行済みユーザーの口座情報は新 DB にも書き込み
  2. 二重書き込みによる安全性確保:

    • 移行中は新旧両方の DB にデータを書き込む
    • データの整合性を保ちながら、安全に移行を進める
    • ビジネスカードのみで必要な ETL 処理などは引き続き旧 DB を参照する
graph TD
    subgraph migration ["移行中のアーキテクチャ"]
        A[バクラクビジネスカード] --> B[既存DB]
        A --> H[Moneytree API]
        H --> B

        F[バクラク債権管理] --> C[口座管理サービス]
        C --> I[Moneytree API]
        C --> D[新DB]

        G[ETL] --> B

        A -.->|認証のみ| C
        C -.->|フォールバック| A
    end

Phase 3: 完全移行と依存関係解消(今後の計画)

最終フェーズでは、既存システムの依存関係を完全に除去し、新システムへの完全移行を実施する予定です。:

  1. 段階的な移行:

    • ビジネスカード内の Moneytree LINK の直接 API 呼び出しを段階的に削除しすべて口座管理サービス経由にする
    • データベーススキーマの及びコードベースのクリーンアップ
    • ETL パイプラインの更新
  2. 安全性の確保:

    • 各段階での動作確認
    • ロールバック可能な形で行う
graph TD
    subgraph after ["移行完了後のアーキテクチャ"]
        A[バクラク債権管理] --> B[口座管理サービス]
        C[バクラクビジネスカード] --> B
        B --> D["DB"]
        B --> E[Moneytree API]
        G[ETL] --> D
    end

まとめ

口座管理サービスの実装を通じて、既存プロダクトに密結合している機能をお客様影響なしで分離する難しさ、そして段階的な移行戦略の重要性を実感しました。

特に、以下の点に注意を払いました:

  1. ユーザー体験の維持:

    • サービス停止を避ける
    • お客様に追加アクションを求めない(認証基盤の移管に伴う再認証)
    • 既存機能を維持しながらの移行
  2. 技術的な課題への対応:

    • 認証情報の安全な移行
    • 複数システムの整合性維持
  3. 移行の安全性確保:

    • 段階的な移行によるリスク分散
    • ロールバック可能な形で行う

今後も、LayerX では継続的にシステムアーキテクチャの改善を進め、技術的な挑戦を通じてより良いサービス提供を目指していきます。