🔄

レガシーシステムでデータベース操作ライブラリを安全に置き換える

2024/12/11に公開

はじめに

この記事は コミューンプロダクト開発 Advent Calendar 2024 の11日目です。

背景

レガシーめなサーバサイドので利用しているデータベース操作ライブラリのリプレースを行いました。
状況としては以下の通りです。

  • データベース操作ライブラリに依存したトランザクションが、複数のレイヤーにまたがっている
  • データベース操作ライブラリに依存したモデルデータが、複数のレイヤーで使い回されている

また以下のような制約があります。

  • 新旧のデータベース操作ライブラリでトランザクションに互換性はなく、1つAPIエンドポイントで両方のライブラリを使うことはできない
  • 複数のAPIエンドポイントが同じメソッドを参照していることもある

そのため単純なリプレースだと以下のイメージように影響範囲が肥大化し、障害発生のリスクが高まります。

このような依存関係になっているとして、


/endpoint1 を切り替えるには


参照先すべてを変える必要がある


その依存先も変える必要がある


その参照先、その依存先、…と影響範囲が広がっていく

アプローチ

根本解決としては依存関係をきちんと整理してリファクタリングすることなのですが、それは工数がかかりすぎて現実的ではありませんでした。

そのため、以下のようなアプローチにてデータベース操作ライブラリのリプレースを行うことにしました。
ここから「古いデータベース操作ライブラリ」を「旧ライブラリ」、「新しいデータベース操作ライブラリ」を「新ライブラリ」と表記します。

  1. APIエンドポイント単位で、旧ライブラリから新ライブラリへの切り替えを行う
  2. 置き換えるAPIエンドポイントが参照しているすべてのメソッドを、新ライブラリを使うように修正する
  3. 他のエンドポイントからも参照しているメソッドは、旧ライブラリを使っているコードは残し、横に新ライブラリを使ったコードを追加する(実装の中身はコピペする)
  4. 旧ライブラリを使っているメソッドの参照がなくなったら、そのメソッドを削除する

図にすると以下のようになります。


このような依存関係になっているとして、


/endpoint1 の切り替えを行いたい


自分以外からも参照されているものは、切り替え後のコピーを用意する


続いて /endpoint2 の切り替え


既に切り替え済みのメソッドがあれば付け替え、参照されなくなったメソッドは削除


最後に /endpoint3 の切り替え


既に切り替え済みのメソッドがあれば付け替え、参照されなくなったメソッドは削除

このようにして、エンドポイント単位でライブラリ切り替えを可能とする戦略を立てました。

これを高速で行うために

ソースコードは TypeScript で書かれているため、上記アプローチは ts-morph にて効率化しました。
ts-morph とは一言で説明すると、 TypeScript の構文解析や変換を簡単に行えるライブラリです。

詳細な実装は割愛しますが、以下のようなフローで置き換えを半自動化できます。

  1. メソッドからの参照を末端まで確認し、旧ライブラリを使っていなければ何もしない
  2. メソッドへの依存が複数あるか確認し、複数あれば横に新メソッドを追加する
  3. メソッド(または新メソッド)内の各行について
    a. 旧ライブラリの呼び出しであれば、新ライブラリの呼び出しに変換する
    b. メソッドの呼び出しであれば、このフローチャートを再帰的に呼び出す
    c. そうでなければ何もしない

旧ライブラリから新ライブラリへの変換部分については、使用しているメソッドや引数の詳細を確認し、泥臭い変換の実装が必要になりました。
一方でその他の部分については、ほぼほぼ自動化できました。

所感

記載した方法で概ね上手く変換することができました。
一方でうまくいかなかったポイントもありました。

  • 旧ライブラリ独自の手法を用いているところは、新ライブラリでその挙動を実現する方法をゼロから模索し、手動で書き換えなければならなかった
    今回でいうと Plain SQL を使っているところで起こりがち。
  • 新旧ライブラリの挙動で実は細かい差分があり、事故ってしまうこともあった
    true/false なのか 1/0 なのか、など…
  • 一時的に同じ処理が2箇所に書かれることになるが、一方のみに機能追加やバグ修正などを行ってしまうことがあった
    新旧メソッドの互換性が取れなくなったり、切替時にバグが再発したり
  • 新ライブラリへの置き換えがまだの領域への機能追加で、旧ライブラリを使ったコードで実装が進むことがあった
    旧ライブラリを剥がしてる一方で旧ライブラリのコードが生み出される悲しみ

…後半については、チーム間のコミュニケーションは大事という話ですね。

まとめ

ライブラリの利用はシステム開発と切っても切れない関係です。
導入時にそのメンテナンスやリプレースも見据えた設計ができていればよいのですが、そうでない場合も多いと思います
本記事のアプローチ以外の方法もあるとは思いますが、影響範囲を最小化しつつリプレースを行うための方法として参考になれば幸いです。

コミューン株式会社

Discussion