freeeの開発情報ポータルサイト

ファイルの後方互換を考える

こんにちは、freee販売 開発エンジニアをやっているtakayanです。この記事はfreee Developers Advent Calendar 2023 - Adventar 17日目です。

2023年に開催されたfreee技術の日ではPublic APIについての話をしました。その中で後方互換の話もしましたが、この記事では後方互換はPublic APIだけではなくWebアプリケーション開発でも気にしないといけないということを書いていきます。

アプリケーション内にあるデータをCSVなどで取り出す機能はそれなりに世の中にあると思います。 今回はWebアプリケーションにおけるインポートやエクスポートファイルの扱いに焦点を当てたいと思います。

そもそも後方互換はなんで大事なの?

例として、毎日の食事を記録できるアプリを考えます。 そこで記録した食事のCSVファイルをエクスポートすると以下のようなファイルがエクスポートされるとします。

日時 主食 副菜
2023年12月17日 ごはん 味噌汁
2023年12月18日 パン コーンスープ

仕様変更により以下のようになったとします。

日時 食事内容
2023年12月17日 [ごはん, 味噌汁]
2023年12月18日 [パン, コーンスープ]

このとき、今までcsvファイルをエクスポートして独自で食事内容からカロリーを計算してた人は、食事内容についてカラムが変わったので例えば以下のようにコードを変更する必要があります。

# calculcate_kcalはカロリーを計算する関数

# before
calculate_kcal(主食) 
calculate_kcal(副菜)

# after
食事内容.each { | 食事 |  calculate_kcal(食事) } 

csvを変更した後にbeforeのままのだとbeforeの方は引数として、配列を期待していないのでエラーが起こり今までうまくいっていたカロリー計算が急に失敗することになり、ユーザーさんの不利益に繋がります。

やってしまいがちな後方互換の失敗

では、やってしまいがちな失敗をいくつか挙げたいと思います。

カラムの名前の変更

これは上記例にも当てはまりますが、インポートファイルやエクスポートファイルの入出力内容の見直しやwebアプリケーションの仕様変更に追従する形で変更されることで起こることが多いかなと思います。

ファイル名の変更

カラム名の変更に関連してファイルの仕様が変わったのでそれがわかるようにv2と末尾につけたり、似たような名前のファイルを扱う機能を実装したのでわかりやすいように既存のファィルの名前を変更したりすることで起こることが多いかなと思います。

この場合、もともと食事.csvのようなファイル名で扱っていたとして変更後のファイルが食事内容.csvというファイル名にアプリケーションで変更された場合、ユーザーさんが食事.csvのつもりで扱っていた処理は全て食事内容.csvで扱わないといけなくなり、気づかないとファイル名の違いでエラーになります。

ロジック修正による変更

WebアプリケーションのUI変更や仕様変更によるドメインロジックなどの変更によって影響がファイル内容に及ぶことがあります。影響範囲の読み違えだったりで起こります。

この場合、ユーザーさんは急に意図しない結果を受け取ることになります。

後方互換を失う変更を行うときの変更方法の例

失敗するということはそのような変更がしたいということでもあります。 そうような時でもどうしたら比較的安全に変更をできるのかの例をいくつか挙げていきたいと思います。

変更を入れるnヶ月前にお知らせを出す

既存のファイルから変更を加える時にnヶ月前にユーザーに変更箇所の連絡をして移行作業を前もってやってもらう方法です。freeeのPublic APIではこの方法をとっています。 利点としては、複数の仕様を並列稼働させなくていいことです。一方で、ユーザーさんには予告だけで実物を見ずに急に変更が加えられることで切り替えをうまくやらないといけないという負担がかかります。

変更したいカラムを単純に増やす

カラムを変更するのではなく追加し両方に対応するようにする方法です。

日時 主食 副菜 食事内容
2023年12月17日 ごはん 味噌汁 [ごはん, 味噌汁]
2023年12月18日 パン コーンスープ [パン, コーンスープ]

このようにして、両方返すことで新旧どちらでも使えるようにし、インポートでは存在する方または優先される方を使用してインポートするという方法です。 これは、ファイルのパラメータを追加することで、ユーザーはファイルが変更されるタイミングでの後方互換を気にする必要がなくなります、一方で、開発側はどちらを優先するかの分岐を実装することになりますし、両方から片方に統一したい時はまた同じ後方互換の悩みを持つことになります。

暫定版として公開してしばらくして確定版に変更する

暫定版として変更後のファイルを公開し、廃止予定のファイルをdeprecatedとして提供し、頃合いを見て暫定版を確定版にし、deprecated版を廃止することで入れ替えます。これは、例えばrubyなどの言語のversion管理に見られるように実験版を提供しその後、確定版を提供するというやり方と似ています。

このやり方では、ユーザーは実際に提供される予定の仕様を見て、ファイルの仕様変更に対応できます。一方、開発者はしばらく入れ替えるまでversionを2つ管理する必要がある他、できれば廃止予定のものを閉じるにはそれがどれだけ使われ続けているかを監視した方がいいかもしれません。

Public APIでも大型の仕様変更をする際は、この方法を取ることがあります。同様に、ファイルの変更でもまるっきり内容や名前を変更する場合はこちらを採用するのがいいのかもしれません。 Public APIについてもブログに掲載されているので参考にしてみてください。

version管理して並行稼働させる

gemなどのパッケージで見られるように、過去versionは更新しないがそのまま置いておくという方法です。これは、ユーザーは後方互換をあまり考えず同じversionを使い続けることができますが、一方で開発者は複数のversionを管理し続ける必要があります。また、サポートのversion範囲も定めた方がいいでしょう。

終わりに

油断するとやってしまいがちな後方互換の失敗、ファイルは提供するとどう使われるか、またインポートの時もどんなファイルがやってくるかを完全に把握できない難しさがあります。 このブログをきっかけに、日々の開発に少しでも後方互換を意識して開発してもらえると幸いです。

それでは明日はymrlさんにバトンタッチしたいと思います。