web-dev-qa-db-ja.com

CRUD API:更新するフィールドをどのように指定しますか?

ある種のデータベースに永続化されているある種のデータ構造があるとします。簡単にするために、このデータ構造をPersonと呼びます。これで、他のアプリケーションがPersonsを作成、読み取り、更新、および削除できるようにするCRUD APIを設計する必要があります。簡単にするために、このAPIが何らかのWebサービスを介してアクセスされると仮定します。

CRUDのC、R、Dパーツのデザインはシンプルです。私はC#のような関数表記を使用します-実装はSOAP、REST/JSON、または他の何かである可能性があります:

_class Person {
    string Name;
    DateTime? DateOfBirth;
    ...
}

Identifier CreatePerson(Person);
Person GetPerson(Identifier);
void DeletePerson(Identifier);
_

アップデートはどうですか?自然なことは

_void UpdatePerson(Identifier, Person);
_

しかし、更新するPersonの-​​whichフィールドをどのように指定しますか?


私が思いつくことができる解決策:

  • 常にcomplete Personを渡す必要があります。つまり、クライアントは次のようにして生年月日を更新します。

    _p = GetPerson(id);
    p.DateOfBirth = ...;
    UpdatePerson(id, p);
    _

    ただし、そのためには、GetとUpdateの間でトランザクションの整合性またはロックを行う必要があります。そうしないと、他のクライアントによって並行して行われた他の変更を上書きする可能性があります。これにより、APIがさらに複雑になります。さらに、次の疑似コード(JSONをサポートするクライアント言語を想定)であるため、エラーが発生しやすくなります。

    _UpdatePerson(id, { "DateOfBirth": "2015-01-01" });
    _

    -which looks正しい-DateOfBirthを変更するだけでなく、他のすべてのフィールドをnullにリセットします。

  • nullであるすべてのフィールドを無視できます。ただし、変更なしDateOfBirth意図的にnullに変更をどのように違いますか?

  • 署名をvoid UpdatePerson(Identifier, Person, ListOfFieldNamesToUpdate)に変更します。

  • 署名をvoid UpdatePerson(Identifier, ListOfFieldValuePairs)に変更します。

  • 送信プロトコルのいくつかの機能を使用します。たとえば、PersonのJSON表現に含まれていないすべてのフィールドを無視できます。ただし、そのためには通常、JSONを自分で解析し、ライブラリの組み込み機能(WCFなど)を使用できないようにする必要があります。

ソリューションはどれも私には本当にエレガントに思えません。確かに、これは一般的な問題なので、誰もが使用するベストプラクティスソリューションは何ですか?

9
Heinzi

このオブジェクトの要件として変更の追跡がない場合(たとえば、「ユーザーJohnが名前と生年月日を変更しました」)、最も簡単なのは、DB内のオブジェクト全体をコンシューマーから受け取ったオブジェクトで上書きすることです。このアプローチでは、ワイヤーを介して送信されるデータが若干増えますが、更新前の読み取りは回避されます。

活動追跡の要件がある場合。あなたの世界ははるかに複雑であり、CRUDアクションに関する情報を格納する方法とそれらをインターセプトする方法を設計する必要があります。それはあなたがそのような要件を持っていない場合、あなたが飛び込みたくない世界です。

個別のトランザクションで値を上書きすることにより、楽観的および悲観的ロックについて調査することをお勧めします。彼らはこの一般的なシナリオを軽減します:

  1. オブジェクトはuser1によって読み取られます
  2. オブジェクトはuser2によって読み取られます
  3. User1によって書き込まれたオブジェクト
  4. User2によって書き込まれたオブジェクトとuser1によって上書きされた変更

ユーザーごとにトランザクションが異なるため、これを使用する標準SQLがあります。最も一般的なのは楽観的ロックです(バージョンに関するコメントで@ SJuan76でも言及されています)。 DB内のレコードのバージョン。書き込み中、バージョンが一致する場合は、まずDBを調べます。バージョンが一致しない場合、その間に誰かがオブジェクトを更新したことを知っており、この状況についてコンシューマにエラーメッセージで応答する必要があります。はい、この状況をユーザーに示す必要があります。

書き込み前にDBから実際のレコードを読み取る必要があることに注意してください(楽観的ロックバージョンの比較のため)。デルタロジックの実装(変更された値のみを書き込む)では、書き込み前に追加の読み取りクエリが必要ない場合があります。

デルタロジックの配置は、コンシューマとの契約に大きく依存しますが、コンシューマにとって最も簡単なのは、デルタの代わりに完全なペイロードを構築することです。

8
luboskrnac

PHP APIが機能しています。更新の場合、JSONオブジェクトでフィールドが送信されない場合、フィールドはNULLに設定されます。その後、すべてがストアドプロシージャに渡されます。ストアドプロシージャは、 field = IFNULL(input、field)ですべてのフィールドを更新します。JSONオブジェクトに1つのフィールドしかない場合は、そのフィールドのみが更新されます。フィールド= ''が必要なセットフィールドを明示的に空にするには、DBがフィールドを更新します空の文字列またはその列のデフォルト値。

2
Jared Bernacchi

クエリ文字列で更新されたフィールドリストを指定します。

PUT /resource/:id?fields=name,address,dob Body { //resource body }

保存されたデータをリクエストボディからのモデルとマージする実装:

private ResourceModel MergeResourceModel(ResourceModel original, ResourceModel updated, List<string> fields)
{
    var comparer = new FieldComparer();

    foreach (
            var item in
            typeof (ResourceModel).GetProperties()
                    .Where(p => p.CustomAttributes.All(a => a.AttributeType != typeof (JsonIgnoreAttribute))))
    {
        if (fields.Contains(item.Name, comparer))
        {
            var property = typeof (ResourceModel).GetProperty(item.Name);
            property.SetValue(original, property.GetValue(updated));
        }
    }

    return original;
}
1
adisembiring