I denne artikkelen skal vi se på hva OpenAPI er for noe og hvordan det kan hjelpe deg med å skrive dokumentasjon på en bedre måte, som er mer integrert i utviklingsprosessen.
Da kan det være lurt å starte med å se på hvorfor dokumentasjon kan være vanskelig og hvilke problemer som kan oppstå.
Nedprioritert dokumentasjon
Dokumentasjon er ofte noe man legger til etter at koden har blitt skrevet, en ettertanke som verken direkte hjelper utviklerne med å levere et produkt eller businessen med å tjene penger.
Nedprioritering av dokumentasjon, ofte ubevisst, kan føre til at den faller bak kodebasen og blir utdatert og misvisende etter hvert som koden videreutvikles. Man kan prøve å løse dette ved å be utviklerne om å være mer nøye på dette og innføre strenge prosedyrer, men dette kan vise seg å være vanskelig og ressurskrevende.
Anna (27): - Oversiktlig versjonshistorikk varmer meg langt inn i hjerterota
Hvordan kan man da løse dette på en mer smidig måte? Hva om koden og dokumentasjonen kunne vært koblet sammen, slik at endringer i den ene krever endringer i den andre for å bygge prosjektet?
Dette og mye annet kan oppnås for HTTP-baserte API-er ved hjelp av noe som heter OpenAPI. OpenAPI er nemlig en standardisert måte å skrive API dokumentasjon på som er både leselig for mennesker og, ikke minst, datamaskiner. At det er leselig for datamaskiner åpner opp for mange type verktøy som ellers ikke hadde vært mulig.
I denne artikkelen skal jeg hjelpe deg med å få en forståelse av OpenAPI, hva det er og hvordan det fungerer, og forhåpentligvis gi deg en idé av hvorfor du burde vurdere å utnytte OpenAPI i ditt neste eller nåværende prosjekt.
OpenAPI "Hello World"
Før vi ser på hva man kan få ut av å bruke OpenAPI-spesifikasjonen for å dokumentere dine APIer skal vi ta en kort intro til hvordan en OpenAPI fil ser ut.
En OpenAPI-fil kan skrives i JSON eller YAML (vi kommer til å bruke YAML i denne artikkelen, men attributt-navnene ville vært de samme i JSON).
Første linje definerer hvilken versjon av spesifikasjonen som brukes gjennom openapi attributten:
openapi: 3.0.0
Likevel vil du i eldre dokumenter kunne finne at versjonen settes med en attributt som heter swagger. Dette er fordi spesifikasjonen endret navn da den gikk fra versjon 2 til versjon 3. Navneendringen ble gjort for å understreke at spesifikasjonen gikk fra å være en del av en suite med verktøy utviklet av selskapet Smartbear til å være et separat open source prosjekt utviklet av Linux Foundation.
På info settes generell info om APIet slik som versjonsnummer, tittel, description og mer:
info:
title: Small OpenAPI example
version: 1.0.0
Men det viktigste ligger i paths - det er her man beskriver endepunktene til APIet inklusivt parametre, retur verdier og mulige statuskoder:
paths:
/message:
get:
responses:
200:
description: Success
content:
application/json:
schema:
type: object
properties:
id:
type: string
format: uuid
message:
type: string
example: "Hello World"
tags:
- Message
summary: Returns a message object
Vi definerer her ett endepunkt: en GET til /message. For å holde eksemplet kort definerer vi så bare én mulig respons: 200, med data av typen application/json. Så beskriver vi dataen som et objekt med to attributter: id og message.
Legger vi alt sammen får vi da:
openapi: 3.0.0
info:
title: Small OpenAPI example
version: 1.0.0
paths:
/message:
get:
responses:
200:
description: Success
content:
application/json:
schema:
type: object
properties:
id:
type: string
format: uuid
message:
type: string
example: "Hello World"
tags:
- Message
summary: Returns a message object
Dette er bare en liten del av hvordan du kan beskrive APIer med OpenAPI. Vil du vite mer, for eksempel hvordan man beskriver parametre, så er den offisielle dokumentasjonen god:
https://swagger.io/docs/specification/basic-structure/
Hanne håper vi blir flinkere på dokumentasjon
Swagger UI
Nå som vi har skrevet vår første API spesifikasjon kan vi begynne å ta i bruk ulike verktøy. OpenAPI baserte verktøy vil kunne hjelpe oss med alt fra dokumentasjon og utvikling til å ta APIet i bruk og holde kode og dokumentasjon synkronisert.
Det kanskje vanligste å gjøre med OpenAPI er å generere en lett-leselig dokumentasjonsside i HTML for de som skal bruke APIet. Dette krever lite oppsett og gir en god oversikt uten at det påvirker hvordan selve API koden skrives.
Ett av verktøyene som gjør dette heter Swagger UI, og som du kanskje gjettet er denne utviklet av selskapet som utviklet OpenAPI spesifikasjonen før det het OpenAPI. De har også en nettbasert editor som du finner på editor.swagger.io. Der kan du skrive OpenAPI kode og få se hvordan endringer i koden påvirker den genererte siden i sanntid.
Bruker vi Swagger UI til å generere dokumentasjon for koden vår vil vi få en html side som ser slik ut:
Dette er fint, men vil du gjøre det enda lettere for klienten, kan du ta steget videre og direkte generere klientkode eller SDKer som klientene kan bruke.
Swagger Codegen
Swagger Codegen kan generere klientkode for over 40 språk og for noen språk har den også bibliotek-spesifikke generatorer (for eksempel for JavaScript's Axios).
Dersom Swagger Codegen ikke har det du trenger så finner du kanskje noe under listen av SDK generatorer på openapi.tools.
Her må man gjøre litt research på hva som er tilgjengelig for den teknologien man bruker, og hvordan den spesifikke generatoren fungerer, men det vil også kunne ta bort mye av usikkerheten ved å integrere mot et api ved å forenkle dette til funksjonskall med statiske typer.
- Forstår de ikke koden din, er den for dårlig
Generert dokumentasjon
I starten sa jeg at OpenAPI kan hjelpe med å holde dokumentasjon og kode synkronisert. Dette kan oppnås på (minst) to måter.
Den første metoden er kanskje den letteste - eller i det minste mest "hands-off". Metoden går nemlig ut på å bruke et verktøy som analyserer koden din og genererer OpenAPI dokumentasjon basert på det. Du slipper da mye av arbeidet rundt å dokumentere ditt API.
En negativ side du likevel bør være bevist på er at du gir du opp direkte kontroll av OpenAPI filen til et verktøy og må da jobbe gjennom verktøyets grensesnitt dersom du har lyst til å endre noe i den genererte dokumentasjonen eller legge til "metadata" slik som titler og beskrivelser.
Generert kode
Den andre metoden går ut på å generere kode basert på API spesifikasjonen, som deretter brukes i implementasjonen av APIet. Dette kan være for eksempel interfacer for API kontrollere eller dataobjekter som brukes i koden.
Jeg satte opp et eksempel prosjekt med Kotlin og Spring boot med (en litt modifisert versjon av) OpenAPI koden vi skrev over, og den genererte koden ser slik ut:
// Message.java
public class Message {
@JsonProperty("id")
private UUID id;
@JsonProperty("message")
private String message;
/* ... Getter og setter metoder for id og message ... */
public boolean equals(Object o) { /*...*/ }
public int hashCode() { /*...*/ }
public String toString() { /*...*/ }
}
// MessageApi.java
public interface MessageApi {
/* ... Spring boot annoteringer ... */
default ResponseEntity<Message> messageGet() {
/* ... default implementasjon ... */
}
}
Og jeg implementerer API spesifikasjonen slik:
// ApiController.kt
@RestController
class ApiController: MessageApi {
/* ... */
@CrossOrigin
override fun messageGet(): ResponseEntity<Message> {
val id = UUID.randomUUID()
val message = getRandomMessage()
val messageObject = Message().id(id).message(message)
return ResponseEntity.of(Optional.of(messageObject))
}
}
På denne måten vil koden avhenge av API spesifikasjonen. I statisk typede språk (slik som Java/Kotlin og TypeScript) vil man få compile-time feilmeldinger dersom kode og dokumentasjon er usynkronisert. Samtidig har du full kontroll over hvordan dokumentasjonen ser ut, og kan lettere bruke andre OpenAPI baserte verktøy.
Denne måten å utvikle et API på fungerer spesielt bra hvis du liker å først designe APIet for så å implementere dette designet (såkalt design-first utvikling).
Nå håper jeg at du ble litt klokere på OpenAPI: hva det er for noe, og hvorfor du kunne vurdert å bruke det. Er du mer nysgjerrig, så anbefaler jeg å laste ned eksempel prosjektet og se om du får til å endre API endepunktet eller legge til et nytt.