---
id: e10df2108115b462a5ac2a289080a901
author: iwanhae
title: Go i ekosystem OpenAPI
description: Przedstawiamy biblioteki oraz strategie, które można wykorzystać podczas tworzenia API opartych na OpenAPI w języku Go.
language: pl
date: 2024-10-18T06:07:43.092628696Z
path: /blog/posts/go-and-the-openapi-ecosystem-zd5a8e472
category: api
---

### Wstęp

Podczas tworzenia produkcyjnego serwera backendowego w języku Go prawie każdy programista natrafia na jeden z pierwszych poważnych problemów, a mianowicie:

> Dokumentacja API, jak to zrobić...?

Po krótkim researchu okazuje się, że warto tworzyć dokumentację zgodną ze specyfikacją OpenAPI, i naturalnie zaczynamy szukać biblioteki współpracującej z OpenAPI. Jednak nawet po podjęciu tej decyzji pojawia się kolejny problem.

> Bibliotek związanych z OpenAPI jest bardzo dużo.. Której powinienem użyć...?

Ten dokument to krótki przegląd bibliotek, napisany z myślą o początkujących w Go, którzy doświadczają takiej sytuacji. Dokument został przygotowany według stanu na koniec 2024 roku. Ekosystem języka zmienia się nieustannie, dlatego zachęcamy do korzystania z niniejszego materiału wraz z bieżącym śledzeniem najnowszych informacji.

### Strategie bibliotek wobec OpenAPI

Jak zapewne wiadomo, OpenAPI jest specyfikacją służącą do jednoznacznego definiowania i dokumentowania REST API. Definiując punkty końcowe API, żądania oraz formaty odpowiedzi w formacie YAML lub JSON, można nie tylko ułatwić pracę programistom, ale także zautomatyzować generowanie kodu po stronie frontendu i backendu, co pozwala ograniczyć bezsensowne powtórzenia i drobne błędy ludzkie.

Aby naturalnie połączyć OpenAPI z projektem, biblioteki w ekosystemie Go przyjmują w głównej mierze trzy strategie:

#### 1. Składanie specyfikacji dokumentu OpenAPI z komentarzy Go

Trudność w pracy zgodnej z OpenAPI polega na tym, że rzeczywisty dokument i kod, który go implementuje, znajdują się w osobnych plikach, w zupełnie różnych lokalizacjach. Dość często zdarza się, że kod zostaje zaktualizowany, ale dokumentacja już nie, albo dokumentacja zostaje zmieniona, a kodu nie udaje się zaktualizować.

Prosty przykład:

1. Zmieniamy logikę API w pliku `./internal/server/user.go`
2. Rzeczywisty dokument znajduje się w `./openapi3.yaml`, a zmianę w nim można łatwo przeoczyć.
3. Jeśli nie zauważymy tego problemu, wyślemy Pull Request i poprosimy kolegów o review.
4. Recenzenci również nie zobaczą zmian w `./openapi3.yaml`, więc może dojść do sytuacji, w której specyfikacja API pozostaje bez zmian, a faktyczna implementacja API się zmienia.

Zapisywanie dokumentacji API w formie komentarzy Go w znacznym stopniu rozwiązuje ten problem. Kod i dokumentacja znajdują się w jednym miejscu, dzięki czemu komentarze można aktualizować równocześnie z modyfikacją kodu. Istnieją narzędzia, które automatycznie generują specyfikację OpenAPI na podstawie tych komentarzy.

Reprezentatywnym projektem jest [Swag](https://github.com/swaggo/swag). Swag analizuje komentarze w kodzie Go i generuje dokumentację w formacie OpenAPI 2. Sposób użycia jest prosty: wystarczy napisać komentarze nad funkcją handlera, zgodnie z formatem określonym przez bibliotekę.

```go
// @Summary 유저 생성
// @Description 새로운 유저를 생성합니다.
// @Tags Users
// @Accept json
// @Produce json
// @Param user body models.User true "유저 정보"
// @Success 200 {object} models.User
// @Failure 400 {object} models.ErrorResponse
// @Router /users [post]
func CreateUser(c *gin.Context) {
    // ...
}
```

Po napisaniu takich komentarzy narzędzie CLI o nazwie Swag analizuje je i generuje dokument OpenAPI 2. Zazwyczaj proces ten uruchamiany jest w ramach CI, a wygenerowany dokument OpenAPI jest publikowany w Git Repository, w finalnym artefakcie buildu lub w zewnętrznym systemie zarządzania dokumentacją API, aby mógł być wykorzystywany podczas współpracy z innymi projektami.

**Zalety:**

- Ponieważ komentarze znajdują się obok kodu, **zmniejsza się prawdopodobieństwo rozbieżności między rzeczywistym kodem a dokumentacją.**
- Dokumentowanie można prowadzić **łatwo i swobodnie, wyłącznie za pomocą komentarzy**, bez dodatkowych narzędzi i skomplikowanej konfiguracji.
- Komentarze nie wpływają na rzeczywistą logikę API, dlatego **dobrze nadają się do dodawania tymczasowych funkcji, których nie chcemy udostępniać w dokumentacji.**

**Wady:**

- Wraz ze wzrostem liczby linii komentarzy **czytelność pojedynczego pliku z kodem może ulec pogorszeniu.**
- Wyrażenie wszystkich specyfikacji API **wyłącznie w formie komentarzy może być trudne.**
- Dokumentacja nie wymusza zgodności z kodem, dlatego **nie można zagwarantować, że dokument OpenAPI odpowiada rzeczywistej logice.**

## 2. Generowanie kodu Go na podstawie dokumentu OpenAPI

Istnieje również podejście, w którym Single Source of Truth (SSOT) znajduje się nie w kodzie Go, lecz w dokumencie. Chodzi o to, aby najpierw zdefiniować specyfikację OpenAPI, a następnie na jej podstawie wygenerować kod Go. Ponieważ specyfikacja API generuje kod, można kulturowo wymusić projektowanie API jako pierwszy krok. Ponadto, ponieważ definiowanie specyfikacji rozpoczyna się na samym początku procesu deweloperskiego, można wcześnie wykryć pominięte elementy i uniknąć sytuacji, w której dopiero po zakończeniu prac okazuje się, że trzeba zmienić specyfikację API i w konsekwencji cały kod.

Reprezentatywnymi projektami stosującymi to podejście są [oapi-codegen](https://github.com/oapi-codegen/oapi-codegen) oraz [OpenAPI Generator](https://github.com/OpenAPITools/openapi-generator/tree/master/samples/server/petstore/go-echo-server). Sposób użycia jest prosty:

1. Tworzymy dokument YAML lub JSON zgodny ze specyfikacją OpenAPI
2. Uruchamiamy CLI
3. Generowany jest odpowiadający mu kod stub w Go
4. Wystarczy zaimplementować szczegółową logikę poszczególnych API, z których może korzystać wygenerowany stub

Poniżej znajduje się przykład kodu generowanego przez oapi-codegen.

```go
// StrictServerInterface represents all server handlers.
type StrictServerInterface interface {
	// ...
	// Returns all pets
	// (GET /pets)
	FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error)
	// ...
}
```

Kod wygenerowany przez oapi-codegen na podstawie tego interfejsu wykonuje parsowanie query parameters, header i body oraz walidację, a następnie wywołuje odpowiednią metodę zadeklarowaną w interfejsie. Użytkownik musi jedynie zaimplementować ten interfejs, aby zakończyć pracę nad implementacją API.

**Zalety:**

- Ponieważ **specyfikacja powstaje jako pierwsza, a rozwój przebiega później**, w pracy wielu zespołów **łatwiej prowadzić prace równolegle**.
- Kod, który wcześniej powstawał w ramach **powtarzalnej, żmudnej pracy, jest generowany automatycznie**, dzięki czemu **wydajność pracy rośnie**, a **debugowanie nadal pozostaje łatwe**.
- **Łatwo zapewnić, że dokumentacja i kod zawsze są zgodne ze sobą.**

**Wady:**

- Osoby, które nie znają specyfikacji OpenAPI, mogą napotkać **stosunkowo strome początkowe krzywe uczenia się**.
- Ponieważ struktura kodu obsługującego API jest generowana automatycznie przez narzędzie, **może być trudno dostosować ją do specyficznych potrzeb**.

> Komentarz autora.
> Według stanu na październik 2024 roku kod Go generowany przez OpenAPI Generator narzuca nie tylko logikę API, ale także całą strukturę projektu. Jest ona sztywna, przez co słabo nadaje się do dodawania wielu funkcji wymaganych w środowisku produkcyjnym. Osobom wybierającym to podejście zdecydowanie polecam oapi-codegen. Autor korzysta z kombinacji oapi-codegen + echo + StrictServerInterface.

## 3. Generowanie dokumentu OpenAPI na podstawie kodu Go

Gdy dziesiątki lub setki osób rozwijają ten sam serwer, nieuchronnie pojawia się problem, polegający na tym, że ujednolicenie poszczególnych API może zostać zaburzone. Dla przykładu: jeśli specyfikacja ponad 100 punktów końcowych API zostanie zadeklarowana w jednym pliku OpenAPI YAML, plik ten urośnie do potwora liczącego ponad 10 000 linii. Przy dodawaniu nowych punktów końcowych nieuchronnie zaczną się pojawiać zduplikowane definicje tych samych modeli, pominięte pola lub nazwy ścieżek niezgodne z konwencją, co stopniowo narusza spójność całego API.

Aby rozwiązać ten problem, można wyznaczyć osobę odpowiedzialną za plik OpenAPI YAML lub stworzyć linter wykrywający błędy automatycznie w CI. Można też jednak zdefiniować Domain-specific language (DSL) w języku Go, który wymusi spójność wszystkich API.

Reprezentatywnym projektem stosującym tę technikę jest Kubernetes (który buduje ją samodzielnie, bez zewnętrznych bibliotek). Można również wykorzystać takie projekty jak [go-restful](https://github.com/emicklei/go-restful) czy [goa](https://goa.design/). Poniżej przykład użycia `goa`.

```go
var _ = Service("user", func() {
    Method("create", func() {
        Payload(UserPayload)
        Result(User)
        HTTP(func() {
            POST("/users")
            Response(StatusOK)
        })
    })
})
```

Pisząc kod Go, który się kompiluje, w ten sposób jednocześnie definiujemy implementację API `POST /users` oraz jego dokumentację, co jest dużą zaletą.

**Zalety:**

- Ponieważ wszystko wynika z kodu, **łatwiej utrzymać spójność API w całym projekcie**.
- Dzięki silnemu systemowi typów Go można uzyskać **dokładniejszą i bardziej jednoznaczną specyfikację** niż przy wykorzystaniu wszystkich funkcji OpenAPI 3.

**Wady:**

- **Trzeba poznać DSL zdefiniowany przez dany framework**, a **zastosowanie go do istniejącego kodu może być trudne.**
- Ponieważ trzeba bezwzględnie stosować się do reguł zaproponowanych przez framework, **swoboda i elastyczność mogą być ograniczone.**

**Podsumowanie**

Każda metoda ma swoje zalety i wady, dlatego ważne jest, aby wybrać rozwiązanie odpowiednie do wymagań projektu i preferencji zespołu. Najważniejsze jest nie to, która metoda jest lepsza, lecz to, by ocenić, jakie rozwiązanie najlepiej odpowiada aktualnej sytuacji, oraz zwiększać produktywność, aby móc wcześniej kończyć pracę i cieszyć się **dobrym balansem między życiem zawodowym a prywatnym**.

Choć artykuł został napisany według stanu na październik 2024 roku, ekosystemy Go i OpenAPI stale się rozwijają, dlatego warto na bieżąco śledzić aktualności oraz zmieniające się zalety i wady poszczególnych bibliotek i projektów, biorąc pod uwagę czas, jaki upłynął od publikacji.

Życzę udanej przygody z Go 😘
