OpenAPI è un progetto nato nel 2015 per volere della società SmartBear Software che fino ad allora aveva sviluppato la specifica Swagger. Il progetto nacque con la sponsorizzazione della Linux Foundation ed annovera tra i suoi membri vendor come: Google, IBM, Microsoft, PayPal, etc. Lo scopo del progetto è quello di promuovere uno standard per la descrizione, produzione, consumo e visualizzazione delle informazioni relative ad un RESTful API Web Service, che sia allo stesso tempo human e machine readable.
La specifica quindi nasce come progetto Swagger ed è probabilmente più nota con questo brand name, per questo motivo c’è ancora molta confusione su quale sia la differenza tra OpenAPI e Swagger, quando utilizzare un nome piuttosto che l’altro e soprattutto su quale sia la differenza tra i due. Semplificando al massimo tale differenza può essere così riassunta:
- OpenAPI: è il nome ufficiale della specifica;
- Swagger: identifica un insieme di tool che implementano ed utilizzano la specifica.
Poiché i tool Swagger sono stati sviluppati dal team coinvolto nella creazione della specifica originale, gli strumenti sono spesso considerati sinonimo della specifiche. Ma gli strumenti Swagger non sono gli unici disponibili per l’implementazione della specifica OpenAPI. Esistono numerose soluzioni per la progettazione, documentazione, test, gestione e monitoraggio di API che supportano la versione 2.0 delle specifiche e che stanno lavorando attivamente per aggiungere il supporto 3.0. Quest’ultima infatti è la prima rilasciata sotto il cappello OpenAPI ed è comunemente conosciuta con l’acronimo OAS 3.0 (OpenAPI Specification 3.0).
Approcci Possibili
La specifica si presta ad essere utilizzata in due modalità diametralmente opposte:
- Approccio top-down o design-first: la specifica è utilizzata per progettare le API prima di aver scritto qualsiasi riga di codice.
- Approccio bottom-up o code-first: è stato già scritto il codice per l’implementazione delle API e si vuole generare la documentare a corredo.
L’approccio code-first è sicuramente quello storicamente più utilizzato, in quanto più semplice perché si possono apportare modifiche ed aggiustamenti mentre si procede nel coding e si adatta perfettamente a un processo di delivery agile. Ma poiché la progettazione è carente potrebbe portare ad una API difficile da capire (per l’utilizzatore) e documentare.
La spinta verso una documentazione chiara e di facile lettura ha reso popolare l’approccio design-first. Non solo più persone possono contribuire nella definizione della documentazione, ma inoltre si traduce spesso in un codice più pulito, perchè si è costretti a pensare in modo più semplice, conciso e facile da seguire.
Struttura di Base
Una definizione OpenAPI può essere espressa attraverso un file YAML o JSON. In questo articolo utilizziamo YAML, ma JSON funzionerebbe ugualmente bene. Il contenuto e la struttura di un file di descrizione OpenAPI è rappresentato nella figura seguente, in cui è anche mostrata la differenza che l’attuale versione 3.0 ha rispetto alla precedente versione 2.0:
Info
In questa sezione sono presenti i metadata associati al contratto dell’API. Le parti obbligatorie di tale sezione sono il titolo, la versione e la descrizione della API. Altri campi ammessi sono le informazioni di contatto, della licenza e una URL che ad un pagina in cui sono espressi i termini e condizioni di utilizzo del servizio. In sostanza, l’oggetto Info dovrebbe fornire agli utilizzatori ed agli sviluppatori una panoramica di alto livello su ciò che fa la API.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
openapi: 3.0.0 info: title: User API description: Javaboss example User API termsOfService: http://www.javaboss.it contact: name: Massimo Romano url: http://massimoromano.altervista.org/ email: admin@javaboss.it license: name: Apache 2.0 url: https://www.apache.org/licenses/LICENSE-2.0.html version: 1.0.0 |
La prima riga del file deve contenere quindi la versione della specifica da applicare e può assumere il valore swagger: "2.0"
o openapi: 3.0.n
(ad esempio openapi: 3.0.0
).
Servers
La sezione servers
fornisce agli utilizzatori delle API le informazioni su come contattare il server che le ospita, attraverso la definizione della URL. A differenza della versione 2.0 della specifica, che consentiva di definire una sola URL, la versione OAS 3.0 supporta più server URL. Ciò è utile poiché nel mondo reale le API esistono in più ambienti (environment) e la logica di business del contratto può cambiare in base all’ambiente. Si pensi ad esempio alla distinzione tra server di test, preproduzione e produzione.
1 2 3 4 5 |
servers: - url: http://www.javaboss.it/prod/v1 description: Production environment - url: http://www.javaboss.it/test/v1 description: test environment |
Security
Nel mondo reale tutte le API hanno bisogno di un certo livello di sicurezza che impedisca ai clienti non autorizzati di accedervi. A tale scopo è necessario descrive i metodi di autenticazione ed autorizzazione supportati dalla API ed in particolare OpenAPI supporta diversi schemi:
- HTTP authentication schemes (Basic, Bearer, etc.);
- API keys (in headers, cookies, o query strings);
- OAuth2;
- OpenID Connect Discovery.
La specifica del livello di sicurezza nella progettazione delle API si ottiene attraverso due step. Il primo consiste nel definire le implementazioni di sicurezza adottate, che sono descritte nella sezione components/securitySchemes
. Il secondo step consiste nell’applicare tali schema globalmente a tutte le API o singolarmente alle diverse operazioni, semplicemente inserendo la sezione security
a livello globale o di metodo.
Nella sezione securitySchemes
quindi definiamo gli shema applicabili fornendo un nome (qualsiasi) allo schema ed indicandone il tipo tra: htt
, apiKey
, oauth2
e openIdConnect
. In base al tipo possono poi essere necessarie ulteriori informazioni, ad esempio:
http |
scheme |
Indica il nome dell’HTTP Authorization scheme utilizzato (RFC7235). |
bearerFormat |
Indica il formato del token. Si noti che si tratta di una informazione finalizzata solamente alla documentazione del metodo perché essendo il token generato dal server al client non serve conoscerne il formato. | |
apiKey |
in |
Indica dove è trasportata la chiave: query , header o cookie . |
name |
Indica il nome del parametro nell’header, nella query o nei cookie che trasporta la chiave. | |
openIdConnect |
openIdConnectUrl |
OpenId Connect URL per scoprire i valori di configurazione di OAuth2. Deve essere una URL. |
oauth2 |
flows |
Un oggetto complesso contenente informazioni di configurazione per i tipi di flusso oAuth2 supportati. |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
components: securitySchemes: BasicAuth: type: http scheme: basic BearerAuth: type: http scheme: bearer bearerFormat: JWT ApiKeyAuth: type: apiKey in: header name: X-API-Key OpenID: type: openIdConnect openIdConnectUrl: https://www.javaboss.it/.well-known/openid-configuration OAuth2: type: oauth2 flows: authorizationCode: authorizationUrl: https://www.javaboss.it/oauth/authorize tokenUrl: https://www.javaboss.it/oauth/token scopes: read: Grants read access write: Grants write access admin: Grants access to admin operations |
Infine la sezione security
indica quale schema di sicurezza applicare tra quelli descritti referenziandoli utilizzando il nome datogli nella sezione securitySchemes
. Nel caso poi di OAuth 2 e OpenID Connect è anche possibile indicare gli scope
, per controllare le autorizzazioni per varie risorse utente. Nel caso di OAuth 2, gli scope
devono essere dichiarati nel securitySchemes
, mentre per OpenID Connect, i possibili scope sono elencati nel discovery endpoint specificato dal parametro openIdConnectUrl
. Gli altri schemi non usano gli scope, quindi troviamo un array vuoto []
a fianco alle loro voci.
1 2 3 4 5 6 7 8 |
security: - BasicAuth: [] - OAuth2: - read_user - write_user - OpenID: - read_user - write_user |
Paths
In questa sezione sono dichiarati i vari end-point esposti dalla API ed i corrispondenti metodi HTTP supportati. A ciascun metodo è associato una sottosezione che inizia con una delle seguenti parole chiavi: get
, put
, post
, delete
, options
, head
, patch
e trace
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
paths: /user/{id}: get: summary: Recupera le informazioni utente description: Consente di recuperare le informazioni associate ad un utente conoscendo il relativo identificativo. operationId: getUserById externalDocs: description: Moggiori informazioni possono essere recuperate al link seguente url: https://www.javaboss.it/specifica-openapi tags: [Gestione utenti] servers: - url: http://www.javaboss.it/preprod/v1 description: Server preproduzione security: - BearerAuth: [] |
Ciascun metodo è caratterizzato da una descrizione breve, summary
, ed una estesa, description
, ma è anche possibile indicare una risorsa esterna attraverso il tag externalDocs
. Inoltre attraverso la definizione di uno o più tag è possibile raggruppare i metodo al fine di uniformare la documentazione prodotta. Ciascuna operazione è poi univocamente identificata da un operationId
che dovrà quindi essere univoca in tutto il documento. Infine è anche possibile sovrascrivere le impostazioni globali per quanto riguarda i server da contattare ed i security schema applicabili.
All’interno di ciascuna definizione di metodo viene poi dettagliato l’effettivo ciclo di request e response, attraverso la definizione degli oggetti parameters
, requestBody
e responses
.
Parameters
In tale sezione sono descritti i parametri che caratterizzano l’operazione. Ciascun parametro è definito attraverso la dichiarazione obbligatoria del parametro name
ed in
. Quest’ultimo identifica la location in cui trovare il parametro e può assumere uno dei quattro seguenti valori: path
, query
, header
e cookie
. Altri parametri non obbligatori sono: description
, required
, deprecated
e allowEmptyValue
.
A tali informazioni va poi aggiunto uno schema che definisce il tipo di dato che caratterizza il parametro.
1 2 3 4 5 6 7 8 9 |
parameters: - name: id in: path required: true description: Identificativo univoco dell'utente schema: type : integer format: int64 minimum: 1 |
Request Body
Il request body viene generalmente utilizzato con le operazioni di creazione e aggiornamento (POST, PUT, PATCH). Ad esempio, quando si crea una risorsa, utilizzando i metodi POST o PUT, o la si aggiorna, utilizzando il metodo PATCH, il request body di solito contiene la rappresentazione della risorsa coinvolta. La sua definizione è abbastanza flessibile in quanto consente di utilizzare diversi tipi di Media Type, come JSON, XML, form data, plain text ed altro, e utilizzare schemi diversi per tipi di media diversi.
La sezione requestBody
è caratterizzata da un contenuto, una descrizione opzionale e da un flag che indica la sua obbligatorietà (che è false
per impostazione predefinita). Il contenuto elenca i tipi di media utilizzati dall’operazione (come application/json
) e specifica lo schema per ciascun tipo di media. Tale schema può essere definito all’interno della stessa definizione del requestBody
o globalmente nella sezione components
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
components: schemas: User: type: object properties: id: type: integer name: type: string ... patch: ... requestBody: description: Valori che devono essere aggiornati required: true content: application/json: schema: $ref: '#/components/schemas/User' |
Responses
Per ogni operazione, è possibile definire diversi codici di stato, ad esempio 200 OK
o 404 NOT FOUND
, e per ognuno di essi fornire:
- una descrizione dell’esito;
- il contenuto del body con il relativo schema;
- una mappa di eventuali header da aggiungere alla response, indicandone il nome e lo schema associato.
Gli shema utilizzati possono essere definiti sia in linea che referenziandoli tramite $ref
dopo averli definiti nella sezione components
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
get: ... responses: 200: description: Utente recuperato headers: X-Custom-Header: description: A custom header schema: type: integer content: application/json: schema: $ref: '#/components/schemas/User' 400: description: L'ID fornito è invalido (not a number). 404: description: Utente non trovato. default: description: Error generico |
Components
Nel processo di progettazione delle API all’aumentare del numero di operazioni e di risorse coinvolte corrisponde un aumento delle dimensioni del file che le definisce. Può dover essere necessario ripetere molte informazioni, come i parametri o la definizione delle request e delle response che, in molti casi, possono essere simili benchè associate a metodi differenti. Riscriverli ogni volta oltre ad impiegare tempo può portare ad errori o peggio a definizioni incoerenti.
La sezione components
, che per altro abbiamo già introdotto in modo indiretto, consente di mitigare questi problemi in quanto destinata alla definizione di oggetti che possono essere referenziati in altri parti del file di specifica. Esempi di oggetti riutilizzabili sono: gli schema, i parametri, le response, etc.
naturalmente gli oggetti definiti nella sezione components
non avranno alcun effetto sulla definizione dell’API a meno che non siano esplicitamente referenziati da proprietà esterne alla sezione.
Generazione della Documentazione
Esistono diversi editor utili sia a produrre il file di specifica che a generare la documentazione associata. Sicuramente quello più famoso e completo è lo swagger editor, che di fatto abbiamo utilizzato per produrre l’esempio descritto nell’articolo e di cui riportiamo uno screenshot nell’immagine seguente.
La particolarità di tale editor è che consente la generazione sia del codice server che client per diverse tecnologie: java, python, php, c#, etc. Inoltre è possibile anche generare la documentazione, in formato html, della nostra API da poter distribuire agli utilizzatori.
Il Codice Sorgente
Il codice sorgente del progetto è scaricabile qui user-openapi.yaml