GoSuda

Go und das OpenAPI Ökosystem

By iwanhae
views ...

Einleitung

Bei der Entwicklung von Production-Backend-Servern mit Go stoßen fast alle Entwickler auf eine der ersten Herausforderungen:

Wie dokumentiere ich APIs...?

Nach kurzer Recherche wird deutlich, dass es vorteilhaft ist, Dokumente gemäß der OpenAPI-Spezifikation zu erstellen, und man sucht zwangsläufig nach Bibliotheken, die mit OpenAPI integriert sind. Doch selbst nach dieser Entscheidung gibt es ein weiteres Problem:

Es gibt viele Bibliotheken für OpenAPI... welche soll ich verwenden...?

Dieses Dokument ist eine kurze Einführung in Bibliotheken für Go-Einsteiger, die sich in dieser Situation befinden. Es wurde Ende 2024 erstellt, und da sich das Sprachökosystem ständig verändert, wird empfohlen, beim Nachschlagen immer den neuesten Stand zu berücksichtigen.

Strategien von Bibliotheken im Umgang mit OpenAPI

Wie Sie wahrscheinlich bereits wissen, ist OpenAPI eine Spezifikation zur eindeutigen Definition und Dokumentation von REST-APIs. Durch die Definition von Endpunkten, Anfragen und Antwortformaten von APIs in YAML- oder JSON-Formaten können nicht nur Entwickler, sondern auch Frontend- und Backend-Code-Generierung automatisiert, unnötige Wiederholungen reduziert und kleine menschliche Fehler vermieden werden.

Um OpenAPI nahtlos in Projekte zu integrieren, verfolgen die Bibliotheken im Go-Ökosystem im Wesentlichen drei Strategien:

1. Kombination von Go-Kommentaren zu OpenAPI-Spezifikationsdokumenten

Einer der schwierigsten Aspekte bei der Entwicklung von APIs gemäß OpenAPI ist die Tatsache, dass das eigentliche Dokument und der Code, der dieses Dokument implementiert, in separaten Dateien an völlig unterschiedlichen Orten liegen. Dies führt häufig dazu, dass der Code aktualisiert wird, die Dokumentation jedoch nicht, oder umgekehrt.

Ein einfaches Beispiel:

  1. In der Datei ./internal/server/user.go wurde die Logik für eine API geändert.
  2. Das eigentliche Dokument befindet sich jedoch in ./openapi3.yaml, und es kann leicht vergessen werden, diese Änderung vorzunehmen.
  3. Wenn diese Änderung nicht bemerkt wird und ein Pull Request gesendet wird und die Kollegen dies überprüfen,
  4. sehen auch die Reviewer die Änderung in ./openapi3.yaml nicht. Daher kann es zu dem unglücklichen Fall kommen, dass die API-Spezifikation gleich bleibt, während die tatsächliche API-Implementierung geändert wird.

Durch die Erstellung von API-Dokumenten in Form von Go-Kommentaren kann dieses Problem bis zu einem gewissen Grad gelöst werden. Da Code und Dokumentation an einem Ort zusammengeführt sind, können die Kommentare gleichzeitig mit der Codeänderung aktualisiert werden. Es gibt Tools, die basierend auf diesen Kommentaren automatisch OpenAPI-Spezifikationsdokumente generieren.

Ein typisches Projekt ist Swag. Swag analysiert Go-Code-Kommentare und generiert Dokumente im OpenAPI 2-Format. Die Verwendung ist einfach: Schreiben Sie Kommentare gemäß dem von jeder Bibliothek definierten Format über die Handler-Funktion.

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

Wenn Kommentare wie diese geschrieben werden, analysiert die CLI von Swag diese Kommentare und generiert ein OpenAPI 2-Dokument. In der Regel erfolgt diese Operation im CI-Prozess, und das generierte OpenAPI-Spezifikationsdokument wird im Git-Repository, im endgültigen Build-Ergebnis oder in einem separaten externen API-Dokumentenverwaltungssystem bereitgestellt und für die Zusammenarbeit mit anderen Projekten verwendet.

Vorteile:

  • Da sich die Kommentare im Code befinden, ist es weniger wahrscheinlich, dass die Formen des tatsächlichen Codes und der Dokumentation unterschiedlich sind.
  • Die Dokumentation kann einfach und frei nur mit Kommentaren erfolgen, ohne separate Tools oder komplexe Einstellungen.
  • Da Kommentare die tatsächliche API-Logik nicht beeinflussen, ist es gut, temporäre Funktionen hinzuzufügen, die nicht in der Dokumentation veröffentlicht werden sollen.

Nachteile:

  • Da die Anzahl der Kommentarzeilen zunimmt, kann die Lesbarkeit einer einzelnen Codedatei abnehmen.
  • Es kann schwierig sein, alle API-Spezifikationen in Form von Kommentaren darzustellen.
  • Da die Dokumentation den Code nicht erzwingt, gibt es keine Garantie dafür, dass die OpenAPI-Dokumentation und die tatsächliche Logik übereinstimmen.

2. Generierung von Go-Code aus OpenAPI-Spezifikationsdokumenten

Es gibt auch eine Möglichkeit, die Single Source of Truth (SSOT) nicht im Go-Code, sondern auf der Dokumentationsseite zu platzieren. Dies ist die Methode, bei der die OpenAPI-Spezifikation zuerst definiert wird und Go-Code basierend auf dem definierten Inhalt generiert wird. Da die API-Spezifikation direkt Code generiert, kann die API-Entwicklung in der Entwicklungskultur erzwungen werden. Da die Definition der API-Spezifikation als erster Schritt im Entwicklungsprozess erfolgt, kann das unglückliche Ereignis verhindert werden, dass Teile erst nach Abschluss der Entwicklung erkannt werden und der gesamte Code zusammen mit der Änderung der API-Spezifikation geändert werden muss.

Typische Projekte, die diese Methode verwenden, sind oapi-codegen und OpenAPI Generator. Die Verwendung ist einfach.

  1. Schreiben Sie ein YAML- oder JSON-Dokument gemäß der OpenAPI-Spezifikation.
  2. Führen Sie die CLI aus.
  3. Entsprechender Go-Stub-Code wird generiert.
  4. Nun müssen Sie nur noch die detaillierte Logik für jede einzelne API direkt implementieren, damit dieser Stub verwendet werden kann.

Das folgende Beispiel zeigt den von oapi-codegen generierten Code.

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}

Über diese Schnittstelle führt der von oapi-codegen generierte Code Logik wie das Parsen und Validieren von Abfrageparametern, Headern und Body durch und ruft die entsprechende in der Schnittstelle deklarierte Methode auf. Der Benutzer muss lediglich die Implementierung für diese Schnittstelle implementieren, um die für die API-Implementierung erforderlichen Aufgaben zu erledigen.

Vorteile:

  • Da die Spezifikation zuerst erstellt und dann die Entwicklung durchgeführt wird, ist es von Vorteil, Aufgaben parallel zu erledigen, wenn mehrere Teams zusammenarbeiten.
  • Da der Code für den Teil, der als repetitive Aufgabe erledigt wurde, automatisch generiert wird, steigt die Arbeitseffizienz, und das Debuggen ist weiterhin einfach.
  • Es ist einfach zu gewährleisten, dass die Form der Dokumentation und des Codes immer übereinstimmen.

Nachteile:

  • Wenn Sie die OpenAPI-Spezifikation nicht kennen, gibt es eine gewisse anfängliche Lernkurve.
  • Da die Form des Codes, der die API verarbeitet, automatisch vom Projekt generiert wird, kann es schwierig sein, darauf zu reagieren, wenn eine Anpassung erforderlich ist.

Kommentar des Autors. Ab Oktober 2024 erzwingt der von OpenAPI Generator generierte Go-Code nicht nur die API-Logik, sondern auch die gesamte Projektform. Die Projektstruktur ist starr, sodass die generierte Codeform nicht für das Hinzufügen verschiedener Funktionen geeignet ist, die in einer realen Produktionsumgebung erforderlich sind. Wenn Sie diese Methode wählen, wird dringend empfohlen, oapi-codegen zu verwenden. Der Autor verwendet oapi-codegen + echo + StrictServerInterface.

3. Generierung von OpenAPI-Spezifikationsdokumenten aus Go-Code

Wenn Dutzende oder Hunderte von Personen an demselben Server entwickeln, kommt es zwangsläufig zu dem Problem, dass die Einheitlichkeit für einzelne APIs beeinträchtigt werden kann. Als anschauliches Beispiel: Wenn die Spezifikationen für mehr als 100 API-Endpunkte in einer einzigen OpenAPI-YAML-Datei deklariert werden, wird diese Datei zu einem Monster mit mehr als 10.000 Zeilen. Bei der Deklaration eines neuen API-Endpunkts werden notwendigerweise dieselben Modelle mehrfach deklariert, einige Felder weggelassen oder unkonventionelle Path-Namen generiert, und die gesamte API-Einheitlichkeit beginnt zu brechen.

Um dieses Problem zu lösen, kann es eine Möglichkeit geben, einen Owner zu haben, der die OpenAPI-YAML-Datei separat verwaltet, oder eine Möglichkeit, einen Linter zu entwickeln, um ihn automatisch im CI-Prozess zu erfassen. Es ist aber auch möglich, eine Domain-specific language (DSL) mit Go zu definieren, um zu erzwingen, dass alle APIs eine konsistente Einheitlichkeit aufweisen.

Ein typisches Projekt, das diese Technik verwendet, ist Kubernetes (selbst aufgebaut, ohne separate Bibliothek) und es kann mit Projekten wie go-restful und goa verwendet werden. Hier ist ein Beispiel für die Verwendung von 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})

Wenn Sie kompilierten Go-Code wie oben schreiben, erhalten Sie den Vorteil, dass die Implementierung und die Dokumentationsdefinition für die API POST /users gleichzeitig abgeschlossen werden.

Vorteile:

  • Da alles aus dem Code stammt, ist es einfach, die API-Konsistenz für das gesamte Projekt beizubehalten.
  • Durch die Verwendung des Strong-Typing-Systems von Go können Sie eine genauere und unumstrittenere Spezifikation erhalten, als wenn Sie alle Funktionen von OpenAPI3 nutzen würden.

Nachteile:

  • Sie müssen die von jedem Framework definierte DSL lernen, und es kann schwierig sein, sie auf vorhandenen Code anzuwenden.
  • Da Sie die vom Framework vorgeschlagenen Regeln zwangsweise befolgen müssen, können die Freiheit und Flexibilität eingeschränkt werden.

Abschließend

Jede Methode hat ihre Vor- und Nachteile. Es ist wichtig, die Methode zu wählen, die am besten zu den Anforderungen des Projekts und den Präferenzen des Teams passt. Das Wichtigste ist nicht, welche Methode am besten ist, sondern die Beurteilung, welche Lösung für die aktuelle Situation am besten geeignet ist, und die Steigerung der Entwicklungsproduktivität, um einen schnellen Feierabend und eine zufriedenstellende Work-Life-Balance zu genießen.

Obwohl der Artikel ab Oktober 2024 geschrieben wurde, entwickelt sich das Go- und OpenAPI-Ökosystem ständig weiter. Berücksichtigen Sie daher beim Lesen dieses Artikels den Zeitraum und verfolgen Sie auch kontinuierlich den neuesten Stand der einzelnen Bibliotheken und Projekte sowie deren geänderten Vor- und Nachteile.

Ich wünsche Ihnen ein glückliches Go-Leben! 😘