Vor kurzem ist Version 1.18 von Go veröffentlicht worden. Die neuen Features sind vor allem Generics in Form von Type Parameters und ein eingebautes Fuzzing für die automatische Erzeugung von Testfällen.
Dieses Release ist ein bedeutender Meilenstein für die weitere Entwicklung von Go, aber es ist wie viele andere Releases seit Version 1.0 vor allem eins: unaufgeregt und kein großer Umbruch für das gesamte Ökosystem. Denn eins schätzen wir in der Praxis an Go besonders: die Kompatibilitätsgarantie und einfache Updates bestehender Projekte. Die Entwicklungsgeschichte von Go ist aus meiner Sicht ein Beispiel für besonders gutes Software-Engineering - und hier liegt nach wie vor ein besonderer Fokus in dem Projekt und auch in der Community.
Doch was ist Software-Engineering eigentlich, und warum ist das so wichtig? Russ Cox hat eine aus meiner Sicht sehr treffende Definition aufgestellt:
Software engineering is what happens to programming when you add time and other programmers.
Diese Einsicht fand auch einen wichtigen Einfluss bei dem Design der Sprache und bei allen bisherigen Weiterentwicklungen:
Go's purpose is therefore not to do research into programming language design; it is to improve the working environment for its designers and their coworkers. Go is more about software engineering than programming language research. Or to rephrase, it is about language design in the service of software engineering.
Das sind sicherlich auch die Gründe, warum die Einführung von Generics, Modules und anderen Änderungen an der Sprache mit viel Bedacht und teilweise auch lieber gar nicht geschehen (wie Vorschläge zu einer syntaktischen Vereinfachung des Error-Handlings).
Der Vorteil: insgesamt haben wir einen sehr geringen Reibungsverlust bei Projekten, die Go verwenden. Damit meine ich den Aufwand während der Entwicklung, der eigentlich gar nicht gewollt ist:
- Abhängigkeiten up-to-date halten oder Ersatz finden, falls diese nicht mehr gepflegt werden
- Notwendige Anpassungen durch Änderungen an der Programmiersprache
- Verwaltung von Abhängigkeiten (Versionskonflikte, etc.)
- Betreuung des Build-Toolings
- Einsatz weiterer Abhängigkeiten für Tests
Langlebige Produkte und Tools haben mehr Vorteile durch einen geringen Reibungsverlust als durch eine möglichst schnelle Entwicklungszeit.
Wo wird Go bei uns eingesetzt?
1. Go als API Backend
Seit Anfang 2015 setzen wir Go in der Entwicklung produktiv ein. Den Anfang haben wir mit Werkzeit gemacht, unserer Software-as-a-Service Lösung zur Zeiterfassung. Das Backend ist in Go geschrieben und stellt eine REST API für die Webapp und Apps bereit.
Basierend auf den guten Erfahrungen haben wir viele weitere Produkte für unsere Kunden basierend auf einem Go Backend gestartet und entwickeln diese auch ständig weiter. Unter anderem sind dies:
- CCP-Check - ein Software-as-a-Service Produkt für die Qualitätssicherung in der Produktion (für HACCP)
- FIT - Ablösung einer generischen Software-Lösung für die Buchung von Trainings an verschiedenen Standorten mit flexiblen Tarifmodellen über eine Webapp und App
Auch in 7 weiteren Produkten - die intern oder noch nicht veröffentlicht sind - steckt ein in Go implementiertes API Backend. Für den Aufbau verzichten wir hier auf Frameworks und setzen zum Großteil auf die hervorragenden Go Core-Packages, ergänzt durch ausgewählte Packages (wie z.B. gqlgen für eine Typ-sichere GraphQL API). Mit der Zeit haben wir hier ein Boilerplate (also quasi eine Standardvorlage) entwickelt, welche den wesentlichen Aufbau eines API Backends strukturiert, aber viele Freiheiten bei der Weiterentwicklung lässt. Mehr Details dazu wollen wir noch in nächster Zeit veröffentlichen.
2. Ergänzend als Microservice
Auch in bestehenden Produkte und Lösungen in anderen Programmiersprachen fügt Go eine Menge an Vorteilen hinzu. Ein robuster HTTP-Service ist schnell gebaut und kann einfach betrieben werden. Bisherige Einsatzzwecke waren z.B.:
- Absicherung von Dateidownloads aus einer Dokumentenverwaltung über einen Go HTTP-Service mit JWT - auch für große Dateien und Video Streaming
- DSGVO konforme Löschung von Daten über eine dritte API mit Protokollierung
- OIDC Authentifizierung für Jitsi
- HTTP-Reverse Proxy mit einem S3 Cache für Bildverarbeitung über imgproxy
3. Als Tool in einem Projekt
Für kleinere Tools greifen wir auch gerne auf Go zurück. Da man hier auch ohne Frameworks und Dependencies auskommen kann, sind solche Tools kein Problem in der langfristigen Pflege - sie lassen sich auch Jahre später noch sinnvoll weiterentwickeln. Hier haben wir in den letzten Jahren zunehmend Node.js und PHP Scripts durch Go ersetzt - die Ausführung ist durch die sehr schnelle Kompilation nicht langsamer als mit einer klassischen Skriptsprache. Und einer der größten Vorteile ist, dass zur Ausführung keine Runtime benötigt wird - ein Binary reicht hier aus. Dieses lässt sich zudem einfach in ein schlankes Docker-Image verpacken (meistens kleiner als 10Mb).
Beispiele für den Einsatz von Go für ein Tool in unseren Projekten sind z.B.:
- Implementierung einer Mock API für lokale Entwicklung und Ausführung von Acceptance Tests (z.B. mit Cypress oder Playwright)
- Import oder Export aus Schnittstellen (z.B. via REST API)
- Aufbereitung von Daten (z.B. aus CSV, XML oder JSON)
4. Für DevOps Tools und Webhooks
Für die Automatisierung von Arbeitsabläufen und Build-Pipelines in Projekten verwenden wir GitLab und Kubernetes. Damit die verschiedenen Tools wie z.B. Harbor, Lighthouse CI, unser Ticketsystem oder Mattermost mit unserem Ablauf in GitLab integriert sind, implementieren wir häufig Webhooks oder CLI Tools in Go. Auch für das Erstellen und Verwalten von Docker Images haben wir einige spezialisierte Tools entwickelt, welche es so in dieser Form noch nicht gab. Da diese Services häufig im Hintergrund über Jahre hinweg zuverlässig ihre Arbeit verrichten sollen, ist Robustheit ein wichtiges Kriterium. Auch ein gutes Fehlerhandling und Logging helfen, falls bei der Automatisierung einmal etwas haken sollte.
Go liefert hier eine solide Basis für die Entwicklung von DevOps-Tools und Webhooks. Mit spezialisierten Packages z.B. für die GitLab API können einfach CLI Tools oder Webhooks, welche auf die umfangreiche API von GitLab zugreifen oder von GitLab aufgerufen werden, entwickelt werden. Durch den Typ-sicheren Aufbau werden Fehler schneller entdeckt und auch Tests für HTTP Services lassen sich einfach ergänzen.
Einige Szenarien, für die wir Tools implementiert haben sind z.B.:
- Automatisches Setzen und Kontrollieren der korrekten GitLab Projekt-Einstellungen für Cleanups (Artifacts)
- Automatische Anpassungen von eingetragenen Webhooks in GitLab Projekten
- Erzeugung einer Übersicht über den Platzverbrauch der GitLab Docker Registry nach Projekten
- Erfassung von Dependencies eines Projekts mit Versionen und möglichen Updates (z.B. für Composer) über eine API mit Webapp zur Auswertung
- Tagging von Docker Images nach Semantic Versioning mit Erzeugung von Minor- und Major-Alias-Tags
- Cleanup von Docker Image Tags nach verschiedenen Regeln (z.B. "behalte letzte 3 Patch-Versionen und letzte 3 Minor-Versionen")
- Einrichten von Harbor Projekten für GitLab Gruppen mit Erzeugung von Robot Accounts und Anlegen von GitLab CI/CD Variablen
- Einrichten von Lighthouse CI Projekten für GitLab Projekte mit entsprechendem Topic und Hinterlegen der GitLab CI/CD Variablen
- Erfassung der GitLab CI Job Laufzeiten über Prometheus Metriken
- Verlinkung von GitLab Merge Requests in Redmine Tickets
- Automatisches Einrichten von Standard-Webhooks und Berechtigungen für neue GitLab Projekte über einen System Webhook
- Automatisierte Auswertung und Änderung von Ressourcen in Kubernetes (z.B. zur Erfassung von PVCs ohne Backup Annotations)
Fazit
Go wird auch weiterhin ein wichtiger Baustein für die Entwicklung von Software bei uns sein. Auch das neue Release 1.18 zeigt, dass es hier mehr um Evolution und gutes Software Engineering geht, als um eine schnelle Revolution - welche zu Bruch im Ökosystem führen kann. Dies ist einer der Gründe, warum wir den Einsatz in langlebigen Produkten und in der Automatisierung so schätzen: es wird vermutlich kein Problem sein, die Lösungen auch noch in 10 Jahren relativ unverändert weiter zu verwenden und zu entwickeln.
Nach unserer Erfahrung eignet sich Go sehr gut für alle Arten von HTTP Servern: vom Microservice bis zum kompletten API Backend. Aber auch CLI Tools und kleinere "Scripts" profitieren vom Typ-sicheren Aufbau, der Robustheit und der Einfachheit im Betrieb.