Verschillende REST-representaties van dezelfde bron

Mijn applicatie heeft een bron op /foo. Normaal gesproken wordt het weergegeven door een HTTP-antwoordlading zoals deze:

{"a": "some text", "b": "some text", "c": "some text", "d": "some text"}

De client heeft niet altijd alle vier leden van dit object nodig. Wat is de RESTfully semantischemanier waarop de client de server kan vertellen wat hij nodig heeft in de representatie? bijv. als het wil:

{"a": "some text", "b": "some text", "d": "some text"}

Hoe moet het het GET? Enkele mogelijkheden (ik zoek correctie als ik REST verkeerd begrijp):

  • GET /foo?sections=a,b,d.
    • De zoekreeks (uiteindelijk een query-reeks genoemd) lijkt te betekenen “zoek bronnen die aan deze voorwaarde voldoen en vertel me erover”, niet “representeer deze bron aan mij volgens deze aanpassing”.
  • GET /foo/a+b+dMijn favoriete als REST-semantiek dit probleem niet dekt, vanwege zijn eenvoud.
    • Breekt URI-ondoorzichtigheid en schendt HATEOAS.
    • Het lijkt het onderscheid tussen bron (de enige betekenis van een URI is het identificeren van één bron) en representatie te doorbreken. Maar dat is discutabel omdat het consistent is met /widgetsdie een presenteerbare lijst van /widget/<id>bronnen vertegenwoordigen, waar ik nooit een probleem mee heb gehad.
  • Maak mijn beperkingen los, reageer op GET /foo/a, enz., en laat de klant een verzoek doen per onderdeel van /foodat hij wil.
    • Vermenigvuldigt de overhead, wat een nachtmerrie kan worden als /foohonderden componenten heeft en de klant er 100 nodig heeft.
    • Als ik een HTML-weergave van /foowil ondersteunen, moet ik Ajax gebruiken, wat problematisch is als ik slechts één enkele HTML-pagina wil die kan worden gecrawld, weergegeven door minimalistische browsers, enz. .
    • Om HATEOAS te onderhouden, moeten er ook links naar die “subbronnen” bestaan ​​binnen andere representaties, waarschijnlijk in /foo: {"a": {"url": "/foo/a", "content": "some text"}, ...}
  • GET /foo, Content-Type: application/jsonen {"sections": ["a","b","d"]}in de hoofdtekst van het verzoek.
    • Bladwijzer ongedaan te maken en niet te cachen.
    • HTTP definieert geen body-semantiek voor GET. Het is legaal HTTP, maar hoe kan ik garanderen dat de proxy van een gebruiker de body niet verwijdert van een GET-verzoek?
    • Mijn REST-clientlaat me geen body plaatsen op een GET-verzoek, dus ik kan dat niet gebruiken om te testen.
  • Een aangepaste HTTP-header: Sections-Needed: a,b,d
    • Ik vermijd liever aangepaste headers indien mogelijk.
    • Bladwijzer ongedaan te maken en niet te cachen.
  • POST /foo/requests, Content-Type: application/jsonen {"sections": ["a","b","d"]}in de hoofdtekst van het verzoek. Ontvang een 201met Location: /foo/requests/1. Dan GET /foo/requests/1om de gewenste weergave van /foote ontvangen
    • Onhandig; vereist heen en weer en wat vreemd uitziende code.
    • Niet te bookmarken en niet te cachen aangezien /foo/requests/1slechts een alias is die maar één keer wordt gebruikt en alleen wordt bewaard totdat het wordt aangevraagd.

Antwoord 1, autoriteit 100%

Ik zou de querystring-oplossing aanraden (je eerste). Je argumenten tegen de andere alternatieven zijn goede argumenten (en die ik in de praktijk ben tegengekomen bij het oplossen van hetzelfde probleem). In het bijzonder de “los de beperkingen/reageer op foo/a” oplossing kanwerken in beperkte gevallen, maar introduceert veel complexiteit in een API van zowel implementatie als consumptie en is naar mijn ervaring niet de moeite waard geweest.

Ik zal uw “lijkt te betekenen”-argument zwak tegengaan met een veelvoorkomend voorbeeld: overweeg de bron die een grote lijst met objecten is (GET /Customers). Het is volkomen redelijk om deze objecten te pagen, en het is gebruikelijk om de querystring te gebruiken om dat te doen: GET /Customers?offset=100&take=50als voorbeeld. In dit geval filtert de querystring op geen enkele eigenschap van het weergegeven object, maar levert het parameters voor een subweergave van het object.

Concreet zou ik zeggen dat je consistentie en HATEOAS kunt behouden door middel van deze criteria voor het gebruik van de querystring:

  • het geretourneerde object moet dezelfde entiteit zijn als het geretourneerde object zonder de querystring.
  • de Uri zonder de querystring zou het volledige object moeten retourneren – een superset van elke beschikbare view meteen querystring op dezelfde Uri. Dus als je het resultaat van de ongedecoreerde Uri in de cache opslaat, weet je dat je de volledige entiteit hebt.
  • het resultaat dat voor een gegeven querystring wordt geretourneerd, moet deterministisch zijn, zodat Uris met querystrings gemakkelijk in de cache kan worden opgeslagen

Wat u echter moet teruggevenvoor deze Uris kan soms complexere vragen opleveren:

  • het retourneren van een ander entiteitstype voor Uris die alleen verschilt door querystring kan ongewenst zijn (/foois een entiteit, maar foo/ais een string); het alternatief is om een ​​gedeeltelijk bevolkte entiteit terug te geven
  • als u welverschillende entiteitstypes gebruikt voor subquery’s, dan, als uw /foogeen aheeft, een 404status is misleidend (/foobestaat!), maar een lege reactie kan even verwarrend zijn
  • het retourneren van een gedeeltelijk bevolkte entiteit kan onwenselijk zijn, maar het retourneren van een deel van een entiteit is misschien niet mogelijk, of kan verwarrender zijn
  • het retourneren van een gedeeltelijk bevolkte entiteit is misschien niet mogelijk als je een sterk schema hebt (als averplicht is, maar de client alleen bverzoekt, ben je gedwongen om een ​​van beide te retourneren een ongewenste waarde voor aof een ongeldig object)

In het verleden heb ik geprobeerd dit op te lossen door specifieke benoemde “views” van vereiste entiteiten te definiëren en een querystring toe te staan ​​zoals ?view=summaryof ?view=totalsOnly– beperking van het aantal permutaties. Dit maakt het ook mogelijk om een ​​subset van de entiteit te definiëren die “logisch” is voor de consument van de service, en kan worden gedocumenteerd.

Uiteindelijk denk ik dat dit vooral neerkomt op een kwestie van consistentie: je kunt relatief gemakkelijk aan HATEOAS-richtlijnen voldoen met behulp van de querystring, maar de keuzes die je maakt, moeten consistent zijn in je hele API en, zou ik zeggen, goed gedocumenteerd.


Antwoord 2, autoriteit 57%

Ik heb het volgende besloten:

Ondersteunt enkele combinaties van leden: ik verzin een naam voor elke combinatie. bijv. als een artikel leden heeft voor auteur, datum en hoofdtekst, zal /article/some-slugalles retourneren en /article/some-slug/metazal gewoon terugkeren de auteur en datum.

Ondersteunt veel combinaties:ik scheid de namen van leden door koppeltekens: /foo/a-b-c.

Hoe dan ook, ik zal een 404retourneren als de combinatie niet wordt ondersteund.

Architectonische beperking

RUST

Hulpbronnen identificeren

Van de definitievan REST:

een hulpbron Ris een tijdelijk variërende lidmaatschapsfunctie MR(t), die voor tijd < i>tverwijst naar een reeks entiteiten, of waarden, die equivalent zijn. De waarden in de set kunnen resourcerepresentaties en/of resource-ID’s zijn.

Een representatie is een HTTP-body en een identifier is een URL.

Dit is cruciaal. Een identifier is slechts een waarde die is gekoppeld aan andere identifiers en representaties. Dat is iets anders dan de identifier→representation mapping. De server kan elke gewenste identifier toewijzen aan elke representatie, zolang beide zijn gekoppeld aan dezelfde bron.

Het is aan de ontwikkelaar om resourcedefinities te bedenken die het bedrijf redelijk beschrijven door te denken aan categorieën als ‘gebruikers’ en ‘posts’.

HATEOAS

Als ik echt om perfecte HATEOAS geef, zou ik ergens in de /foo-weergave een hyperlink kunnen plaatsen naar /foo/members, en die weergave zou gewoon een hyperlink bevatten aan elke ondersteunde combinatie van leden.

HTTP

Van de definitievan een URL:

De querycomponent bevat niet-hiërarchische gegevens die, samen met gegevens in de padcomponent, dienen om een ​​bron te identificeren binnen het bereik van het URI-schema en de naamgevingsautoriteit (indien aanwezig).

Dus /foo?sections=a,b,den /foo?sections=bzijn verschillende identifiers. Maar ze kunnen geassocieerdzijn binnen dezelfde bron terwijl ze toegewezenzijn aan verschillende representaties.

HTTP’s 404-code betekentdat de server niets kon vinden om de URL aan toe te wijzen, niet dat de URL niet aan een bron is gekoppeld.

Functionaliteit

Geen enkele browser of cache zal ooit problemen hebben met schuine strepen of koppeltekens.


Antwoord 3, autoriteit 43%

Eigenlijk hangt het af van de functionaliteit van de bron.
Als de resource bijvoorbeeld een entiteit vertegenwoordigt:

/customers/5

Hier staat de ‘5’ voor een idvan de klant

Reactie:

{
   "id": 5,
   "name": "John",
   "surename": "Doe",
   "marital_status": "single",
   "sex": "male",
   ...
}

Dus als we het nauwkeurig onderzoeken, vertegenwoordigt elke json-eigenschap in feite een veldvan het record op de instantie van de klantbron.
Laten we aannemen dat de consument een gedeeltelijk antwoord wil, dat wil zeggen een deel van de velden. We kunnen ernaar kijken alsof de consument de mogelijkheid wil hebben om via het verzoek de verschillende velden te selecteren die voor hem interessant zijn, maar niet meer (om verkeer of prestaties te besparen, als een deel van de velden moeilijk te berekenen is) .

Ik denk dat in deze situatie de meest leesbare en correcte API zou zijn (krijg bijvoorbeeld alleen nameen surename)

/customers/5?fields=name,surename

Reactie:

{
   "name": "John",
   "surename": "Doe"
}

HTTP/1.1

  • als illegale veldnaam wordt gevraagd – 404 (Niet gevonden) wordt geretourneerd
  • Als verschillende veldnamen worden gevraagd, worden er verschillende antwoorden gegenereerd, wat ook overeenkomt met de caching.
  • Nadelen: als dezelfde velden worden gevraagd, maar de volgorde tussen de velden verschilt (zeg: fields=id,nameof fields=name,id), hoewel het antwoord hetzelfde is, worden die antwoorden afzonderlijk in de cache opgeslagen.

HATEOAS

  • Naar mijn mening is pure HATEOAS niet geschikt om dit specifieke probleem op te lossen. Want om dat te bereiken, heb je een aparte bron nodig voor elke permutatie van veldcombinaties, wat overdreven is, omdat het de API enorm opblaast (stel dat je 8 velden in een bron hebt, je hebt permutaties!).
  • als u resources alleen modelleert voor de veldenmaar niet voor alle permutaties, heeft dit gevolgen voor de prestaties, b.v. u het aantal retourvluchten tot een minimum wilt beperken.

Antwoord 4, autoriteit 14%

Als a,b,c eigendom zijn van een resource, zoals admin, is de juiste manier om te gebruiken de eerste manier die je hebt voorgesteld GET /foo?sections=a,b,domdat je in dit geval een filter zou toepassen op de foocollectie. Anders, als a, b en c een enkele bron zijn van de fooverzameling, zou de volgende manier zijn om een ​​reeks GET-verzoeken uit te voeren /foo/a /foo/b /foo/c. Deze benadering heeft, zoals u zei, een hoge payload voor verzoeken, maar het is de juiste manier om de benadering Restfull te volgen. Ik zou het tweede voorstel van jou niet gebruiken omdat plus char in een url een speciale betekenis heeft.

Een ander voorstel is om het gebruik van GET en POST te staken en een actie te maken voor de foo-verzameling, zoals: /foo/filterof /foo/selectionof een werkwoord dat een actie op de verzameling vertegenwoordigt. Op deze manier kunt u, met een hoofdtekst van een postverzoek, een json-lijst doorgeven van de bron die u zou willen.


Antwoord 5, autoriteit 14%

u zou een mediatype van een tweede leverancier kunnen gebruiken in de aanvraagheader application/vnd.com.mycompany.resource.rep2, u kunt hier geen bladwijzer voor maken, maar queryparameters kunnen niet in de cache worden opgeslagen (/foo?sections=a, b,c) je zou naar matrix-parameters kunnen kijken, maar met betrekking tot deze vraag moeten ze in de cache kunnen worden opgeslagen URL-matrixparameters versus verzoekparameters

Other episodes