古いバージョンのファイルを捨てない:連鎖的変換の仕組み
エクスポートファイルのバージョンが上がっても、古いファイルを読み込める。 v1.0.0→v1.1.0→v1.2.0と段階的に変換する仕組みについて解説します。
破壊的変更とは
ソフトウェアを開発していると、データの持ち方を変えたくなることがあります。 機能追加や設計の見直しに伴い、データベースの構造を変更する必要が出てくるのです。
一括採点では、これまでに以下のような変更がありました:
| バージョン | 変更内容 |
|---|---|
| v1.1.0 | UserProjectに招待情報を追加 |
| v1.2.0 | PageImageをMasterImageとStudentAnswerImageに分離 |
| v1.3.0 | studentIdをstudentNumberにリネーム |
これらは破壊的変更と呼ばれます。 古い形式のデータは、そのままでは新しいバージョンで読み込めません。
古いバージョンで作成したエクスポートファイルを、 新しいバージョンでそのまま読み込もうとするとエラーになります。 形式が変わっているからです。
2つの選択肢:直接変換 vs 連鎖変換
破壊的変更が起きるたびに、変換処理を書く必要があります。 ここで2つのアプローチがあります。
A. 各バージョンから最新版への直接変換
v1.0.0 → v1.3.0 を書く
v1.1.0 → v1.3.0 を書く
v1.2.0 → v1.3.0 を書くバージョンが増えるほど、書くコードが爆発的に増えます。 v1.4.0をリリースすると、さらに3つの変換器を追加(または既存を修正)する必要があります。
B. 隣り合うバージョン間の連鎖変換
v1.0.0 → v1.1.0 → v1.2.0 → v1.3.0一括採点ではBを採用しました。理由は:
- 書く変換器の数が少ない - 常に1つ追加するだけ
- 各変換器がシンプル - 1つの破壊的変更だけを扱う
- 既存コードの修正が不要 - 新しい変換器を追加するだけ
連鎖変換の実装
変換器は配列で管理されています:
const TRANSFORMERS = [
new V1_0_0_to_V1_1_0_Transformer(),
new V1_1_0_to_V1_2_0_Transformer(),
new V1_2_0_to_V1_3_0_Transformer(),
]インポート時には:
- ファイルのバージョンを検出(例: v1.0.0)
- 現在のバージョンまでの変換チェーンを構築
- 順番に変換を実行
v1.4.0をリリースするときは、V1_3_0_to_V1_4_0_Transformerを追加するだけ。既存のコードを変更する必要はありません。
v1.0.0のファイルをインポートする場合は3つの変換器すべてを実行し、 v1.2.0のファイルなら最後の1つだけを実行します。
実際の変換例
v1.1.0 → v1.2.0:テーブル分離
この変換では、PageImageを2つのテーブルに分離しています。
// 古い形式: 1つのテーブルに混在
interface PageImage {
type: "master" | "answer"
// ...
}
// 新しい形式: 役割ごとに分離
interface MasterImage { /* 解答用紙のマスター画像 */ }
interface StudentAnswerImage { /* 生徒の解答画像 */ }変換処理では、typeフィールドを見て振り分けます。マスター画像と生徒の解答画像を分離することで、 それぞれに適した処理ができるようになりました。
v1.2.0 → v1.3.0:フィールド名変更
// 古い形式
{ studentId: "001" }
// 新しい形式
{ studentNumber: "001" }フィールド名を置き換えるだけのシンプルな変換です。studentIdという名前では「データベースのID」と紛らわしかったため、studentNumber(学籍番号)に改名しました。
まとめ
連鎖的変換の仕組みにより、どんなに古いバージョンのファイルでも読み込めるようになっています。
「去年作ったファイルが開けない」ということがないよう、 後方互換性を大切にしています。