Techblog

Golang: Alternative für Microservices?

Von Anton Stöckl
21. Januar 2020

Der Kontext dieses Artikels

Der Vergleich von Programmiersprachen ist knifflig und oft unfair, vor allem wenn der Kontext außer Acht gelassen wird. In diesem Text werde ich mich bei der Bewertung von Golang auf den Kontext konzentrieren, in dem ich in den letzten Jahren mit dieser Sprache gearbeitet habe: Unternehmenssoftware in einer Microservice-Umgebung.

Um die Frage zu beantworten, wie micro Micro ist: Ich habe hauptsächlich in einem Umfeld gearbeitet, in dem das Konzept des Bounded Context aus Domain-driven Design, kurz DDD, angewendet wird. Dabei werden Bounded Contexts sorgfältig geschnitten, so dass zum Beispiel Entitäten, welche ihre Transaktions- und Konsistenzgrenzen schützen müssen, nicht auf mehrere Dienste verteilt werden.

Ich schreibe in diesem Artikel darüber, warum und wann Golang eine gute Wahl sein kann. Es werden nur Vergleiche mit anderen Sprachen und Ökosystemen angestellt, um relevante Unterschiede zu betrachten - explizit nicht um auszusagen, dass Golang per se die bessere Wahl wäre.

Einfachheit und Minimalismus

Golang ist eine minimalistische Sprache, die auf Einfachheit ausgerichtet ist. Sie bietet eine begrenzte Anzahl von Konzepten zur Implementierung von Anwendungen, dadurch ist sie relativ schnell zu erlernen. Go macht es dadurch auch einfacher, Code mit geringer Komplexität und hoher Qualität, Stabilität und Lesbarkeit zu schreiben. Entwickler, die aus OOP-Sprachen wie Java, C# oder PHP kommen, sind in der Regel schnell mit Go vertraut.

Golang ist nicht so ausdrucksstark wie andere Sprachen, was es etwas schwieriger macht, ausdrucksstarken Code zu schreiben. Andererseits gibt es neben der Sprache andere Faktoren für die Ausdrucksstärke einer Anwendung z.B. die Struktur (Anwendungsarchitektur), welche Abstraktionen verwendet werden oder wie die Artefakte (Variablen, Objekte, Methoden) benannt werden, um nur einige bekannte Punkte zu nennen.

Go kennt keine (echte) Vererbung, keine generics, keine Annotationsmagie, keine impliziten Typkonvertierungen, ist aber stark statisch typisiert mit einfacher Typinferenzen in Zuweisungen. Strukturell typisierte interfaces bieten Polymorphie zur Laufzeit via dynamic dispatch.

Packages bieten eine klare Codetrennung und es gibt nur zwei primäre Sichtbarkeitskonzepte: exportiert (öffentlich) oder nicht exportiert (privat innerhalb des Packages). Das einzige Konzept, das mir im Vergleich zu anderen Sprachen wirklich fehlt, ist die Möglichkeit, ganze Typen oder einzelne Eigenschaften read-only zu definieren, um damit zum Beispiel das Konzept der immutability implementieren zu können.

(Für diesen Absatz habe ich mich teilweise von dem am Ende verlinkten Artikel "Why Go? – Key advantages you may have overlooked" inspirieren lassen!)

Werkzeuge und Ökosystem

Golang bringt integrierte Frameworks zum Testen und sogar zum Profilieren mit. Es gibt viele nützliche Add-ons, vor allem zum Testen, und ich verwende fast immer einige davon (siehe Links an Ende des Artikel). Mein derzeit favourisiertes Testframework ist GoConvey. Zum Erzeugen von Mocks aus Interfaces verwende ich meist Mockery.

Mit godoc (extern) hat man schnell Dokumentation generiert, mittels gofmt und goimport (beide Bestandteil von Go) kann man den Code gemäß dem recht strikten Go-Codestil formatieren. Dieser strikte Stil begrenzt auch Stildiskussionen innerhalb des Teams, was wiederum code reviews vereinfacht.

Was ich an der Arbeit mit Go sehr schätze, ist das Fehlen omnipotenter (Web-)Frameworks und ORMs. Diese bringen oft einiges an unbeabsichtigter Komplexität mit sich. Damit kann es schwierig werden, die Infrastruktur von der Geschäftslogik im Sinne von hexagonaler Architektur (auch bekannt als Ports & Adapters) zu entkoppeln. Stattdessen gibt es eine Reihe (etwa) gleichermaßen beliebter Frameworks mit überschaubarem Funktionsumfang und akzeptabler Komplexität.

Das integrierte net/http-Paket reicht oft schon aus, um Web-Endpunkte zu erstellen. Oft kombiniert mit Bibliotheken wie gorilla/mux für das Routing. Es gibt auch einige ORMs, hier kann ich aber keine Erfahrungen teilen, da ich kein großer Fan von ORMs bin - egal für welche Programmiersprache. Die Gründe dafür würden einen weiteren Blog-Artikel erfordern.

Teams, die Tools für die statische Code-Analyse verwenden wollen, können sich die umfangreiche GolangCI-Lint Bibliothek ansehen, die viele Linter einbindet, hochgradig konfigurierbar ist und in eine CI/CD Pipeline integriert werden kann.

Performance von Golang

Bei der Kompilierung

Ich konnte keine Benchmarks finden, die die Kompilierungsgeschwindigkeit von Golang mit anderen Sprachen vergleichen. Meiner persönlichen Erfahrung nach ist die Kompilierung hinreichend schnell: Ich warte nie wirklich auf das Ende eines builds, wenn ich z.B. Tests ausführe oder einen Dienst ohne vorherige Kompilierung in meiner lokalen Entwicklungsumgebung starte. Auch hier ist der Kontext wichtig: Es ist ein großer Unterschied, ob man einen riesigen Monolithen oder einen (relativ) kleinen Dienst kompiliert, genau wie bei anderen kompilierten Sprache.

Nicht repräsentativ von mir gemessen:

  • Manjaro Linux, das als virtuelle Maschine auf meinem Windows Laptop läuft
  • Intel® Core™ i7-8850H CPU @ 2.60GHz × 6, ca. 10GB Speicher für VM
  • Go version 1.13
  • gut 7100 Codezeilen
  • Kompilierungszeit: knapp 2 Sekunden

Zur Laufzeit

Es gibt allerhand Benchmarks, die die Leistung von Programmiersprachen vergleichen. Sie alle müssen mit Vorsicht betrachtet werden, denn sie können voreingenommen oder für den Kontext einer Anwendung irrelevant sein. Meiner Erfahrung nach tritt bei Anwendungen, bei denen viele Datenbankabfragen oder Anfragen an andere Services über Netzwerk stattfinden, die reine Laufzeitperformance der Programmiersprache stark in den Hintergrund.

Dennoch habe ich vier Beispiele mit unterschiedlichen Ergebnissen für Go ausgewählt.

Go vs. Java: Hier ist Go ungefähr gleichauf.

Go vs. Python: In diesem Vergleich gewinnt Go.

Go vs. Node js: Wieder gewinnt Go.

Go vs. Rust: Dieser Punkt geht an Rust.

Einige Kommentare zu diesen Ergebnissen:

Python ist in diesem Vergleich die einzige interpretierte Sprache, daher ist sie nachvollziehbar langsamer als kompilierte Sprachen.

Wie bereits erwähnt, ist die reine Leistung der Programmiersprache hauptsächlich für rechenintensive Anwendungen relevant, jedoch nicht so sehr, wenn die Dienste die meiste Zeit auf Antworten einer Datenbankanfrage warten.

Zudem ist es oft nicht sinnvoll, auf Leistungskennzahlen zu optimieren, die für die jeweilige Anwendung gar nicht benötigt werden (siehe: premature optimization). Andererseits kann es in Cloud-basierten Microservice-Umgebungen ein großer Kostenvorteil sein, wenn man nur 10 statt 50 Instanzen eines Dienstes starten muss.

Ein weiterer Punkt, der dabei manchmal übersehen wird: Wie lange dauerte es, die Testsuite auf lokalen Entwickler-Rechnern oder auf dem CI-Server auszuführen? Schließlich kostet es langfristig viel Zeit, wenn Entwickler regelmäßig zu lange auf Testergebnisse warten müssen!

Vor diesem Hintergrund empfehle ich, die Laufzeitperformance zwar als einen wichtigen Faktor für die Wahl der Programmiersprache für Anwendungen zu betrachten, gleichzeitig ist es aber nur eine von vielen Heuristiken für eine gute Entscheidung. Was diesen Punkt angeht, ist Golang definitiv gut im Rennen!

Nebenläufigkeit

Nebenläufigkeit, englisch concurrency, ist ein integraler Bestandteil von Go. Dieses Konzept kann mittels Go-Routinen (goroutine) umgesetzt werden. Kanäle (channel) unterstützen die Synchronisation mehrerer Go-Routinen. Es ist nicht allzu kompliziert, mittels dieser beiden Bestandteile Muster wie Produzent/Verbraucher zu implementieren.

Da ich viele Jahre mit PHP-Entwicklung verbracht habe (das keine Nebenläufigkeit mitbringt), fand ich es beim Umstieg auf Golang nicht sehr schwierig, nebenläufige Anwendungen zu implementieren. Natürlich liegt der Teufel im Detail, Nebenläufigkeit ist nun mal nicht trivial!

Ausführbarer Beispielcode: ein einfaches Produzenten-/Verbraucherprogramm

Das gleiche nochmal mit parallelen Verbrauchern

Fazit

Ich würde vorschlagen, einen genaueren Blick auf Golang zu werfen, wenn:

  • Sie eine monolithische Anwendung zu Microservices refaktorieren wollen
  • die aktuelle Anwendung - oder Teile davon - echte Performance-Probleme haben, welche auf die verwendete(n) Programmiersprache(n) zurückzuführen sind
  • die aktuelle Anwendung viele (Cloud) Server benötigt, um die Last zu bewältigen, und dadurch zu hohe Kosten beim Hosting verursacht
  • neue Dienste keine mächtigen Web-Frameworks benötigen, sondern nur relativ einfache REST- oder RPC-Endpunkte bereitstellen sollen
  • für Sie die Einfachheit mehr Nutzen bringt, als die unbegrenzten Mögligkeiten manch anderer Programmiersprachen und deren Ökosysteme

Weiterführende Lektüre und erwähnte Bibliotheken

Neuen Kommentar schreiben

Public Comment form

  • Zulässige HTML-Tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd><p><h1><h2><h3>

Plain text

  • Keine HTML-Tags erlaubt.
  • Internet- und E-Mail-Adressen werden automatisch umgewandelt.
  • HTML - Zeilenumbrüche und Absätze werden automatisch erzeugt.

ME Landing Page Question