GoSuda

Go e l'ecosistema OpenAPI

By iwanhae
views ...

Introduzione

Nello sviluppo di un server backend in Go per la produzione, una delle prime sfide che quasi tutti gli sviluppatori incontrano è la seguente:

Come documentare le API...?

Approfondendo la questione, si comprende l'utilità di redigere la documentazione in conformità con le specifiche OpenAPI, e si cerca naturalmente una libreria compatibile con OpenAPI. Tuttavia, anche dopo aver preso questa decisione, sorge un'ulteriore problematica:

Esistono molte librerie correlate a OpenAPI... quale scegliere...?

Questo documento è una breve introduzione alle librerie, redatta per i principianti di Go che si trovano in questa situazione. Il documento è stato redatto alla fine del 2024 e, poiché l'ecosistema linguistico è in continua evoluzione, si consiglia di prenderlo come riferimento, tenendosi sempre aggiornati sugli sviluppi più recenti.

Strategie delle librerie nell'affrontare OpenAPI

Come è noto, OpenAPI è una specifica per definire e documentare in modo chiaro le API REST. Definisce endpoint, richieste e formati di risposta delle API in formato YAML o JSON, aiutando non solo gli sviluppatori ma anche il front-end e il back-end ad automatizzare la generazione del codice, riducendo ripetizioni inutili e piccoli errori umani.

Per integrare senza problemi OpenAPI nei progetti, le librerie dell'ecosistema Go adottano principalmente tre strategie:

1. Combinazione di commenti Go in documenti di specifiche OpenAPI

Uno degli aspetti complessi nello sviluppo di API conformi a OpenAPI è che la documentazione effettiva e il codice che la implementa si trovano in file separati, in posizioni completamente diverse. Pertanto, è fin troppo frequente aggiornare il codice senza aggiornare la documentazione, o viceversa.

Un semplice esempio:

  1. Si modifica la logica di un'API nel file ./internal/server/user.go.
  2. La documentazione effettiva si trova in ./openapi3.yaml, e si potrebbe dimenticare di apportare le relative modifiche.
  3. Se si invia una Pull Request senza accorgersi di queste modifiche.
  4. Anche i revisori non noteranno le modifiche in ./openapi3.yaml. Si potrebbe verificare lo spiacevole inconveniente che la specifica dell'API rimane invariata mentre l'implementazione effettiva dell'API viene modificata.

La stesura della documentazione API sotto forma di commenti Go può risolvere in parte questo problema. Poiché codice e documentazione si trovano nello stesso punto, è possibile aggiornare i commenti contestualmente alla modifica del codice. Esistono strumenti in grado di generare automaticamente documenti di specifiche OpenAPI basandosi su questi commenti.

Un progetto rappresentativo è Swag. Swag analizza i commenti nel codice Go per generare documenti in formato OpenAPI 2. L'utilizzo è semplice: è sufficiente scrivere commenti secondo il formato definito da ciascuna libreria sopra la funzione handler.

 1// @Summary Creazione utente
 2// @Description Crea un nuovo utente.
 3// @Tags Users
 4// @Accept json
 5// @Produce json
 6// @Param user body models.User true "Informazioni utente"
 7// @Success 200 {object} models.User
 8// @Failure 400 {object} models.ErrorResponse
 9// @Router /users [post]
10func CreateUser(c *gin.Context) {
11    // ...
12}

Dopo aver scritto questi commenti, la CLI di Swag li analizza per generare un documento OpenAPI 2. In genere, questa operazione viene eseguita nel processo di CI e il documento delle specifiche OpenAPI generato viene distribuito al repository Git, al risultato finale della build o a un sistema esterno separato di gestione della documentazione API, per essere utilizzato in collaborazione con altri progetti.

Vantaggi:

  • Poiché i commenti sono inclusi nel codice, si riduce la probabilità che il codice effettivo e la documentazione differiscano.
  • È possibile effettuare una documentazione semplice e libera, con soli commenti, senza strumenti o configurazioni complicate.
  • Poiché i commenti non influiscono sulla logica effettiva dell'API, è possibile aggiungere temporaneamente funzioni che non si desidera rendere pubbliche nella documentazione.

Svantaggi:

  • Il numero elevato di righe di commenti può ridurre la leggibilità di un singolo file di codice.
  • Potrebbe essere difficile esprimere tutte le specifiche API sotto forma di commenti.
  • Poiché la documentazione non vincola il codice, non si ha la certezza che la documentazione OpenAPI e la logica effettiva corrispondano.

2. Generazione di codice Go da documenti di specifiche OpenAPI

È possibile anche mantenere il Single source of Truth (SSOT) non nel codice Go, ma nella documentazione. Questo avviene definendo innanzitutto le specifiche OpenAPI, e generando poi il codice Go basandosi sui contenuti definiti. Poiché le specifiche API generano automaticamente il codice, è possibile forzare culturalmente la progettazione API in anticipo. Dal punto di vista della sequenza di sviluppo, la definizione delle specifiche API è il primo passaggio, prevenendo in tal modo che, una volta completato lo sviluppo, si prendano atto di lacune e si debba modificare l'intera struttura del codice insieme alla specifica API.

I progetti più rappresentativi che adottano questo approccio sono oapi-codegen e OpenAPI Generator. L'utilizzo è semplice:

  1. Si scrive un documento YAML o JSON conforme alle specifiche OpenAPI.
  2. Si esegue la CLI.
  3. Viene generato il codice stub Go corrispondente.
  4. Ora è sufficiente implementare direttamente la logica dettagliata per le singole API, in modo che questo stub possa essere utilizzato.

Di seguito è riportato un esempio di codice generato da oapi-codegen.

1// StrictServerInterface rappresenta tutti gli handler del server.
2type StrictServerInterface interface {
3	// ...
4	// Restituisce tutti gli animali domestici
5	// (GET /pets)
6	FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error)
7	// ...
8}

Tramite l'interfaccia, il codice generato da oapi-codegen esegue operazioni come l'analisi dei parametri di query, degli header e del body, nonché la validazione, e richiama il metodo appropriato dichiarato nell'interfaccia. L'utente deve implementare solo l'interfaccia per completare l'implementazione dell'API.

Vantaggi:

  • Poiché le specifiche vengono definite prima dell'inizio dello sviluppo, è più facile procedere con il lavoro in parallelo nel caso di collaborazione tra più team.
  • Poiché il codice per le operazioni ripetitive e noiose viene generato automaticamente, l'efficienza del lavoro aumenta pur mantenendo la facilità di debug.
  • È facile garantire che la documentazione e la struttura del codice siano sempre coerenti.

Svantaggi:

  • Se non si hanno conoscenze delle specifiche OpenAPI, la curva di apprendimento iniziale è maggiore.
  • Poiché la struttura del codice che gestisce le API viene generata automaticamente dal progetto, potrebbe essere difficile apportare personalizzazioni nel caso siano necessarie.

Commento dell'autore. A ottobre 2024, il codice Go generato da OpenAPI Generator forza la struttura dell'intero progetto oltre alla logica delle API, rendendo la struttura del progetto rigida e generando codice non adatto ad aggiungere diverse funzioni necessarie in un ambiente di produzione reale. Per coloro che adottano questo approccio, si consiglia vivamente di utilizzare oapi-codegen. L'autore utilizza oapi-codege + echo + StrictServerInterface.

3. Generazione di documenti di specifiche OpenAPI tramite codice Go

Quando decine o centinaia di persone lavorano contemporaneamente allo stesso server, un problema inevitabile è la possibile perdita di uniformità tra le singole API. Un esempio pratico è che, se la specifica per più di 100 endpoint API viene dichiarata in un unico file YAML OpenAPI, il file diventerebbe un mostro con oltre 10.000 righe. Dichiarando un nuovo endpoint API, si finirebbe inevitabilmente per dichiarare modelli identici in modo ridondante, omettendo alcuni campi o creando nomi di percorsi non conformi alle convenzioni, portando alla perdita dell'uniformità complessiva delle API.

Per risolvere questi problemi, si può designare un proprietario che gestisca il file YAML OpenAPI, o sviluppare un linter per rilevarli automaticamente durante il processo di CI. Tuttavia, è possibile anche definire un Domain-specific language (DSL) in Go per forzare l'uniformità di tutte le API.

Un progetto rappresentativo che utilizza questa tecnica è Kubernetes (realizzato internamente senza librerie separate). È inoltre possibile utilizzare progetti come go-restful e goa. Di seguito è riportato un esempio di utilizzo di goa:

 1var _ = Service("user", func() {
 2    Method("create", func() {
 3        Payload(UserPayload)
 4        Result(User)
 5        HTTP(func() {
 6            POST("/users")
 7            Response(StatusOK)
 8        })
 9    })
10})

Scrivendo codice Go compilabile come sopra, si ottiene il vantaggio che l'implementazione dell'API POST /users e la definizione della documentazione vengono completate simultaneamente.

Vantaggi:

  • Poiché tutto deriva dal codice, è più facile mantenere la coerenza delle API per l'intero progetto.
  • Sfruttando il sistema a tipi di Go, è possibile ottenere specifiche più accurate e non controverse rispetto all'utilizzo di tutte le funzionalità di OpenAPI3.

Svantaggi:

  • È necessario imparare il DSL definito da ciascun framework, e potrebbe essere difficile applicarlo al codice esistente.
  • Poiché è necessario seguire le regole fornite dal framework, la libertà e la flessibilità possono essere ridotte.

In conclusione

Ciascun metodo presenta vantaggi e svantaggi, ed è importante scegliere il metodo più adatto in base ai requisiti del progetto e alle preferenze del team. Ciò che conta di più non è quale metodo sia migliore, ma piuttosto valutare quale soluzione sia più adatta alla propria situazione attuale e aumentare la produttività dello sviluppo per andare a casa prima e godersi un soddisfacente equilibrio tra vita privata e lavoro.

Questo testo è stato redatto a ottobre 2024, ma poiché l'ecosistema Go e OpenAPI è in continua evoluzione, si prega di tenere in considerazione il divario temporale tra la stesura e la lettura, e di rimanere aggiornati sulle ultime notizie e sui cambiamenti di vantaggi e svantaggi di ogni libreria e progetto.

Vi auguro una felice vita con Go! 😘