Hoppa till innehåll

Svårdefinierade resurser och representationer

Resurser är grundläggande för att skapa ett RESTful API. Det är resurserna som manipuleras med hjälp av standardiserade metoder (HTTP-verben). I de flesta fall är det relativt enkelt att skapa resurser genom att utgå från den information som ska hanteras i domänen.

I vissa fall är det inte lika tydligt vad som är en resurs och vad som är dess representation. Exempelvis kan de metoder som finns tillgängliga i HTTP-protokollet rimma illa med den metod man vill använda. Typiska situationer där problem uppstår är där det finns många underliggande metoder för många resurser (i till exempel rena beräkningsmetoder eller tjänster som stödjer komplexa verksamhetsprocesser). Hur hanterar jag metoder där resursen eller dess representation är svår att definiera?

Användning av resurser och http-verb

Roy Fieldings doktorsavhandling som är grunden för REST, beskriver en resurs på följande sätt:

The key abstraction of information in REST is a resource. Any information that can be named can be a resource: a document or image, a temporal service (e.g. "today's weather in Los Angeles"), a collection of other resources, a non-virtual object (e.g. a person), and so on. In other words, any concept that might be the target of an author's hypertext reference must fit within the definition of a resource. A resource is a conceptual mapping to a set of entities, not the entity that corresponds to the mapping at any particular point in time. Roy Fieldings

Resurser utgör kärnan i varje REST API. Resursidentifierare (URI), resursrepresentationer och interaktioner med hjälp av metoder (HTTP-metoder) är alla uppbyggda kring begreppet resurser. Det är mycket viktigt att välja rätt resurser och modellera resurserna med rätt detaljnivå vid utveckling av ett REST API:et så att API-konsumenterna får önskad funktionalitet och så att API:et beter sig korrekt och går att underhålla under dess livscykel.

Utgångspunkten i valet av resurser är att analysera domänen och extrahera de begrepp som är relevanta. Fokus bör läggas på konsumenternas behov och hur man gör API:et relevant och användbart. Begreppen beskrivs med hjälp av substantiv. Exempel: i en bankdomän kan man identifiera substantiven kund, adress, konto. Dessa substantiv är kandidater till att vara resurser. När substantiven (resurserna) har identifierats, kan interaktionerna med API:t modelleras som HTTP-verb mot dessa substantiv. Om de interaktioner eller metoder som HTTP-verben ger inte riktigt passar in kan man approximera.

Exempel med substantivet konto: här kan man utföra interaktionerna "öppna" (öppna ett konto), "stäng" (stäng ett konto), "sätta in" (sätta in pengar på ett konto), "ta ut" (ta ut pengar från ett konto). Interaktioner beskrivs i regel som verb. Dessa verb kan mappas till HTTP-verb. Till exempel kan en API-konsument "öppna" ett konto genom att skapa en instans av "konto"-resurs med hjälp av HTTP POST-metoden. På liknande sätt kan en API-konsument "stänga" ett konto genom att använda metoden HTTP DELETE. En API-konsument kan "ta ut" eller "sätta in" pengar med HTTP "PUT" / "PATCH" / "POST" metoder.

Detta förenklade tillvägagångssätt fungerar på en abstrakt nivå, men det är svårt att modellera en mer komplicerad domän i praktiken. Så småningom stöter man på interaktioner som är svåra att anpassas till HTTP-verben eller situationer där interaktionen saknar en naturlig resurs eller representation. Nedan följer tre exempel som illustrerar de situationen som kan uppstå samt hur de kan hanteras.

Interaktioner som inte enkelt kan anpassas till HTTP-verben

Om man hamnar i en situation där de vanliga HTTP-verbens (CRUD-operationerna) inte räcker för de interaktioner som man vill utföra kan det hanteras på ett antal olika sätt. Nedan följer två exempel

  1. Omstrukturera interaktionen så att den ser ut som ett fält av en resurs. Detta fungerar om metoden inte tar några parametrar. Till exempel kan en metod aktivera mappas till ett booleskt fält aktiverad i en resurs och uppdateras via en PATCH till resursen.
  2. Behandla det som en underresurs med RESTful-principer. Ett problem som kan uppstå med lösningen i punkt 1 är att interaktionen aktivera ofta följs av fler interaktioner, te.x. pausa och avsluta. En bättre lösning i detta fall är att skapa en underresurs status som kan uppdateras med diverse olika statusar (aktiverad, pausad, avslutad osv).

Funktionen som man vill erbjuda i sitt API saknar en naturlig representation

REST står för Representational State Transfer och hela idén är att det är resursens tillstånd via dess representation som hela tiden utbyts mellan klienten och servern och på det sättet kan man manipulera resursen. Detta är en del av RESTful-principen enhetligt gränssnitt. Vad händer då resursen eller dess representation inte kan hittas på ett enkelt sätt?

Ett typexempel är att man vill skapa ett API för matematiska beräkningar, exempelvis addera eller subtrahera. Visserligen kan man omvandla verbet (addera) till substantivet (adderare) för att skapa en något som är resursliknande på en ytlig nivå men är det relevant att göra GET addition, hur ser representationen ut för adderare? En lösning på detta problem är att använda POST till adderare-resursen där det matematiska uttrycket fungerar som inparameter och resultatet, dvs summa är svaret på förfrågan. Samtidigt avstår man från att implementera GET. Detta blir naturligt för en användare av API:t. I liknande fall som detta, ställs man inför valet att följa RESTful principerna strikt eller tumma på principerna och skapa ett API som är mer naturligt namnsatt. Ofta är det bättre i praktiken att skapa ett naturligt och enkelt API än att krysta fram något bara för att det ska följa principerna.

Implementation av verksamhetstjänster

Det är inte ovanligt att man vill skapa ett API för att stödja en verksamhetsprocess. Ofta modelleras detta på en hög nivå med hjälp av ett antal verksamhetstjänster. Fokus för en verksamhetstjänst är funktionaliteten snarare än data i sig. Det är inte ovanligt att en verksamhetstjänst använder flera informationsobjekt samtidigt och har flera underliggande tjänster. Verksamhetstjänsterna är därför ofta grovkorniga, få men innehållsrika och komplexa, till skillnad mot finkorniga som är många och opererar på en lägre nivå direkt mot data via CRUD-funktioner. Det är heller inte ovanligt att verksamhetstjänster har ett behov av att uppdatera många datamängder i en transaktion för att data inte ska hamna i ett otillåtet tillstånd, en process kan t.ex. inte vara pågående samtidigt som den är avslutad. Ett finkornigt API med enkla datamängder och enkla CRUD-operationer lämpar sig väl för REST-arkitekturen, men det är inte alltid problemfritt med finkorniga API:er. Ett API som är finkornigt lämnar mycket av implementation av verksamhetslogiken åt konsumenten. Konsumenten behöver därför ha god kunskap om verksamhetslogiken, i vilken ordning datamängder ska uppdateras samt kunna hantera felsituationer där en verksamhetstransaktion endast delvis lyckas. Detta kan ske genom att antingen göra omförsök av de delar som inte lyckats eller backa transaktionen. I dessa situationer är det troligen bättre att låta servern (via ett API) hantera den komplexa verksamhetslogiken.

Ett fiktivt exempel kan vara att skriva ut en patient från ett sjukhus. I denna transaktion ska en notering göras i patientjournalen och förberedelser för betalning genomföras, t.ex. uppdatera högkostnadsskyddet samt generera en faktura till folkbokföringsadressen. Verksamhetstjänsten heter följaktligen skrivaUtPatient och genomför långt mer än bara en uppdatering av representation av data. Det första man bör göra är att forma om verbet “skriva ut” till ett substantiv för att det ska likna en resurs mer. Det känns konstigt att använda två verb i rad "uppdatera skriva ut". Lyckligtvis kan man relativt enkel forma om verb till substantiv genom att lägga till "-ing" eller "ning" i svenska (motsvarande teknik kan användas även för engelska med exempelvis tillägget "-tion"). I vårat exempel blir det utskrivningAvPatient. Det passar bättre språkligt med "uppdatera utskrivning av patient" även om det inte är perfekt. I den här typen av operationer ska man använda POST. Vidare är det oerhört viktigt att man dokumenterar noggrant vad operationen POST i detta fall gör då det är en mängd olika saker som sker som inte är triviala.

Rekommendationer

Detta var tre olika på olika situationer som kan uppstå då det gäller namngivning av resurser samt hur de kan hanteras. Vid design av ett API är det viktigt att tänka efter hur API:et ska fungera. Ska det vara detaljrikt eller är det bättre att gömma komplex verksamhetslogik bakom få resurser? Det är bra att fundera en extra gång över vad representationen för resursen är. I många fall behöver man approximera både representationen och betydelsen av operationen för att det ska passa HTTP-metoderna. Det viktigaste ur ett pragmatiskt perspektiv är att ett API ska kännas rätt och intuitivt för användaren.