CRUD-API: Wie geben Sie an, welche Felder aktualisiert werden sollen?


9

Angenommen, Sie haben eine Art Datenstruktur, die in einer Art Datenbank beibehalten wird. Nennen wir diese Datenstruktur der Einfachheit halber Person. Sie müssen nun eine CRUD-API entwerfen, mit der andere Anwendungen Persons erstellen, lesen, aktualisieren und löschen können . Nehmen wir zur Vereinfachung an, dass auf diese API über eine Art Webdienst zugegriffen wird.

Für die Teile C, R und D von CRUD ist das Design einfach. Ich werde eine C # -ähnliche Funktionsnotation verwenden - die Implementierung könnte SOAP, REST / JSON oder etwas anderes sein:

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

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

Was ist mit Update? Das Natürlichste wäre

void UpdatePerson(Identifier, Person);

aber wie würden Sie angeben, welche Felder Personaktualisiert werden sollen?


Lösungen, die ich finden könnte:

  • Sie können immer verlangen, dass eine vollständige Person übergeben wird, dh der Kunde würde so etwas tun, um das Geburtsdatum zu aktualisieren:

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

    Dies würde jedoch eine Art Transaktionskonsistenz oder eine Sperrung zwischen Get und Update erfordern. Andernfalls können Sie eine andere Änderung überschreiben, die parallel von einem anderen Client vorgenommen wurde. Dies würde die API viel komplizierter machen. Darüber hinaus ist es fehleranfällig, da der folgende Pseudocode (unter der Annahme einer Client-Sprache mit JSON-Unterstützung)

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

    - was richtig aussieht - würde nicht nur DateOfBirth ändern, sondern auch alle anderen Felder auf null zurücksetzen.

  • Sie können alle Felder ignorieren null. Wie würden Sie dann einen Unterschied machen, ob Sie es nicht ändern DateOfBirth oder absichtlich auf null ändern ?

  • Ändern Sie die Signatur in void UpdatePerson(Identifier, Person, ListOfFieldNamesToUpdate).

  • Ändern Sie die Signatur in void UpdatePerson(Identifier, ListOfFieldValuePairs).

  • Verwenden Sie eine Funktion des Übertragungsprotokolls: Sie können beispielsweise alle Felder ignorieren, die nicht in der JSON-Darstellung der Person enthalten sind. Dies erfordert jedoch normalerweise, dass Sie den JSON selbst analysieren und die integrierten Funktionen Ihrer Bibliothek (z. B. WCF) nicht verwenden können.

Keine der Lösungen erscheint mir wirklich elegant. Dies ist sicherlich ein häufiges Problem. Was ist also die Best-Practice-Lösung, die von allen verwendet wird?


Warum ist der Identifikator nicht Teil der Person? PersonLassen Sie für neu erstellte Instanzen, die noch nicht persistiert sind, und für den Fall, dass der Bezeichner als Teil des Persistenzmechanismus festgelegt wird, ihn einfach auf null. Für die Antwort verwendet JPA eine Versionsnummer. Wenn Sie Version 23 lesen und das Element aktualisieren, wenn die Version in DB 24 ist, schlägt der Schreibvorgang fehl.
SJuan76

Erlaube und kommuniziere sowohl PUTals auch PATCHMethoden. Bei Verwendung PATCHsollten nur Sendeschlüssel ersetzt werden, wobei PUTdas gesamte Objekt ersetzt wird.
Lode

Antworten:


8

Wenn Sie keine Nachverfolgung von Änderungen als Anforderung für dieses Objekt haben (z. B. "Benutzer John hat Name und Geburtsdatum geändert"), ist es am einfachsten, das gesamte Objekt in der Datenbank mit einem Objekt zu überschreiben, das Sie vom Verbraucher erhalten. Dieser Ansatz würde etwas mehr Daten beinhalten, die über Kabel gesendet werden, aber Sie vermeiden das Lesen vor dem Update.

Wenn Sie eine Aktivitätsverfolgungsanforderung haben. Ihre Welt ist viel komplizierter und Sie müssen festlegen, wie Informationen über CRUD-Aktionen gespeichert und abgefangen werden sollen. Das ist die Welt, in die du nicht eintauchen willst, wenn du keine solchen Anforderungen hast.

In Bezug auf das Überschreiben von Werten durch separate Transaktionen würde ich vorschlagen, nach optimistischen und pessimistischen Sperren zu suchen . Sie mildern dieses häufige Szenario:

  1. Objekt wird von Benutzer1 gelesen
  2. Objekt wird von Benutzer2 gelesen
  3. Von user1 geschriebenes Objekt
  4. Von Benutzer2 geschriebenes Objekt und von Benutzer1 überschriebene Änderungen

Jeder Benutzer hat eine andere Transaktion, daher Standard-SQL. Am häufigsten ist optimistisches Sperren (auch von @ SJuan76 im Kommentar zu Versionen erwähnt). Ihre Version Ihr Datensatz in der Datenbank und während des Schreibens werfen Sie zuerst einen Blick in die Datenbank, wenn die Versionen übereinstimmen. Wenn die Versionen nicht übereinstimmen, wissen Sie, dass in der Zwischenzeit jemand das Objekt aktualisiert hat, und Sie müssen dem Verbraucher eine Fehlermeldung zu dieser Situation senden. Ja, Sie müssen diese Situation dem Benutzer zeigen.

Beachten Sie, dass Sie den tatsächlichen Datensatz vor dem Schreiben aus der Datenbank lesen müssen (für einen optimistischen Vergleich der Sperrversionen), sodass für die Implementierung der Delta-Logik (nur geänderte Werte schreiben) möglicherweise keine zusätzliche Leseabfrage vor dem Schreiben erforderlich ist.

Die Einführung der Delta-Logik hängt in hohem Maße vom Vertrag mit dem Verbraucher ab. Beachten Sie jedoch, dass es für den Verbraucher am einfachsten ist, die volle Nutzlast anstelle von Delta zu erstellen.


2

Wir haben eine PHP-API in Arbeit. Wenn für Aktualisierungen kein Feld im JSON-Objekt gesendet wird, wird es auf NULL gesetzt. Dann wird alles an die gespeicherte Prozedur übergeben. Die gespeicherte Prozedur versucht, jedes Feld mit field = IFNULL (Eingabe, Feld) zu aktualisieren. Wenn sich also nur 1 Feld im JSON-Objekt befindet, wird nur dieses Feld aktualisiert. Um ein festgelegtes Feld explizit zu leeren, muss field = '' vorhanden sein. Die Datenbank aktualisiert das Feld dann entweder mit einer leeren Zeichenfolge oder mit dem Standardwert dieser Spalte.


3
Wie setzen Sie absichtlich ein Feld auf null, das noch nicht null ist?
Robert Harvey

Alle Felder sind NICHT NULL gesetzt, daher erhalten die CHAR-Felder standardmäßig '' und alle ganzzahligen Felder 0.
Jared Bernacchi

1

Geben Sie die Liste der aktualisierten Felder in der Abfragezeichenfolge an.

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

Implementieren Sie das Zusammenführen gespeicherter Daten mit dem Modell aus dem Anforderungshauptteil:

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;
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.