Top API's by Top Of Minds Accelerate

API Design

API design – HTTP VERB

När man designar sitt API är det viktigt att tänka på att man använder rätt verb på rätt ställe. Detta kan ibland orsaka viss förvirring och även leda till att API:et används felaktigt och i värsta fall att data försvinner eller förändras på ett icke önskvärt sätt. Nedan ska jag försöka bena ut hur man använder de vanligaste verben. Vi utgår ifrån att vi har ett API som hanterar flygplan och dess säten /airplanes/{airplaneId}/seats och vi låtsas att ett säte kan ha följande attribut

{

”color”:”blue”,
”size”: ”medium”,
”gadgets”:[{”screen”,”usb”,”garbage-bag”,life-jacket}],
”id”: 35

}

GET

Verbet GET används som det låter för att hämta information. Information kan hämtas antingen direkt på en resurs eller på ett id. GET: /airplanes/4567/seats ger tillbaka alla säten som finns i flygplanet med id 4567. Man kan även använda sig av query params för filtrering. Tex GET: /airplanes/4567/seats?color=blue returnerar alla säten som har färgen blå. Lämpliga HTTP – statuskoder för detta kan vara 200 vid träff, 204 om inga blå säten finns. Om man istället gör en GET på /airplanes/4567/seats/35 så skulle man få tillbaka endast ett säte, det med ID 35. Här kan man tänkas få statuskod 200 om det finns eller 404 om resursen inte finns.

POST

POST används för att skapa nya instanser av en resurs. POST: /airplanes/4567/seats skulle alltså skapa ett nytt säte i flygplanet med id 4567. 

{
"color": "blue",
"size": "medium",
"gadgets": [{
"screen",
"usb"
}]
}

En POST med ovanstående body skulle alltså skapa ett nytt säte med blå färg, storlek medium och ett par gadgets. ID attributet skickas inte in utan skapas helst av systemet som tar emot datat. Detta kan med fördel returneras i svaret. Här kan man förvänta sig statuskod 201 Created eller 202 Accepted. Även listor av objekt kan skickas in vid POST och flera nya objekt skapas vid samma anrop.

POST kan ibland användas till mycket annat också som att skicka formulär eller inloggningsinformation men vid apidesign så bör man tänka att det ska användas för att skapa nya objekt.

PUT

PUT görs på ett befintligt objekt och används för att uppdatera ett helt objekt. Om vi gör en put på /airplanes/{airplaneId}/seats/35 så betyder det att vi skriver över det befintliga sätet med ID 35.

Säg att vi gör en PUT på säte 35 och använder nedanstående body

{
"color": "blue",
"size": "medium",
"gadgets": [{
"screen",
"usb"
}]
}

Då betyder det att vi tar bort flytväst och papperskorg från det här sätet. 

Lämpliga OK statuskoder vid PUT är 200 OK eller 204 No content. 

PATCH

PATCH görs också det på ett eller flera befintliga objekt men till skillnad från PUT så betyder det att man bara uppdaterar delar av ett objekt så om vi här gör en put på /airplanes/4567/seats/35 med följande body

{
"color":"red"
}

så betyder det att vi enbart uppdaterar attributet color, resterande attribut förblir intakta.  Statuskoder man kan förvänta sig om det går bra vid PATCH är 200 eller 204. 

DELETE

DELETE tar bort befintliga objekt till exempel så skulle DELETE: /airplanes/4567/seats/35 ta bort säte 35 från flygplanet med id 4567. DELETE: /airplanes/4567/seats skulle ta bort alla säten från flygplanet 4567. Statuskoder man kan använda om det går bra är 200, 202 eller 204,

HEAD

HEAD fungerar ungefär som en GET bara att den inte returnerar någon body. Detta kan till exempel användas om man vill veta om en resurs finns innan man ska uppdatera den. 

OPTIONS

OPTIONS används för att ta reda på vilka kommunikationsval man har. Den returnerar till exempel vika http verb som kan användas på en resurs, vilka headers som är godkända mm. Detta används ofta när javascript körs i browsern och anropar apier som finns på en annan server för att kontrollera Cross Origin Resource Sharing (CORS)

API Design – Bestämma en URI för filtrering av data

När ett API ska designas är ett vanligt use case att hämta information från det bakomliggande systemet. I det enklaste fallet kan vi sätta upp en resurs, med tydligt innehåll, som kan anropas för att hämta alla instanser av resursen.

GET /airplanes

Detta anrop ska då ge en kollektion av instansen airplane, med alla som finns i systemet. Men ofta är man ute efter en specifik instans, eller en delmängd baserat på indata som man är intresserad av. För att kunna fråga efter en specifik instans anger man det i URI som identifierare för resursen.

GET /airplanes/1a2b3c

Om man inte har identifieraren utan vill söka ut ett eller flera svar, går det att göra på olika sätt. Det mest vanliga sättet är att använda query parametrar för att filtrera sina svar, och därmed få ut en delmängd av den kollektion av resursen som finns i system. En tumregel att använda är att om två olika anrop ska returnera samma typ av information, då är det samma resurs som ska anropas. När du designar ditt API bestämmer du vilka fält som ska vara sökbara.

GET /airplanes?manufacturer=Boeing

Vilket ska ge en kollektion av samma informationsobjekt, men enbart med de fall då fältet manufacturer har värdet Boeing. Om man vill ge möjlighet för ytterligare filtrering går det bra att kombinera flera query parametrar.

GET /airplanes?manufacturer=Boeing&propulsion=jet

Alla dessa anrop jobbar med samma resurs, så samma informationsobjekt returneras. Men med hjälp av query paramterar har vi lyckats filtrera ner mängden till att returnera endast de instanser som är relevanta i vårt aktuella fall.

HATEOAS

Akronymen HATEOAS (Hypermedia As The Engine Of Application State) är en del av REST-arkitekturen som syftar till att en klient ska kunna hitta de resurser den behöver i ett API genom hyperlänkar i svaret från servern. Det motsvaras av nivå 3 i Richardsons mogenhetsmodell för REST-API:er (https://en.wikipedia.org/wiki/Richardson_Maturity_Model). 

Som exempel kan vi ta användningsfallet att boka ett säte för en flygresa.

Flygplanets lediga säten kan hämtas via anropet:

GET /flights/8493/seats?status=available

vilket ger svaret:

HTTP/1.1 200 OK

[
  {
    "seat_number": 1,
    "row": 3,
    "links": [
      {
        "rel": "/linkrels/seat/book",
        "url": "/airplanes/8493/seats/rows/3/seatnumbers/1"
      }
    ]
  },
  {
    "seat_number": 2,
    "row": 3,
    "links": [
      {
        "rel": "/linkrels/seat/book",
        "url": "/airplanes/8493/seats/rows/3/seatnumbers/2"
      }
    ]
  }
]

För varje säte får vi förutom information om rad och sätesnummer också information om vilka operationer vi kan utföra. Dessa ges i form av en beskrivning av operationen och en URL som pekar på resursen. I det här fallet får vi veta hur man kan boka sätet.
Om vi väljer att boka sätet:

POST "/flights/8493/seats/rows/3/seatnumbers/2"
{
  "passenger": "John Smith"
}

får vi svaret:

HTTP/1.1 201 Created
{
  "passenger": "John Smith",
  "seat_number": 2,
  "row": 3,
  "links": [
    {
      "rel": "/linkrels/seat/cancel",
      "url": "/flights/8493/seats/rows/3/seatnumbers/2"
    },
    {
      "rel": "self",
      "url": "/flights/8493/seats/rows/3/seatnumbers/2"
    }
  ]
}

I svaret får vi ny information om vad vi kan göra med den bokade platsen. Det är inte fullständig information, båda länkarna pekar på samma URL så klienten måste själv förstå att man avbokar med en DELETE och hämtar information om bokningen via en GET. Hur man anger informationen i rel-attributet är implementationsspecifik och bör dokumenteras för att klienter ska kunna förstå vad den innebär.

En fördel med att använda sig av HATEOAS är att man kan ändra URL-strukturen i ett API utan att klienten behöver påverkas (så länge de baserar sina nästkommande anrop på informationen som anges där). Det är också ett sätt att meddela klienter om ny funktionalitet, förutsatt att de tittar på vad som dyker upp i svaret.

Det finns ingen standard för hur den här typen av hyperlänkar ska utformas, däremot finns det ett antal ansatser, bland annat RFC-5988, RFC-4287 och JSON HAL (Hypertext Application Language).

API Autentisering

Autentisering är processen för att validera att en användare är den den utger sig för att vara.
Detta är första steget i varje säkerhets process.
Det finns en uppsjö av olika metoder för att säkerställa autentisering. Här nedan följer några av de vanligaste.

Lösenord

  • Basic Authentication

Token/Keys

  • JWT
  • OAuth 1,2

Certifikat

  • TLS/mTLS

Basic Authentication

Klienten skickar med användarnamn och lösenord i request headern som används för verifiering.

JWT (JSON Web Token)

Standard för att skapa och signera JSON objekt som kan användas för verifiering.

OAuth

Standard för att ge klienter tokens med vilka de kan tillfälligt använda för verifiering.

TLS/mTLS

Klient och server använder sig av signade certifikat för verifiering

Versionshantering av API’er

Ett naturligt steg inom API-Management bör innefatta versionshantering. Ämnet går ofta ihop med dokumentation och namnstandard men i stora drag brukar man separera versionhantering i integrationsbrytande samt icke-integrationsbrytande ändringar.

Integrationsbrytande versionshantering
Vanligen en major up-version (v1 –> v2)

Exempel på ”integrationsbrott”:

  • Ett byte i anropet / svarets typ (integer –> float)
  • Avveckling av delar av API’et
  • Ett byte i format på svarsdatat

Icke integrationsbrytande versionshantering
Vanligen en minor up-version (v1.0 –> v.1.0.1)

Exempel på ”icke-brott”:

  • Ändring i endpoint
  • Ändring i svarsparametrar

Metoder för versionsändringar

URI versionshantering

URL versionshantering är kanske den ”vanligaste” typen där man på ett enkelt sätt via endpoint kan inkludera version.
Tex:
https://api.topapi.se/api/v1/airplanes/
https://api.topapi.se/api/v2/airplanes/

Fördelen med detta är att man på ett enkelt sätt kan både se och managera version, men man får tänka till vid uppgradering för att inte begå brott!

Alternativa versionshanteringar via relativa url’er samt header (Accept-version) är några alternativ som kanske främst tillämpas där värdet av att hålla den äkta url’en intakt till varje pris! samt 

5/5
0
Antal visningar

Begränsa inte ditt REST API till CRUD

Ibland hamnar man i den obekväma situationen att tvingas gå utanför CRUD ramarna i sin API design. Enkelt beskrivet har man ett behov som inte handlar om att skapa eller uppdatera ett attribut inom resursen. I vårt exempel med flygplan har vi tex följande URI:er för att hämta/skapa/uppdatera en resurs:

  • POST https://api.topapi.se/api/airplanes/
  • GET https://api.topapi.se/api/airplanes/{airplaneId}
  • PUT https://api.topapi.se/api/airplanes/{airplaneId}

Men vad gör vi för att tex starta flygplanet om nu detta inte motsvarar ett attribut i resursen ”airplane”? Tex att starta mitt flygplan och skicka det till himlen. Här behövs någonting annat än vanliga CRUD operationen. Är det helt fel enligt REST guidelines?

”If you finish the demanding, yet satisfying task of reading Roy Fielding’s PhD thesis, which defines the REST architectural style, you will see that it says nothing about limiting our REST services to CRUD operations and it also says nothing about limiting ourselves to the collection pattern…”

Mitt tips är att använda det man kallar för ”controller pattern” där man använder ett verb som beskriver operationen. Ett URI exempel för vårt flygplan som ska skickas iväg blir då:

  • PUT https://api.topapi.se/api/airplanes/{airplaneId}/launchAirplane

Här uppdaterar vi inte resursen utan ”triggar” istället en aktivitet för densamma. Exemplet visar inte vilka parametrar som ev behöver skickas med i operation. 

Ett bra alternativ till att sätta kontroll verbet i URIn nedan. Här gör man det tydligt att det inte är en resurs i URIn som modifieras.

  • PUT https://api.topapi.se/api/airplanes/{airplaneId}?action=launchAirplane
  • PUT https://api.topapi.se/api/airplanes/{airplaneId}?launchAirplane=true