GoSuda

Go și ecosistemul OpenAPI

By iwanhae
views ...

Introducere

Atunci când dezvoltă un server Backend de producție în Go, aproape toți dezvoltatorii se confruntă cu una dintre primele provocări, care este următoarea:

Cum realizez documentația API...?

Cercetând puțin, se constată că este avantajos să se redacteze documente conform specificației OpenAPI, iar în mod natural se caută o bibliotecă care să se integreze cu OpenAPI. Cu toate acestea, chiar și după o astfel de decizie, apare următoarea problemă:

Există multe biblioteci legate de OpenAPI... pe care ar trebui să o folosesc...?

Acest document este o scurtă introducere a bibliotecilor, concepută pentru începătorii în Go care se confruntă cu această situație. Documentul a fost redactat la sfârșitul anului 2024 și, având în vedere că ecosistemul limbajului este în continuă schimbare, se recomandă să se țină cont de acesta și să se urmărească întotdeauna cele mai recente evoluții.

Strategiile bibliotecilor față de OpenAPI

După cum probabil știți deja, OpenAPI este o specificație pentru definirea și documentarea clară a API-urilor REST. Prin definirea endpoint-urilor API, a formatelor de cerere și răspuns în format YAML sau JSON, aceasta nu doar că ajută dezvoltatorii, ci și automatizează generarea codului pentru frontend și backend, reducând repetițiile inutile și minimizând erorile umane minore.

Pentru a integra în mod natural OpenAPI în proiecte, bibliotecile din ecosistemul Go adoptă în mare parte următoarele trei strategii:

1. Combinarea comentariilor Go cu documentul de specificație OpenAPI

Una dintre dificultățile în dezvoltarea API-urilor conform OpenAPI este că documentul real și codul care implementează acel document există în fișiere separate, în locații complet diferite. Astfel, se întâmplă destul de des ca, după actualizarea codului, documentația să nu fie actualizată, sau viceversa.

Ca un exemplu simplu:

  1. Am modificat logica API-ului într-un fișier numit ./internal/server/user.go, dar
  2. documentația reală se află în ./openapi3.yaml, iar modificarea acesteia poate fi uitată din greșeală.
  3. Dacă se trimite o cerere de tip Pull Request fără a fi conștient de problemele legate de aceste modificări și se solicită o revizuire de la colegi,
  4. deoarece nici recenzorii nu pot vedea modificările din ./openapi3.yaml, poate apărea nefericita situație în care specificația API rămâne aceeași, dar implementarea reală a API-ului se modifică.

Redactarea documentației API sub formă de comentarii Go poate rezolva într-o oarecare măsură această problemă. Deoarece codul și documentația sunt reunite într-un singur loc, comentariile pot fi actualizate simultan cu modificarea codului. Există instrumente care generează automat documente de specificație OpenAPI pe baza acestor comentarii.

Un proiect reprezentativ este Swag. Swag parsează comentariile din codul Go și generează un document în format OpenAPI 2. Utilizarea este simplă. Este suficient să se scrie comentarii deasupra funcției handler, conform formatului stabilit de fiecare bibliotecă.

 1// @Summary Creare utilizator
 2// @Description Creează un nou utilizator.
 3// @Tags Utilizatori
 4// @Accept json
 5// @Produce json
 6// @Param user body models.User true "Informații utilizator"
 7// @Success 200 {object} models.User
 8// @Failure 400 {object} models.ErrorResponse
 9// @Router /users [post]
10func CreateUser(c *gin.Context) {
11    // ...
12}

După redactarea acestor comentarii, un CLI numit Swag le va parsa și va genera un document OpenAPI 2. De obicei, această operațiune se realizează în cadrul procesului CI, iar documentul de specificație OpenAPI generat este apoi distribuit într-un depozit Git, în rezultatul final al construirii, sau într-un sistem extern de gestionare a documentației API, pentru a fi utilizat în colaborarea cu alte proiecte.

Avantaje:

  • Deoarece comentariile sunt alături de cod, probabilitatea ca forma reală a codului și a documentației să difere este redusă.
  • Permite documentarea simplă și liberă doar prin comentarii, fără instrumente suplimentare sau configurări complexe.
  • Deoarece comentariile nu afectează logica reală a API-ului, este ușor să adăugați funcționalități temporare care ar fi dificil de publicat în documentație.

Dezavantaje:

  • Pe măsură ce numărul de linii de comentarii crește, lizibilitatea unui singur fișier de cod poate scădea.
  • Poate fi dificil să se exprime toate specificațiile API sub formă de comentarii.
  • Deoarece documentația nu impune codul, nu se poate garanta că documentul OpenAPI și logica reală sunt identice.

2. Generarea codului Go din documentul de specificație OpenAPI

Există și o metodă prin care Single Source of Truth (SSOT) este plasată în documentație, nu în codul Go. Aceasta implică definirea inițială a specificației OpenAPI și generarea codului Go pe baza conținutului definit. Deoarece specificația API generează direct codul, se poate impune culturalmente ca proiectarea API-ului să fie prioritară, iar definirea specificației API fiind primul pas în ordinea dezvoltării, se poate preveni din timp nefericita situație în care, după finalizarea dezvoltării, se realizează că au fost omise aspecte, necesitând modificarea specificației API și a întregului cod.

Proiectele reprezentative care adoptă această abordare sunt oapi-codegen și OpenAPI Generator. Modul de utilizare este simplu.

  1. Se redactează un document yaml sau json conform specificației OpenAPI.
  2. Se execută CLI-ul.
  3. Se generează codul Go stub corespunzător.
  4. Acum, trebuie doar să implementați logica detaliată pentru fiecare API individual, astfel încât acest stub să poată fi utilizat.

Următorul este un exemplu de cod generat de oapi-codegen:

1// StrictServerInterface represents all server handlers.
2type StrictServerInterface interface {
3	// ...
4	// Returns all pets
5	// (GET /pets)
6	FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error)
7	// ...
8}

Codul generat de oapi-codegen prin intermediul interfeței de mai sus are o structură în care efectuează logica de parsare și validare a query parameters, header, body și apelează metoda corespunzătoare declarată în interfață. Utilizatorul trebuie doar să implementeze această interfață pentru a finaliza lucrul necesar implementării API-ului.

Avantaje:

  • Deoarece specificația este stabilită mai întâi și apoi se trece la dezvoltare, este avantajos pentru colaborarea paralelă în cazul echipelor multiple.
  • Codul pentru sarcini repetitive, care erau realizate manual, este generat automat, ceea ce crește eficiența muncii și rămâne avantajos pentru depanare.
  • Este ușor de garantat că documentația și codul sunt întotdeauna consecvente.

Dezavantaje:

  • Dacă nu se cunoaște specificația OpenAPI, curba de învățare inițială poate fi destul de abruptă.
  • Deoarece forma codului care gestionează API-ul este generată automat de proiect, poate fi dificil de adaptat în cazul în care este necesară personalizarea.

Comentariul autorului. Începând cu octombrie 2024, codul Go generat de OpenAPI Generator impune nu doar logica API-ului, ci și structura întregului proiect, generând un cod rigid, nepotrivit pentru adăugarea diverselor funcționalități necesare într-un mediu de producție real. Celor care adoptă această metodă li se recomandă insistent să utilizeze oapi-codegen. Autorul folosește oapi-codegen + echo + StrictServerInterface.

3. Generarea documentului de specificație OpenAPI din codul Go

Atunci când zeci sau sute de oameni dezvoltă același server, apare inevitabil problema lipsei de uniformitate între API-urile individuale. Ca un exemplu intuitiv, dacă specificațiile pentru peste 100 de endpoint-uri API ar fi declarate într-un singur fișier OpenAPI yaml, acel fișier ar deveni un monstru de peste 10.000 de linii. Atunci când se declară un nou endpoint API, ar apărea inevitabil duplicarea acelorași modele, omiterea unor câmpuri sau crearea unor denumiri de Path care nu respectă convențiile, ducând la o lipsă de uniformitate generală a API-ului.

Pentru a rezolva această problemă, s-ar putea desemna un proprietar separat pentru gestionarea fișierului OpenAPI yaml sau s-ar putea dezvolta un Linter pentru a detecta automat astfel de erori în timpul procesului CI. Cu toate acestea, se poate defini un Domain-specific language (DSL) în Go pentru a impune o uniformitate consistentă tuturor API-urilor.

Un proiect reprezentativ care utilizează această tehnică este Kubernetes (care o construiește intern, fără o bibliotecă separată), iar alte proiecte precum go-restful și goa pot fi folosite pentru a o implementa. Următorul este un exemplu de utilizare a 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})

Prin scrierea codului Go compilabil de mai sus, se obține avantajul că implementarea API-ului POST /users și definirea documentației sunt finalizate simultan.

Avantaje:

  • Deoarece totul derivă din cod, este ușor de menținut consistența API-ului pentru întregul proiect.
  • Utilizând sistemul de tipuri puternice al Go, se poate obține o specificație mai precisă și mai lipsită de ambiguitate decât prin utilizarea tuturor funcționalităților OpenAPI3.

Dezavantaje:

  • Este necesară familiarizarea cu DSL-ul definit de fiecare framework, iar aplicarea la codul existent poate fi dificilă.
  • Deoarece este necesar să se respecte cu strictețe regulile propuse de framework, libertatea și flexibilitatea pot fi reduse.

Concluzie

Fiecare metodă are avantaje și dezavantaje, iar alegerea metodei adecvate este importantă în funcție de cerințele proiectului și preferințele echipei. Cel mai important lucru nu este ce metodă este mai bună, ci să se evalueze care este soluția cea mai potrivită pentru situația actuală și să se maximizeze productivitatea dezvoltării pentru a termina munca mai repede și a se bucura de un echilibru satisfăcător între viața profesională și cea personală.

Deși acest articol a fost scris în octombrie 2024, ecosistemul Go și OpenAPI este în continuă evoluție, prin urmare, având în vedere intervalul de timp de la citirea acestui articol, vă rugăm să urmăriți în continuare evoluțiile bibliotecilor și proiectelor, precum și avantajele și dezavantajele modificate ale acestora.

Vă doresc o viață fericită în Go~ 😘