web-dev-qa-db-ja.com

JSONデータ形式の変更を処理する正規の方法はありますか?

問題

JSONにシリアル化されたC#クラス(現在Newtonsoftの JSON.Net を介して)があり、データベースに格納されているとします。

public class User
{
    public string authInfo;
}

クラス定義が変更されると、古いデータの読み込みに失敗します。手作業でデータベースを更新しようとしても、変換中にサーバーのダウンタイムがない限り、データが失われる可能性があります。

public class User
{
    public string username;
    public string token;
}

ソリューション(私の試み)

古いデータを新しいデータ形式に変換する逆シリアル化の後に実行されるコールバックを使用できます。 (属性とパラメーターは、使用されているシリアル化フレームワークに基づいて調整する必要があります。):

public class User
{
    public string username;
    public string token;
    [Obsolete] public string authInfo;

    [OnDeserialized]
    public void FixData()
    {
        if (username == null)
        {
            var parts = authInfo.Split("/");
            username = parts[0];
            token = parts[1];
            authInfo = null;
        }
    }
}

フィールドの形式をリストからオブジェクト(または数値)に、またはその逆に変更する必要がある場合、新しいフィールドはauthInfo_2、タイプが再び変更されるとインクリメントされます。フィールドの形式をあるタイプのリストから別のタイプのリストに変更する必要がある場合は、新しいフィールドも作成する必要があります。

public class User
{
    [Obsolete] public List<string> address;
    public List<AddressLine> address_2;
    // FixData() will convert from address to address_2
}

問題:nullが古いデータまたは新しいデータの有効な値である場合、データが新しい形式に移行されたかどうかを判断できません。以下は、新しいデータが追加されたかどうかを追跡する回避策です。

public class User
{
    [Obsolete] public List<string> name; // serialized old data
    private string _familyName; // serialized
    private bool _isFamilyNameSet; // serialized
    public string familyName { get { return _familyName; } set { _familyName = value; _isFamilyNameSet = true; } } // not serialized
    // FixData() will convert from name to familyName
}

質問

この手順は私が作成した一連のルールであり、おそらく重要な何かを見逃しているでしょう。シリアル化されたデータのバージョン管理を扱う、認められたベストプラクティスはありますか? (バージョン番号を含めると、多くの問題が発生するようです。)

2
piojo

問題

一般的に言えば、同じデータモデルの異なるバージョンを同じコードで処理すると、余分な不要な複雑さが生じます。一般的な問題には次のものがあります。

  • 名前が変更されたフィールド
  • 変更されたデータ型
  • 古いフィールドが削除されました
  • 追加された新しいフィールド
  • 複数のフィールドにリファクタリングされた既存のデータ
  • 既存のデータを1つのフィールドに結合
  • 再定義された既存のフィールドのセマンティクス

これらのどれもあなたがそれを助けることができるならばあなたがあなたのコア/ドメインロジックに忍び込んでもらいたいものではありません。

さらに、将来的に他のバージョンの変更を計画している場合、古い形式を維持することにより、データ形式の複数の進化を経て、複雑さが急増している可能性があります。


理想的には、すべての古いデータを新しい形式に移行し、古い形式を完全に解散します

このシナリオを処理する理想的な方法は、最初にドメインロジックがさまざまなデータ形式に煩わされないようにすることです。新しい形式を追加するたびに複雑さが増しますが、データを移行することにより、「1回限り」の操作になる可能性があります。

データの移行を実行するときは、「ロールバック」パスを作成することが重要です。つまり、移行中に問題が発生した場合にデータの損失を防ぐことができるように、適切なバックアップ/復元手順を導入します。

また、適切な健全性チェックとデータ検証を適切に実施して、移行後にデータが良好な状態であることを確認してください。

もちろん、これは常にオプションというわけではありません。複数のデータバージョンは、避けられない、必要な悪です。


移行が選択できない場合は、永続モデルをドメインモデルから分離します。

このロジックは、実行時に発生することを除いて、移行コードと多少似ていますが、「移行ロジック」は、データが完全に移行または廃棄され、追加の注意が必要になるまで、長期にわたって留まります。それをアプリケーションの残りの部分から切り離します。

データベース内の異なる形式で保存された同じデータのshapeの異なるバージョンまたはバリエーションに関する懸念は、他の場所から離れた1か所で処理する必要がありますあなたのコード;残りのロジックに必要なすべてを含む標準のデータレイヤーインターフェイスの背後に隠されています。これにより、データベースに複数のデータ形式を格納することの複雑さと影響を最小限に抑えることができます。

コアロジックに複数の異なる形式を可能な限り公開しないでくださいコードの残りの部分は、永続データの実際の形状または形式にとらわれない必要があります。

データレイヤーの内部では、JSON.NETでデシリアライズするために使用できるさまざまな「モデル」構造を保持します。 AutoMapperで「永続性」モデルとドメインモデルを切り替える方法をご覧ください。これらのJSONシリアライザーモデルは、永続化フォーマットの知識を表すため、コアロジックのどこにも使用しないでください。

これには何らかのバージョン管理が必要になります-リポジトリ/シリアライザーはデシリアライズする内部JSONモデル形式を知る必要があるため、おそらくシリアル化されたデータと共にデータベース内にバージョン番号を保存する必要があります。異なるデータバージョンを明確に区別する方法。

Booleanフィールドを使用してバージョンを切り替えることは避けてください。データ形式を区別する方法が「isNewVersion」などのtrue/false値になる場合は、将来バージョン3を導入する場合に問題になります。 。

例えば:

internal class MyDataModelVersion1 { /* Old JSON Persistence Format POCO */ }

internal class MyDataModelVersion2 { /* New JSON Persistence Format POCO */ }

public class MyStandardModel { /* Common/Domain Model */ }

public class MyRepository 
{
    public MyStandardModel GetData(int id) 
    {
        var row = ReadFromDatabase(id);
        MyStandardModel model = null;

        if (row.Version == 1)
        {
            var data = Json.DeserializeObject<MyDataModelVersion1>(row.Json);
            model = Mapper.Map<MyStandardModel>(data);
        }
        else if (row.Version == 2)
        {
            var data = Json.DeserializeObject<MyDataModelVersion2>(row.Json);
            model = Mapper.Map<MyStandardModel>(data);
        }
        else { /* throw exception */ }

        return model;
    }
}

このアプローチの主な理由は、新しいデータシェイプを導入するときに変更が必要なコードの唯一の部分がリポジトリ/データレイヤーにあることを確認するためです。残りのコードは気にする必要がありません。

移行ほど理想的ではありませんが、データバージョンの切り替えを1つの場所にカプセル化し、コアロジックの汚染を回避します。

6
Ben Cottrell

新しいクラスに古いクラスを知らせないようにします。

クラス名が変更された場合、

OldRepository
{
    public List<OldUser> GetAll()
}

Converter
{
    public NewUser Convert(OldUser)
}

NewRepository
{
    public void Add(NewUser)
}

次に、スクリプトを使用してDB全体を新しい形式に変換するか、新しいクラスの古いクラスに依存せずにオンザフライで変換できます。

通常、このようなDBにシリアル化されたデータを格納する必要がある場合は、フィールドを分割するのではなく、特定の行に格納されているデータのバージョンを確認できるように、データのバージョン管理を含める必要があります。

@ Hans-Martinは以下のように述べています。複数のデータバージョンが長時間ぶらつくと、予期しない問題が発生する可能性があります。クリーンブレークを実行して、すべてのデータを新しい構造にアップグレードできる場合、それは良いことです。

主な問題は、ダウンタイムなしで変更を処理することです。

3
Ewan