GoSuda

Go și ecosistemul OpenAPI

By iwanhae views ...

Introducere

Atunci când dezvoltați un server Backend de producție în limbajul Go, aproape fiecare dezvoltator întâlnește, de regulă, una dintre primele provocări, care poate fi formulată astfel:

Documentarea API-ului, cum o facem...?

Căutând puțin în această privință, se constată că este avantajos să scriem documentația conform specificației OpenAPI și, în mod firesc, se ajunge să se caute o bibliotecă integrată cu OpenAPI. Totuși, odată luată această decizie, apare următoarea problemă:

Există multe biblioteci legate de OpenAPI.. Care ar trebui folosită...?

Acest document este o scurtă prezentare a bibliotecilor, redactată pentru novicii în Go care se confruntă cu această situație. Documentul a fost redactat la sfârșitul anului 2024, iar, întrucât ecosistemul limbajului evoluează permanent, vă recomandăm să îl consultați împreună cu cele mai recente noutăți privind acest domeniu.

Strategiile bibliotecilor pentru OpenAPI

După cum probabil știți deja, OpenAPI este o specificație destinată definirii și documentării clare a REST API-urilor. Prin definirea endpoint-urilor API, a cererilor și a formatelor de răspuns în format YAML sau JSON, specificația contribuie esențial la automatizarea generării de cod atât pentru dezvoltatori, cât și pentru componentele de frontend și backend, reducând astfel repetițiile inutile și diminuând erorile umane minore.

Pentru a integra în mod natural OpenAPI în proiect, bibliotecile din ecosistemul Go adoptă, în linii mari, următoarele trei strategii:

1. Compunerea specificației OpenAPI din comentarii Go

O dificultate a dezvoltării API conforme OpenAPI constă în faptul că documentația efectivă și codul care implementează acea documentație se află în fișiere separate, în locații total diferite, astfel încât nu este deloc rar ca un cod să fie actualizat fără ca documentația să fie modificată, sau ca documentația să fie actualizată fără ca un cod să fie modificat corespunzător.

Un exemplu simplu:

  1. Modificați logica unui API în fișierul ./internal/server/user.go
  2. Documentația efectivă se află în ./openapi3.yaml, iar modificarea acesteia poate fi uitată din greșeală.
  3. Dacă nu observați această problemă și trimiteți un Pull Request, care este apoi revizuit de colegi,
  4. Nici revizorii nu pot vedea modificările din ./openapi3.yaml, astfel încât se poate ajunge la situația nefericită în care specificația API rămâne neschimbată, în timp ce implementarea reală a API-ului este modificată.

Scrierea documentației API sub formă de comentarii Go permite rezolvarea, într-o anumită măsură, a acestei probleme. Deoarece codul și documentația sunt grupate în același loc, puteți actualiza comentariile odată cu modificarea codului. Există instrumente care generează automat specificația OpenAPI pe baza acestor comentarii.

Un proiect reprezentativ este Swag. Swag analizează comentariile din codul Go și generează documentația în format OpenAPI 2. Utilizarea este simplă: deasupra funcției handler scrieți comentarii conforme formatului stabilit de bibliotecă.

 1// @Summary 유저 생성
 2// @Description 새로운 유저를 생성합니다.
 3// @Tags Users
 4// @Accept json
 5// @Produce json
 6// @Param user body models.User true "유저 정보"
 7// @Success 200 {object} models.User
 8// @Failure 400 {object} models.ErrorResponse
 9// @Router /users [post]
10func CreateUser(c *gin.Context) {
11    // ...
12}

Odată ce comentariile sunt scrise în acest mod, CLI-ul Swag analizează aceste comentarii și generează documentația OpenAPI 2. De obicei, această operație se execută în cadrul procesului CI, iar specificația OpenAPI generată este publicată în Git Repository, în rezultatul final al compilării sau într-un sistem extern de gestionare a documentației API, pentru a fi utilizată în colaborarea cu alte proiecte.

Avantaje:

  • Deoarece comentariile se află alături de cod, există o probabilitate mai mică ca forma codului și a documentației să difere.
  • Fără instrumente suplimentare sau configurări complicate, documentarea poate fi realizată simplu și flexibil, doar prin comentarii.
  • Deoarece comentariile nu afectează logica reală a API-ului, este convenabil să adăugați funcționalități temporare pe care nu doriți să le expuneți în documentație.

Dezavantaje:

  • Pe măsură ce numărul de linii de comentarii crește, lizibilitatea unui fișier de cod poate scădea.
  • Este posibil ca toate specificațiile API să fie greu de exprimat sub formă de comentarii.
  • Deoarece documentația nu impune codul, nu se poate garanta că documentația OpenAPI corespunde logicii reale.

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

Există și o abordare în care Single source of Truth (SSOT) se află în documentul de specificație, nu în codul Go. Este vorba despre definirea mai întâi a specificației OpenAPI, urmată de generarea codului Go pe baza conținutului definit. Deoarece specificația API generează codul, se poate impune din punct de vedere cultural proiectarea API-ului înaintea dezvoltării, iar, întrucât definirea specificației API este primul pas al procesului de dezvoltare, această metodă are avantajul de a preveni din timp situația în care, după finalizarea dezvoltării, se constată lipsuri și întregul cod trebuie modificat odată cu specificația API.

Proiectele reprezentative care adoptă această abordare sunt oapi-codegen și OpenAPI Generator. Utilizarea este simplă:

  1. Scrieți un document yaml sau json conform specificației OpenAPI
  2. Rulați CLI-ul
  3. Se generează codul Go stub corespunzător.
  4. Pentru ca acest stub să poată fi utilizat, trebuie doar să implementați manual logica detaliată a fiecărui API.

Următorul exemplu reprezintă codul 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 pe baza acestei interfețe execută operații precum parsarea parametrilor de interogare, a header-elor și a body-ului, validarea acestora și apelează metoda corespunzătoare declarată în interfață. Utilizatorul trebuie doar să implementeze structura care satisface această interfață, pentru ca lucrul necesar implementării API-ului să fie finalizat.

Avantaje:

  • Deoarece specificația se definește mai întâi și dezvoltarea urmează apoi, este avantajos pentru lucrul în paralel, în cazul colaborării mai multor echipe.
  • Codul care se scria manual, repetitiv și anevoios, este generat automat, ceea ce crește eficiența muncii și, în același timp, facilitează în continuare depanarea.
  • Este mai ușor să se garanteze că documentația și forma codului coincid întotdeauna.

Dezavantaje:

  • Dacă nu cunoașteți specificația OpenAPI, există o curbă de învățare inițială destul de abruptă.
  • Deoarece forma codului care gestionează API-ul este generată automat de instrument, poate fi dificil de reacționat atunci când este necesară personalizarea.

Comentariul autorului. Începând cu octombrie 2024, codul Go generat de OpenAPI Generator impune nu doar logica API-ului, ci întreaga structură a proiectului, iar structura rigidă a acestuia îl face nepotrivit pentru adăugarea diverselor funcționalități necesare unui mediu de producție real. Celor care adoptă această metodă le recomand cu tărie să utilizeze oapi-codegen. Autorul utilizează oapi-codegen + echo + StrictServerInterface.

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

Atunci când zeci sau sute de persoane dezvoltă simultan un server, apare inevitabil problema că uniformitatea specificațiilor API pentru fiecare endpoint se poate pierde. Ca exemplu intuitiv, dacă specificațiile a peste 100 de endpoint-uri API sunt declarate într-un singur fișier OpenAPI yaml, acel fișier va ajunge un monstru de peste 10.000 de linii, iar la declararea unui nou endpoint API se ajunge inevitabil la declararea repetată a acelorași modele, la omiterea unor câmpuri sau la apariția unor denumiri de path care nu respectă convențiile, ceea ce duce la pierderea uniformității generale a API-ului.

Pentru a rezolva această problemă, s-ar putea desemna un Owner separat pentru gestionarea fișierelor OpenAPI yaml sau s-ar putea dezvolta un Linter care să detecteze automat aceste probleme în cadrul procesului CI. Totuși, se poate impune uniformitatea completă a tuturor API-urilor și prin definirea unui Domain-specific language (DSL) în limbajul Go.

O tehnică de acest tip este utilizată de proiectul reprezentativ Kubernetes (fără a folosi biblioteci externe, prin implementare proprie). Se pot utiliza și proiecte precum go-restful și goa. Mai jos 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 unui cod Go compilabil, asemenea celui de mai sus, se obține avantajul că implementarea API-ului POST /users și definirea documentației sunt finalizate simultan.

Avantaje:

  • Deoarece totul este generat din cod, este ușor să se mențină consistența API-ului pentru întregul proiect.
  • Prin utilizarea sistemului de tipuri strict al limbajului Go, se obține o specificație mai precisă și lipsită de ambiguități decât în cazul utilizării tuturor funcționalităților OpenAPI3.

Dezavantaje:

  • Trebuie să învățați DSL-ul definit de fiecare framework, iar aplicarea acestuia la un cod existent poate fi dificilă.
  • Deoarece trebuie să respectați obligatoriu regulile propuse de framework, libertatea și flexibilitatea pot fi reduse.

Încheiere

Fiecare metodă are avantaje și dezavantaje, iar alegerea metodei potrivite în funcție de cerințele proiectului și de preferințele echipei este importantă. Cel mai important lucru nu este care metodă este mai bună, ci evaluarea soluției cel mai potrivite pentru situația actuală și creșterea productivității dezvoltării, pentru a putea beneficia de program de lucru scurt și de un echilibru plăcut între viața profesională și cea personală.

Deși acest articol a fost redactat în octombrie 2024, ecosistemele Go și OpenAPI evoluează continuu. Ținând cont de intervalul de timp dintre momentul redactării și momentul lecturii, vă recomandăm să urmăriți în continuare noutățile privind bibliotecile și proiectele menționate, precum și schimbările din avantajele și dezavantajele acestora.

Mult succes în lumea Go! 😘