Komplexe Webanwendungen sind häufig träge und schlecht skalierbar, neue Funktionen lassen sich nur zeitaufwendig nachrüsten. Abhilfe verspricht ein Umstieg auf agile Microservices. Damit handelt man sich allerdings ein paar andere Probleme ein.
WordPress, Owncloud und viele andere klassische Webanwendungen erledigen sämtliche Aufgaben allein. Unter anderem prüfen sie die Passwörter der Nutzer, kommunizieren mit einer Datenbank und durchsuchen die dort gelagerten Informationen. Solche monolithischen Anwendungen besitzen nur eine Codebasis, die sich recht einfach strukturieren, testen und pflegen lässt – zumindest zu Beginn der Entwicklung.
Um konkurrenzfähig zu bleiben, müssen Dienste und Webanwendungen kontinuierlich neue Funktionen erhalten, die auch noch möglichst schnell in Betrieb gehen sollen. Je mehr neue Funktionen und Aufgaben hinzukommen, desto mehr bläht sich der Code auf (Abbildung 1). Das zum Redaktionsschluss aktuelle Owncloud 10.10 besteht beispielsweise aus über 28 000 PHP-Dateien. Schon kleinere Änderungen wirken sich dann auf andere Teile der Anwendung aus. Soll beispielsweise die Datenbank zusätzliche Informationen speichern, muss man sämtliche davon betroffenen Codeteile anpassen und testen.

Abbildung 1: Bei einer monolithischen Anwendung kann die Codebasis mit steigender Funktionszahl gewaltige Dimensionen annehmen.
Programmfehler können außerdem weitreichende Folgen nach sich ziehen. Bleibt etwa die Suchfunktion in einer Endlosschleife hängen, steht womöglich die komplette Applikation. Darüber hinaus wird der Quellcode immer unübersichtlicher, was die Wartung und das Einarbeiten neuer Entwickler erschwert. Abschließend lässt sich eine monolithische Anwendung nicht mehr einfach skalieren. Genau an dieser Stelle stand auch Owncloud mit seiner gleichnamigen Filesharing-Plattform. Um das Problem zu lösen, wählte das Team einen recht radikalen Ansatz, der sich derzeit großer Beliebtheit erfreut.
Dabei zerlegt man kurzerhand die Software in mehrere eigenständige Dienste (Abbildung 2). Jeder Dienst erfüllt eine eng umrissene Aufgabe. So könnte ein Dienst eine Datei suchen, während sich ein anderer um die Authentifizierung kümmert. Die entsprechenden Funktionen bieten die Dienste ihren Kollegen über einheitliche Schnittstellen an. Auf diese Weise entstehen viele kleine, spezialisierte und autonom arbeitende Dienste: die Microservices. Wie ein fleißiges Bienenvolk realisieren sie zusammen ein Content-Management-System, einen Streaming-Dienst oder eine Filesharing-Plattform.

Abbildung 2: Microservices kümmern sich um eine abgeschlossene Aufgabe und verwalten ihre eigenen Daten. In Diagrammen repräsentieren häufig Sechsecke einen Microservice.
Hilfreiche Autonomie
Für den Begriff Microservice gibt es nach wie vor keine einheitliche Definition. Schlimmer noch: In den letzten Jahren nutzten viele Marketingabteilungen das Buzzword recht schwammig für ihre eigenen Zwecke. Microservices besitzen jedoch unumstritten zwei wesentliche Eigenschaften. Zunächst konzentriert sich jeder Dienst auf eine Aufgabe (Single-Responsibility-Prinzip, SRP). Hierdurch weist ein Microservice einen relativ kompakten Quellcode auf, der sich gut überblicken, versionieren und testen lässt.
Darüber hinaus arbeiten die Microservices autonom. Im Idealfall nutzt jeder Microservice sogar einen eigenen Datenspeicher. Auf diese Weise lassen sich die Dienste einzeln aktualisieren und im Bedarfsfall schnell ersetzen. Beispielsweise könnten Sie die Authentifizierung nachträglich gegen neue Angriffsarten härten, ohne die übrigen Microservices anfassen zu müssen. Die bessere Wartbarkeit ermöglicht zudem eine agilere Arbeitsweise und kürzere Release-Zyklen. Obendrein reißt eine defekte Suchfunktion nicht mehr das komplette System ins Verderben, und man spart sich beim Ändern einer Konfiguration den Neustart der kompletten Anwendung.
Dank ihrer Arbeitsweise skalieren Microservices deutlich besser als Monolithen. Benötigt etwa der Suchdienst mehr Speicher für seinen Index, verschieben Sie ihn auf einen Server mit mehr Hauptspeicher. Darüber hinaus besteht die Möglichkeit, Microservices in Clouds wie Amazon AWS oder Microsoft Azure auszulagern. An jedem Microservice kann ein eigenes Team werkeln und dabei individuell passende Frameworks und Programmiersprachen nutzen. Zu guter Letzt lassen sich Microservices in anderen Softwaresystemen wiederverwenden.
Aufgrund dieser Vorteile implementierten die Owncloud-Entwickler ihre Filesharing-Plattform mit Microservices komplett neu. Das Ergebnis firmiert unter dem Namen Owncloud Infinite Scale (OCIS [1]) und lässt sich bereits ausprobieren (Abbildung 3). Ein Umstieg auf die Microservice-Architektur gelingt allerdings nicht ganz so einfach, wie es auf den ersten Blick wirkt.

Abbildung 3: OCIS besteht aus zahlreichen Microservices; Reva dient als Bindeglied zu den Datenspeichern.
Stolperfallen
Im ersten Schritt gilt es, die zu erledigenden Aufgaben zu identifizieren und sie anschließend auf die Microservices zu verteilen. Wer hier unüberlegt vorgeht, sitzt im schlimmsten Fall vor einem chaotischen Berg aus Microservices. Verhindern können das entsprechende Vorgehensweisen, allen voran das Domain Driven Design (DDD).
Ist die Aufteilung nicht offensichtlich, sollte man laut Elastic-Entwickler Tarek Ziadé sogar von Microservices absehen [2]. Seiner Erfahrung nach kann das spätere Hinzufügen und Entfernen von Microservices schmerzhafter sein als das Refactoring einer monolithischen Anwendung. Zudem sei es immer einfacher, den Code in einen eigenen Microservice auszulagern, als zwei Dienste wieder zu verschmelzen, warnt Ziadé.
Des Weiteren müssen Sie sich überlegen, welche Microservices nur von Kollegen und welche auch von externen Clients kontaktiert werden dürfen. Im einfachsten Fall existiert ein Frontend-Dienst, der mit den Webbrowsern der Benutzer kommuniziert, die Benutzeroberfläche ausliefert und bei den anderen Microservices die notwendigen Aufgaben anstößt (Abbildung 4).

Abbildung 4: Bei OCIS dient die neu gestaltete Weboberfläche als Einstiegspunkt für den Webbrowser. Andere Clients docken an anderen Stellen an.
Durch die vielen miteinander sprechenden Dienste entsteht zwangsweise mehr Netzwerkverkehr, womit wiederum die Infrastruktur zurechtkommen muss. Daneben kümmert sich jeder Microservice selbst um die Datenspeicherung. So könnte der Microservice zur Authentifizierung einen LDAP-Server konsultieren, während die Suchfunktion einen Index in einem Memcached-Speicher anlegt. Ihre Daten müssen die Microservices jedoch irgendwie untereinander abgleichen. Im Beispiel ist etwa offen, wann man Informationen aus dem Suchindex entfernt.
Quelltexte jonglieren
Die Microservices besitzen unterschiedliche Release-Zyklen und somit verschiedene Versionen. Daher müssen Sie festhalten, welche Microservices in welchen Versionen zusammenarbeiten. Daneben ist das Verwalten des Quellcodes nicht mehr trivial: Steckt man jeden Microservice in ein eigenes Repository, oder lagert man alle in einem? Das Owncloud-Entwicklungsteam verwendete zunächst mehrere Repositories, schwenkte jedoch schnell wieder um. Der komplette OCIS-Code steckt jetzt in einem Repository, was das Verwalten und das Deployment vereinfacht. Getrennte Repositories können dennoch Sinn ergeben, da sie die Microservices voneinander separieren.
Abschließend stellt sich die Frage nach der Programmiersprache und den zu verwendenden Frameworks. Um Wildwuchs zu verhindern, sollten Sie sich auf eine Sprache konzentrieren und die möglichen Frameworks einschränken. Damit können alle Entwickler den Code sämtlicher Microservices lesen, und Sie müssen nicht mit zahlreichen verschiedenen Werkzeugen hantieren.
Beim Implementieren von Microservices erfreut sich vor allem Go großer Beliebtheit [3]. Dessen Compiler linkt sämtliche Bibliotheken in ein kompaktes Binary, das er obendrein für zahlreiche Plattformen generiert. Go-Programme laufen daher nicht nur besonders flott, sondern Sie müssen auch keine PHP-Umgebung oder andere zusätzliche Softwarekomponenten pflegen. Zusätzlich vereinfacht sich das Deployment. Wie schnell man mit den eingebauten Funktionen einen HTTP-Server auf die Beine stellt, demonstriert Listing 1.
Listing 1
Einfacher Webserver in Go
package main
import (
"fmt"
"net/http"
)
func resp(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "<h1>Hallo Welt!</h1>")
}
func main() {
http.HandleFunc("/", resp)
http.ListenAndServe(":8080", nil)
}
Owncloud hat sich ebenfalls für Go entschieden und dabei gleichzeitig einen interessanten Ansatz gewählt: Damit ein Microservice bei einem Absturz nicht seine Kollegen gleichermaßen außer Gefecht setzt, läuft er normalerweise in einem eigenen Prozess. In OCIS gibt es nur ein kompaktes Programm, das nach dem Aufruf jeden Dienst in einem eigenen Thread startet. OCIS nutzt dazu die von Go angebotenen Goroutinen [4]. Da alle Dienste somit immer noch im selben Prozess laufen, fallen die Tests und das Deployment leichter. Im Hintergrund verwaltet die Go-Bibliothek Suture die Dienste und fängt mit ihren ausgeklügelten Kontrollmechanismen unerwartete Situationen ab [5].
Sicherungen und Einfahrten
Wer Systeme aus Microservices zusammenstöpselt, stolpert immer wieder über ähnliche Probleme. Microservices kommunizieren über ein Netzwerk, das nicht immer zuverlässig arbeitet, Nachrichten mit einer Latenz verschickt und in dem Dienste ausfallen können. In solchen Fällen wartet der aufrufende Microservice einige Zeit auf Antwort und versucht dann erneut eine Kontaktaufnahme. Dieses zeitraubende Vorgehen bremst die komplette Anwendung aus. Linderung verschafft ein zwischengeschalteter Circuit Breaker: Er nimmt stellvertretend die Anfragen entgegen und leitet sie an den Microservice weiter (Abbildung 5). Bemerkt der Circuit Breaker einen Ausfall seines Microservices, antwortet er sofort mit einer Fehlermeldung.
Abbildung 5: Ein Circuit Breaker kann Anfragen zwischenspeichern und später an den wieder einsatzbereiten Microservice weiterleiten.
Solche vorgefertigten Lösungsansätze für typische Probleme bezeichnet man als Microservice-Pattern. Ein weiteres häufig eingesetztes Pattern ist das API-Gateway. Bei diesem Dienst klopft ein Microservice an, wenn er eine Funktion eines Kollegen nutzen möchte. Das API-Gateway prüft dann, ob der Microservice die notwendigen Zugriffsrechte besitzt, und leitet den Aufruf an den passenden Dienst weiter. Ein API-Gateway kann die Aufrufe zudem für eine andere Schnittstelle übersetzen oder eine ältere Schnittstellenversion auf eine neue umbiegen (Abbildung 6). Letzteres ist vor allem dann nützlich, wenn ein Microservice seine Schnittstelle geändert hat, aber noch nicht alle anderen Microservices angepasst wurden.

Abbildung 6: Das API-Gateway nimmt Anfragen über eine REST-Schnittstelle an und wandelt diese in für den Microservice verständliche gRPC-Anfragen um.
Microservices laufen auf verschiedenen Servern in einem Netzwerk. Die einzelnen Dienste sollen sich je nach Bedarf auf andere Server verschieben lassen; mitunter startet ein Load Balancer dynamisch neue Dienste. Möchte ein Microservice einen Kollegen kontaktieren, muss er herausfinden, wo und wie der gerade zu erreichen ist (Service Discovery). Dabei hilft ein zentrales Telefonbuch, die Service Registry. Bei ihr meldet sich jeder neu gestartete Microservice an, während seine Kollegen dort seine Kontaktdaten erfragen.
Ein weiteres Pattern verbessert die Skalierung mit einem Message Broker. Dieser Dienst sammelt im ersten Schritt eingehende Aufgaben in einer Warteschlange (Job Queue). Worker-Dienste holen im zweiten Schritt dort nach und nach die Aufgaben ab und führen sie aus (Abbildung 7). Ein im Zusammenhang mit Microservices beliebter Message Broker heißt RabbitMQ [6].

Abbildung 7: Sobald beim Message Broker ein Ereignis eingeht, informiert er alle Microservices, die sich dafür registriert haben.
Neben den vier hier vorgestellten Mustern existieren zahlreiche andere, die Chris Richardson in seinem Buch “Microservices Patterns” gesammelt hat [7]. Beim Einsatz der Patterns sollten Sie allerdings genau prüfen, ob sie für das eigene Problem infrage kommen und welche Konsequenzen sie nach sich ziehen. Der Circuit Breaker lohnt sich vor allem bei häufig angefragten Microservices, die mit höherer Wahrscheinlichkeit ausfallen können – etwa ein externes Zahlungssystem. Mit dem API-Gateway dagegen holen Sie sich unter Umständen einen Single Point of Failure ins Haus.
Datenformate
Microservices tauschen untereinander Daten aus, die häufig im JSON-Format durchs Netz fließen, verbreitet findet man aber auch XML oder YAML. Den Umgang mit diesen Formaten sollten alle Programmiersprachen zumindest über passende Bibliotheken vereinfachen. In Go lohnt ein Blick auf die Standardbibliothek.
Anstelle von JSON und Co. nutzen viele Microservices das binäre Datenformat Protocol Buffers (kurz Protobuf [8]). Dessen Datenpakete fallen kleiner aus und flutschen schneller durchs Netz. Ihr Einsatz gestaltet sich jedoch etwas komplexer: Zunächst beschreibt man den Aufbau der zu übertragenden Daten in einer Textdatei. Aus diesem Schema generiert dann ein Compiler Quellcode für eine Hochsprache wie C++, Python oder Go. Dieser Code enthält fertige Funktionen zum Umwandeln der Daten (Abbildung 8). Eine Alternative zu Protobuf bietet Messagepack [9], das kein separates Schema voraussetzt.

Abbildung 8: Bei den Protocol Buffers beschreibt man die zu transferierenden Daten (links im Bild), zu denen dann ein Compiler hilfreichen Quellcode generiert.
Schnittstellen
Möchte ein Microservice die Suchfunktion anstoßen, schickt er dem zuständigen Kollegen eine entsprechende Nachricht. Solche Remote Procedure Calls (RPCs) erfolgen häufig über eine REST-Schnittstelle. Dabei ruft der Microservice via HTTP eine URL auf, die eine zu manipulierende Ressource repräsentiert. Die auszuführende Aktion bestimmen die entsprechenden Methoden des HTTP-Protokolls wie PUT oder GET. Die REST-API funktioniert nach einem einfachen, verbreiteten Prinzip, und für die meisten Programmiersprachen existieren passende Bibliotheken. Beim Entwickeln unter Go lässt sich beispielsweise das Gin-Framework nutzen [10].
Allerdings sind REST-Schnittstellen bei sehr vielen Anfragen ineffizient und fehleranfällig. Als Alternative dient das bei Facebook entstandene GraphQL, das sich aufgrund seiner Arbeitsweise vor allem für externe Dienste oder als Schnittstelle nach außen anbietet. Für die Kommunikation zwischen Microservices hat sich das von Google entwickelte gRPC-Framework etabliert. Es verpackt die zu verarbeitenden Daten via Protobuf und schickt sie mit HTTP/2 effizient zum entsprechenden Microservice. gRPC erleichtert Anbindungen an zahlreiche Sprachen, darunter auch Go. Zum Einsatz kommt das Framework unter anderem bei Netflix, Dropbox und OCIS.
Wahlhelfer
In der Praxis müssen Sie häufig mehrere Protokolle einsetzen. OCIS zum Beispiel nutzt OpenID Connect für die Authentifizierung, eine LDAP-Schnittstelle sowie das WebDAV-basierte OC Sync Protocol. Bei der Auswahl eines Datenformats und einer Schnittstelle empfiehlt es sich, zu offenen und Programmiersprachen-unabhängigen Methoden zu greifen. Das vereinfacht die Zusammenarbeit mit externen Diensten, und Sie können auf gängige Frameworks zurückgreifen.
Das ausgesuchte Datenformat, das gewünschte Übertragungsverfahren und vor allem die Schnittstellen sollten Sie penibel dokumentieren. Entsprechende Standards greifen Ihnen dabei unter die Arme, im Fall von REST etwa die OpenAPI alias Swagger [11]. Die Schnittstellen dürfen sich zudem nur marginal verändern, sonst könnten Ihnen aufwendige Umbauarbeiten an mehreren Microservices blühen.
Beim Entwickeln von Microservices unterstützen Sie zahlreiche Frameworks. Go-Programmierern hilft Go Kit [12]. Damit lassen sich beispielsweise die benötigten Dienste recht bequem über die zur Sprache gehörenden Interfaces modellieren. Die Owncloud-Entwickler nutzen in OCIS die Alternative Go Micro, die eine Service Registry mitbringt [13]. Dank ihr kann das Team einzelne Dienste auf andere Server verschieben, obwohl OCIS normalerweise nur in einem Prozess läuft. Go Micro ist sogar prinzipiell in der Lage, einzelne Microservices aus einem Repository zu holen, direkt zu übersetzen und zu starten.
Test, Test, 1, 2, 3
Die Funktionen der Microservices können Sie asynchron oder synchron aufrufen. Im zweiten Fall blockiert der Aufruf die Ausführung, bis eine Antwort eintrudelt. Das bietet sich an, wenn etwa ein Online-Shop auf die Zahlungsbestätigung warten muss. Asynchrone Aufrufe empfehlen sich für nebenläufige Prozesse wie die Suchfunktion. Allerdings lässt sich dann das Verhalten des Systems nicht mehr genau vorhersagen, was wiederum Tests erschwert. Einige Sprachen entlasten Entwickler dabei immerhin etwas. Ein Paradebeispiel bietet Go, das auf Wunsch die Phasen einer HTTP-Anfrage verfolgt (HTTP Tracing).
Besonders aufwendig gestalten sich Integrations-, End-to-End- und Load-Tests. Während ein Integrationstest nur prüft, ob ein Microservice mit seinen unmittelbaren Kollegen zusammenarbeitet, testet ein End-to-End-Test, ob das komplette System wie gewünscht funktioniert. Das nicht deterministische Verhalten von Microservices macht diese Tests jedoch besonders knifflig. Beispielsweise prüft eine einzige Suchanfrage noch nicht, ob die Suche tatsächlich in allen Lebenslagen funktioniert. Außerdem verkompliziert sich das Debugging, da als Verursacher eines Fehlers gleich mehrere Microservices und das Netzwerk selbst in Betracht kommen. Die Load-Tests stellen schließlich noch sicher, dass die Microservices nicht trödeln. Mit aufwendigen Lasttests und simulierten Netzwerkfehlern versucht man dabei vor allem Flaschenhälse (Bottlenecks) in der Datenverarbeitung aufzudecken.
Daneben ist auch das Deployment beeinträchtigt, da man jetzt zahlreiche Dienste in der richtigen Reihenfolge starten muss. Hier können Sie Werkzeuge für Continuous Integration und Continuous Delivery (CI/CD) einsetzen wie das beliebte Jenkins. Diese Werkzeuge bauen die Microservices, stoßen hinterlegte Tests an und nehmen bei Erfolg den Microservice in Betrieb. Die fertigen Microservices landen meist in einem Container, der dann unter Docker, Kubernetes und Konsorten läuft. Die OCIS-Entwickler nutzen ein CI/CD-System. Da dort dank Go alle Microservices in einem Binary stecken, vereinfacht sich das Deployment drastisch.
Unter Beobachtung
Um im laufenden Betrieb Probleme frühzeitig zu entdecken, müssen Sie die Microservices dauerhaft überwachen. Dazu sollten die Dienste aussagekräftige Logs in einem einheitlichen Format erzeugen. Im Fall von Go genügt erneut ein Blick auf die Standardbibliothek, deren Funktionen die Logs direkt an den jeweiligen Syslog-Dienst weiterreichen können. Ergänzend gibt es den flotten Zap-Logger von Uber [14] und Lumberjack [15], das einen Log-Rotating-Mechanismus implementiert.
In jedem Fall muss man die erzeugten Logs so auslegen, dass sich das Verhalten von mitunter Hunderten Microservices nachvollziehen lässt. Dazu empfiehlt es sich, die Daten zu priorisieren und über Tags zu verschlagworten. Die finalen Logs sollten Sie über eine zentrale Stelle sammeln und zusammenführen. Hierzu stehen Werkzeuge wie Logstash [16] oder Graylog [17] zur Verfügung.
Performance-Metriken fördern Bottlenecks und Probleme im Nachrichtenaustausch zutage. Prozessorauslastung und ähnliche Daten können die Microservices selbst erheben. Wer Go einsetzt, findet im erwähnten Go Kit passende Unterstützung. Darüber hinaus kann der Go-Compiler Codeteile einem Benchmarking unterziehen.
Bei komplexeren Microservices sollten Sie die Ausführungsgeschwindigkeiten innerhalb der Dienste messen. Dadurch kommen Sie zum Beispiel einer bummelnden Suchfunktion auf die Schliche. Hierbei helfen die Zeitmessfunktionen der jeweiligen Programmiersprache. Die Metriken sammeln und visualisieren (Abbildung 9) Werkzeuge wie Prometheus [18].

Abbildung 9: Prometheus bereitet wichtige Metriken grafisch auf. Für einige Sprachen wie Go stellt das Prometheus-Team eigene Clients bereit.
Abgeschlossen
In jedem Fall gilt es, Microservices umfassend gegen Angriffe abzusichern. Zunächst einmal gelten hierbei die üblichen Sicherheitsregeln für monolithische Anwendungen. Entsprechend sollte jeder Microservice den von außen eingehenden Daten grundsätzlich misstrauen und beispielsweise problematische HTML-Tags in Texten filtern. Des Weiteren darf eine Firewall lediglich den Zugriff auf den Frontend-Dienst gestatten, keinesfalls jedoch die direkte Kommunikation mit den anderen Microservices. Die wiederum sollten untereinander ausschließlich verschlüsselt kommunizieren.
Darüber hinaus betrifft Authentifizierung nicht mehr nur reale Personen, die Microservices müssen sich ebenfalls gegenseitig vertrauen. Dabei lässt sich unter anderem der OAuth2-Standard nutzen, der Verfahren für die Authentifizierung von Diensten untereinander liefert (Stichwort Client Credentials Grant, CCG). Dabei kommen Token zum Einsatz, die sich die Microservices bei der Kommunikation wie einen Personalausweis vorzeigen.
Fazit
Eine bereits vorliegende Anwendung auf eine Microservice-Architektur umzustellen, kostet viel Zeit und Gehirnschmalz. Die Entwicklung von OCIS dauerte mehrere Jahre. Zum Redaktionsschluss lief immer noch die ausgedehnte Beta-Phase, parallel pflegt das Owncloud-Team seine alte PHP-Anwendung.
Microservices sind zudem nicht immer die erste Wahl. Ein übersichtliches Blog-System beispielsweise lässt sich als monolithische Anwendung deutlich einfacher warten. Der Einsatz von Microservices lohnt sich jedoch bei komplexen Systemen wie Owncloud, die gut skalieren und hochverfügbar sein müssen. In solchen Situationen spielen Microservices als verteiltes System ihre Stärken aus. (csi)
Infos
- Owncloud Infinite Scale (OCIS): https://owncloud.dev/ocis/
- “Python Microservices Development” (ISBN: 978-1-78588-111-4): https://www.schweitzer-online.de/buch/Ziade/Python-Microservices-Development/9781785881114/A42230013/
- Go: https://go.dev
- Asynchrone Programmierung mit Go: Tim Schürmann, “Laufende Parallelen”, LM 09/2022, S. 70, https://www.lm-online.de/48195
- Suture: https://github.com/thejerf/suture
- RabbitMQ: https://www.rabbitmq.com
- “Microservices Patterns” (ISBN: 978-1-61729-454-9): https://www.manning.com/books/microservices-patterns
- Protobuf: https://developers.google.com/protocol-buffers
- Messagepack: https://msgpack.org
- Gin Web Framework: https://gin-gonic.com/docs/
- OpenAPI: https://www.openapis.org
- Go Kit: https://gokit.io
- Go Micro: https://github.com/asim/go-micro
- Zap: https://pkg.go.dev/go.uber.org/zap
- Lumberjack: https://github.com/natefinch/lumberjack
- Logstash: https://www.elastic.co/de/logstash/
- Graylog: https://www.graylog.org
- Prometheus: https://prometheus.io






