Rest API Design - Mit IDs oder Literal Strings arbeiten?

8

Sollte die API beim Entwerfen eines RESTful-Webdiensts so ausgelegt sein, dass sie die ID für Zeichenfolgen für Werte verwendet, die zwischen dem Server hin und her übertragen werden?

Hier ein Beispiel: Angenommen, ich habe eine Mitarbeiterressource mit Status- und Geschlechtsattributen. In der Datenbank Status und Geschlecht und separate Tabellen und damit separates Domain-Objekt, jedes mit seiner eigenen Kennung.

Angenommen, die Kundenanfrage / Mitarbeiter / 1. Dort könnte der Server so etwas zurückgeben ....

Fall 1:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": {
        "id": 1,
        "gender": "FEMALE"
    },
    "status": {
        "id": 3,
        "status": "FULL_TIME"
    }
}

Fall 2:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": "FEMALE",
    "status": "FULL_TIME"
}

Fall 3:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "genderId": 1,
    "statusId": 3
}

Fall 3 scheint am wenigsten sinnvoll zu sein, da der Client keine Ahnung hat, was genderId 1 ist, es sei denn, er dreht sich um und ruft den Server erneut an, um diese Daten abzurufen.

Angenommen, der Client aktualisiert den Benutzer durch:

PUT /employee/1

Sollte die Anforderungsnutzlast die IDs oder eine Zeichenfolge verwenden? In jedem Fall muss das Back-End sie nachschlagen, um sicherzustellen, dass sie gültig sind. Es ist jedoch besser, mit IDs über Strings zu arbeiten.

jkratz55
quelle

Antworten:

3

Angenommen, ich habe eine Mitarbeiterressource mit Status- und Geschlechtsattributen. In der Datenbank Status und Geschlecht und separate Tabellen und damit separates Domain-Objekt, jedes mit seiner eigenen Kennung.

Ihre API-Darstellungen sollten nicht eng mit Ihren Implementierungsdetails verknüpft sein. Ich würde sogar sagen, dass das Ableiten Ihrer API-Darstellungen aus Ihren Implementierungsdetails genau rückwärts ist.

Denken Sie Adapter Patternan das Buch der Viererbande . Die Nachrichten im Web sind die eines Dokumentenspeichers. Ihr Ziel bei der Erstellung einer API ist es, die Dokumente zu erstellen, die Ihre Kunden wünschen, und sie gleichzeitig von den Details der Erstellung dieser Dokumente zu isolieren.

Die Motivation dafür ist, dass Sie die Implementierungsdetails jederzeit ändern können, wobei Sie sicher sein können, dass Ihre Kunden nicht brechen, solange Sie die von Ihnen zurückgegebenen Darstellungen nicht ändern.

Beachten Sie außerdem, dass eine einzelne logische Ressource möglicherweise viele Darstellungen enthält, von denen nur einige Änderungen unterstützen.

Angenommen, der Client aktualisiert den Benutzer

Mit welcher Vertretung möchten Sie als Verbraucher arbeiten? Ich vermute, dass der nächste ist

{
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": "FEMALE",
    "status": "FULL_TIME"
}

Wenn ich diese Darstellung an einen von Ihnen angegebenen Ort lege, sollten Sie wirklich in der Lage sein, den Rest zu klären.

Wenn Sie Darstellungen für Maschinen erstellen, möchten Sie wahrscheinlich weniger Mehrdeutigkeit in Ihrer Rechtschreibung

{
    "https://schema.org/givenName": "Jane",
    "https://schema.org/familyName": "Doe",
    "active": true,
    "https://schema.org/gender": "https://schema.org/Female",
    "https://schema.org/employmentType": "FULL_TIME"
}

Gleiche logische Ressource, zwei verschiedene Darstellungen. Pferde für Kurse.

VoiceOfUnreason
quelle
1

Sowohl Fall 1 als auch Fall 2 sehen gut aus. Die Auswahl kann durch die Art und Weise vorhergesagt werden, wie Sie Ihr Domain-Modell organisieren.

Sie haben die Mitarbeiter-, Geschlechts- und Statustabellen in der Domäne wiedergegeben (vermutlich mit ORM). Jede dieser Klassen in diesem bestimmten Modell ist eine Entität mit einer eigenen Kennung. Die weitere Darstellung des gesamten Modells über die REST-API sieht logisch aus und passt zu Fall 1.

Alternativ können Sie sich an die DDD-Prinzipien halten, bei denen die Unterschiede zwischen Entitäten und Wertobjekten besonders berücksichtigt werden . Unter diesem Gesichtspunkt ist Mitarbeiter eine Entität (mit ID), und Geschlecht und Status sind möglicherweise gute Kandidaten, um Wertobjekte zu werden (eingebettet in die Mitarbeiterentität; ohne Bezeichner). Dies passt zu Fall 2.

Stimmen Sie voll und ganz zu, dass Fall 3 ein No-Go ist.

Serhii Shushliapin
quelle
1
Wenn es keinen zwingenden Grund gibt, würde ich eine Webdienst-API nicht eng mit einem Datenbankdesign verknüpfen. APIs und Datenbanken haben unterschiedliche Clients mit unterschiedlichen Anforderungen.
Eric Stein
Stimme vollkommen zu. Dies ist nur meine Beobachtung des API-Designs des Autors (Gender and State Expose IDs). Unter Einhaltung der DDD-Prinzipien würde ich sie als Wertobjekte in meinem Domänenmodell entwerfen und daher ihre Bezeichner nicht über die REST-API verfügbar machen (das ist Fall 2).
Serhii Shushliapin
1

Fall 2 ist die einzige echte Option. Sie haben bereits auf die Probleme mit Fall 3 hingewiesen. Fall 1 enthält Informationen, die dem Client der API nicht wichtig sind (z. B. die internen IDs für Status), und erfordert, dass der Client die IDs zum Erstellen einer PUT-Anforderung kennt . Ja, die PUT-Anforderung ist etwas knapper, wenn sie die IDs anstelle der vollständigen Zeichenfolgen verwenden kann. Der Client weiß jedoch, dass er "FULL_TIME" oder "PART_TIME" angibt, und nicht, dass er zufällig beliebige IDs in Ihrer Datenbank hat .

Natürlich können Sie die IDs in Ihrer API-Dokumentation dokumentieren, aber es ist genauso einfach, die gültigen Werte zu dokumentieren, die die Zeichenfolgen zulassen dürfen, und wahrscheinlich klarer.

David Conrad
quelle
2
Sie sollten beachten, dass dies bedeutet, dass ein Geschlecht oder Status niemals umbenannt werden kann. Wenn Clients nur mit dem Namen arbeiten, ist der Name effektiv die eindeutige Kennung. Wenn Clients eine ID als eindeutige Kennung verwenden müssen, ist dies eine wichtige Änderung.
Eric Stein
0

Aufgezählte Daten, wie Sie sie hier haben, können in hohem Maße zwischengespeichert werden. Verwenden Sie Links anstelle von Objekten. Verwenden Sie Caching-Header, damit Clients Geschlechter und Status lokal zwischenspeichern können, beispielsweise 24 Stunden lang. Dann verlässt nur der erste Anruf des Tages den Client-Computer. Sie können das Caching wahrscheinlich auch so konfigurieren, dass Zwischenserver die Informationen speichern können, sodass einige Clientanforderungen nicht einmal auf Ihren Server gelangen.

GET /employees/1
{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": "/genders/1",
    "status": "/statuses/3"
}

// clients populate their dropdown with
GET /genders
[
    {"id":1, "gender":"FEMALE"},
    {"id":2, "gender":"MALE"},
    ...
]

// clients look up an employee's gender with
GET /genders/1
{
    "id": 1,
    "gender": FEMALE
}

Ein Nachteil ist, dass dies /genders/1nicht für Menschen lesbar ist. Sie können stattdessen verwenden /genders/female, aber dann können Sie den Namen eines Geschlechts nie ändern, ohne Kunden zu brechen. Das ist der Kompromiss zwischen synthetischem Schlüssel und natürlichem Schlüssel - Flexibilität und Lesbarkeit.

Möglicherweise möchten Sie auch alle Ihre Wertelisten unter einem gemeinsamen Endpunkt platzieren, z

/lists/genders/1
/lists/statuses/3

Dadurch wird den Kunden klargestellt, dass es sich bei allen um Schlüssel-Wert-Paare handelt, die zu verschiedenen Gruppierungen gehören.

Eric Stein
quelle
0

Ich würde mich für etwas zwischen 1 und 2 entscheiden, aus den Gründen, die David erwähnte:

Sie möchten die ID von Dingen nur dann offenlegen, wenn dies erforderlich ist.

Das Offenlegen der ID kann jedoch zu einem bestimmten Zeitpunkt erforderlich werden. In diesem Fall ist die Abwärtskompatibilität ein Problem. Also würde ich das tun:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": {
        "name": "FEMALE"
    },
    "status": {
        "name": "FULL_TIME"
    }
}

Das hat die gleichen Eigenschaften wie Option 2; Es hat jedoch den Vorteil, dass das spätere Hinzufügen der ID keine BC-Unterbrechung verursacht:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": {
        "id": 1,
        "name": "FEMALE"
    },
    "status": {
        "id": 3,
        "name": "FULL_TIME"
    }
}

Wie Eric in den Kommentaren betont, wird der Entitätsname weiterhin als eindeutiger Bezeichner verwendet. Wenn die ID später eingeführt wird, muss der Name immer noch derselbe bleiben, da ältere Clients darauf codiert haben könnten (oder eher werden ).

Marstato
quelle
Dieser Ansatz führt eine neue Option ein. Mit 2 verschiedenen Ressourcen: die erste zum Abfragen und die zweite zum Erstellen oder Aktualisieren. Es scheint zwar zu viel Code zu sein, erleichtert jedoch die Wartung.
Laiv
@ Laiv: Ich habe das nicht vorgeschlagen; Im Moment würde ich das namezum Abfragen und Aktualisieren verwenden.
Marstato
1
Sie sollten beachten, dass dies bedeutet, dass ein Geschlecht oder Status niemals umbenannt werden kann. Wenn Clients nur mit dem Namen arbeiten, ist der Name effektiv die eindeutige Kennung. Wenn Clients eine idals eindeutige Kennung verwenden müssen, ist dies eine wichtige Änderung.
Eric Stein