Ready, Steady, Go! So gelingt der Einstieg in Googles Programmiersprache
Weil die Programmiersprache Go so einfach und performant ist und außerdem auch noch eine mächtige Standardbibliothek mitbringt, hat sie schnell Fuß gefasst – nicht nur in der Webentwicklung: Zum Beispiel stammen die populäre Containervirtualisierung Docker und die darauf aufbauende Orchestrierungssoftware Kubernetes nahezu vollständig
vom Go-Compiler.
Ursprünglich entwickelte Google Go nur für den Einsatz im eigenen Unternehmen. 2009 stellte das Unternehmen die Sprache jedoch erstmals einer breiteren Öffentlichkeit vor und legte 2012 die erste stabile Version vor.
Der Fokus von Go liegt auf der statischen Typisierung, der Einfachheit und der guten Unterstützung von Netzwerk- und Parallelprogrammierung. Im Gegensatz zu vielen anderen üblichen Web-App-Sprachen kompiliert Go seine Programme vor der Ausführung. Der Go-Compiler übersetzt den Quelltext dabei in nativen Maschinencode, der sich dann ohne weitere Abhängigkeiten oder Laufzeitumgebungen ausführen lässt. Das macht die Installation von Go-Applikationen extrem einfach: Jedes kompilierte Go-Programm besteht aus nur einer Datei, die Entwickler ohne weitere Abhängigkeiten hin- und herkopieren können.
Startschuss für erste Go-Projekte: Den Compiler installieren
Der Compiler lässt sich sehr leicht installieren. Die Go-Installationspakete für alle relevanten Betriebssysteme (Windows, Mac OS und Linux) enthalten den Compiler sowie die Standardbibliothek. Die in Go geschriebene Bibliothek ist sehr umfangreich und enthält die Basisfunktionen von Go. Dazu gehören beispielsweise Krypto-Funktionen, Bibliotheken für die Netzwerkprogrammierung und – für Web-Entwickler interessant – eine HTTP-Server-Bibliothek sowie eine einfache, aber mächtige Template-Sprache. Wichtig ist vor allem: Der Go-Compiler, die Standardbibliothek und alle übrigen Tools brauchen Go-Developer nur für die Entwicklung von Go-Programmen. Für die Ausführung der kompilierten Programme sind sie unnötig und werden nicht gebraucht.
Die Entwicklungsumgebung
Zum aktuellen Zeitpunkt gibt es nur wenige integrierte Entwicklungsumgebungen (IDEs), in der Go offiziell als Sprache unterstützt wird. Eine davon ist Goland (Link: https://www.jetbrains.com/go/), ein Jetbrains-Produkt aus der IntelliJ-Familie, die seid kurzem zur Verfügung steht. Außerdem gibt es Plugin für Visual Studio Code. Dort lässt sich das Plugin über den Menüpunkt „Extensions“ installieren; es findet sich unter dem Namen „Go“.
Erste Schritte
Go-Projekte bestehen stets aus Paketen. Alle Quelldateien in einem Verzeichnis gehören dabei jeweils zu dem Paket, das eine entsprechende Deklaration im Quelltext angibt. Jedes Go-Projekt benötigt ein Paket mit dem Namen main, das zudem eine main()-Methode implementieren muss. Diese ruft Go beim Start als erstes auf. Ein einfaches „Hallo Welt“-Programm könnte in Go also zum Beispiel so aussehen:
package main import "fmt" func main() { fmt.Println("Hallo Welt!") }
Die Syntax von Go orientiert sich an anderen prozeduralen und objektorientierten Sprachen. Da sie jedoch so einfach ist, sollte sie für Entwickler, die bereits mit anderen Programmiersprachen vertraut sind, keine Überraschungen bieten: Jede Go-Quelldatei muss stets mit einer package-Deklaration beginnen. Andere Pakete lassen sich über den import-Befehl importieren. Die Funktionen dieser Pakete (hier etwa fmt.Println) rufen Entwickler dann über den Namen des Pakets auf. Über das func-Schlüsselwort deklarieren sie neue Funktionen.
Im Anschluss lässt sich ein Go-Projekt mit dem Kommandozeilenbefehl go build – oder über die entsprechende Option in der IDE – kompilieren. Um eine Anwendung zu testen, können Entwickler das Programm über go run <dateiname> in einem Schritt ausführen.
Auch das Cross-Compiling ist einfach – wenn also beispielsweise ein Go-Developer eine Software auf Mac OS entwickelt und testet, sie im Produktivbetrieb allerdings auf einem Linux-Server ausführt. Hierzu muss der Entwickler vor dem Kompilieren einfach die Umgebungsvariable GOOS setzen:
$ GOOS=linux go build
Die erste Go-Applikation
Für eine erste, einfache Web-Applikation genügt das Paket
net/http aus der Go-Standardbibliothek. Im Unterschied zu manch anderen im Web gebräuchlichen Programmiersprachen benötigen Go-Anwendungen keinen externen Webserver (wie einen Apache oder NGINX). Sie öffnen stattdessen selbst einen entsprechenden Port und nehmen HTTP-Verbindungen an. Ein Go-Programm lässt sich daher einfach über eine Kommandozeile starten und kann sofort HTTP-Anfragen verarbeiten.
Für die Verarbeitung von HTTP-Anfragen müssen Programmierer zunächst einen „Request Handler“ definieren. Diese Funktion wird für jeden eingehenden Request aufgerufen und nimmt als Parameter einen ResponseWriter und einen Request entgegen. Im ersten Parameter können Go-Entwickler die Antwort auf eine HTTP-Anfrage formulieren. Im zweiten Parameter stehen dann Informationen über die eingehende Anfrage zur Verfügung – beispielsweise die aufgerufene URL, die HTTP-Methode oder die Kopfzeilen der Anfrage. Im einfachsten Fall nutzen Programmierer die Write-Methode, um die HTTP-Anfrage mit einer einfachen Text- oder HTML-Antwort zu beantworten:
func handleRequest(res http.ResponseWriter, req *http.Request) { res.Header().Set("Content-Type", "text/html") res.Write("<h1>Hallo Welt!</h1>") }
In der main-Methode des Programms weisen Go-Entwickler diesen Request Handler über die Funktion http.HandleFunc einem bestimmten Pfad zu. Die Methode http.ListenAndServe startet den eigentlichen HTTP-Server:
package main import "net/http" func handleRequest( // ... func main() { http.HandleFunc("/", handleRequest) http.ListenAndServe(":8080“, nil) }
Der erste Parameter der Funktion ListenAndServe beschreibt dabei den TCP-Port, auf dem die Web-Anwendung Informationen empfängt (in diesem Fall alle Netzwerkschnittstellen auf Port 8080). Das obige Programm lässt sich nach dem Kompilieren mit go build wie gehabt über die Kommandozeile starten.
Da es sich nun um einen langlaufenden Server-Prozess handelt, wird es sich jedoch nicht automatisch beenden, sondern so lange in der Kommandozeile laufen, bis jemand es mit STRG-C wieder beendet. Während der Server läuft, kann man die Applikation beispielsweise im Browser über die URL http://localhost:8080 aufrufen.
Daten in JSON ausgeben
In manchen Fällen reicht es aus, wenn eine Web-Applikation ihre Daten im JSON-Format ausgibt – beispielsweise, wenn sie einen REST-Webservice anbieten soll. Dies ist in Go ebenfalls sehr einfach mit dem Paket encoding/json möglich. Da Go eine statisch typisierte Sprache ist, sollten Entwickler zuvor jedoch eine Datenstruktur definieren, die die JSON-Ausgabe der Applikation beschreibt:
type User struct { FirstName string `json:"first_name"` LastName string `json:"last_name"` Email string `json:"email"` }
Ein „Struct“ beschreibt in Go eine komplexe Datenstruktur, die Attribute verschiedenen Typs und auch Methoden enthalten kann. Es lässt sich ein wenig mit der Klasse einer objektorientierten Sprache vergleichen, unterscheidet sich jedoch in einigen entscheidenden Punkten. Zum Beispiel bieten Structs keine Vererbung und nur eine eingeschränkte Datenkapselung. Das Beispiel oben definiert ein Struct mit drei Attributen, die einen Nutzer beschreiben. Die mit Backticks umschlossenen json:-Anweisungen bestimmen, wie die Anwendung Instanzen dieser Struktur später auf JSON-Objekten abbilden soll. Zu Testzwecken können Go-Einsteiger nun noch einige Instanzen dieses Structs erstellen:
var users = []User{ {"Martin", "Helmich", "martin@example.com"}, {"Max", "Mustermann", "max@example.com"}, }
In einem HTTP-Request Handler nutzen sie anschließend das Paket encoding/json, um ein JSON-Dokument im HTTP-Response zu schicken:
import "encoding/json" import "fmt" // ... func main() { http.HandleFunc("/users", func(res http.ResponseWriter, req *http.Request) { res.Header().Set("Content-Type", "application/json") err := json.NewEncoder(res).Encode(users) if err != nil { res.WriteHeader(500) fmt.Fprintf(res, "Fehler bei JSON-Kodierung: %s", err) } }) // ... }
In diesem Beispiel sieht man eine weitere Besonderheit von Go: In vielen anderen Programmiersprachen erfolgt die Fehlerbehandlung über „Exceptions“, die geworfen und über ein try / catch-Konstrukt wieder gefangen werden müssen. Go behandelt Fehler üblicherweise über Methoden, die einen Fehler als Rückgabewert zurückliefern. Im Beispiel oben ist das etwa die Methode Encode. Beim Aufruf einer Funktion muss die Go-Anwendung dann den (potenziellen) Fehler einer Variable zuweisen und überprüfen. Diese Art der Fehlerbehandlung erfordert zunächst ein wenig Umgewöhnung, steht mit ein bisschen Übung der Nutzung von Exceptions jedoch in nichts nach.
Die Template-Engine nutzen
Wer lieber eine HTML-Ausgabe wünscht, kann das Paket
html/template verwenden:
import "html/template" var users = []User { // ... func main() { http.HandleFunc("/users", func(res http.ResponseWriter, req *http.Request) { t, err := template.ParseFiles("userlist.html") if err != nil { res.WriteHeader(500) fmt.Fprintf(res, "Fehler beim Laden des Templates: %s", err) } else { res.Header().Set("Content-Type", "text/html") t.Execute(res, users) } }) // ... }
In diesem Beispiel verwenden wir die Variable users aus dem vorherigen Beispiel weiter. Das dazugehörige HTML-Template
in der Datei userlist.html könnte dann so aussehen:
<h1>Nutzer</h1>
<ul>
{{range .}}
<li><a href="mailto:{{ .Email }}">{{ .FirstName }} {{ .LastName }}</a></li>
{{end}}
</ul>
Innerhalb des Templates sind spezielle Anweisungen in doppelt geschweiften Klammern untergebracht. Die Variable . gibt dabei Zugriff auf den Wert, den die Execute-Methode beim Aufruf an das Template übergeben hat. In diesem Fall ist dies eine Liste von Nutzern. Der range-Operator erlaubt dann das Iterieren über diese Liste. Innerhalb des range-Operators enthält . nun den jeweiligen Nutzer. .FirstName erlaubt den Zugriff auf die Attribute dieses Nutzers.
Frameworks für Go
Die Go-Standardbibliothek enthält bereits viele Funktionen, mit denen Entwickler auch nicht ganz so triviale Web-Projekte schnell und einfach umsetzen können. Aus diesem Grund entscheiden sich viele Go-Entwickler gegen den Einsatz weiterer
Frameworks. Nichtsdestotrotz gibt es verschiedene Frameworks, die auf den Funktionen der Standardbibliothek aufbauen und diese erweitern. Darunter gibt es auf der einen Seite Full-Stack-Frameworks wie Revel. Sie bieten ein komplettes MVC-Framework sowie zahlreiche weitere Funktionen, etwa zur Authentifizierung, für das Sitzungs-Management, das Caching und vieles mehr.
Auf der anderen Seite gibt es Frameworks wie Echo oder Gin, die einen minimalistischeren Ansatz verfolgen – ähnlich etwa wie Express in der Node.JS-Welt oder Slim für PHP. Diese Frameworks eignen sich besonders gut für kleinere Applikationen oder Microservices, die vielleicht sogar ausschließlich über eine REST-API kommunizieren können.
Neben den klassischen Frameworks gibt es auch noch Funktionsbibliotheken, etwa das Gorilla Toolkit. Diese bieten einzelne Komponenten, die Entwickler zusammen mit der Standardbibliothek nutzen können. So gibt es in diesen Funktionsbibliotheken beispielsweise flexiblere Routing-Komponenten oder eine Komponente zur Sitzungsverwaltung. Installieren lassen sich Frameworks und Bibliotheken ebenfalls über die Go-Standardwerkzeuge. Um beispielsweise das Echo-Frame-
work zu installieren, reicht ein einfacher Aufruf des Werkzeugs go get:
go get github.com/labstack/echo
Anschließend können Go-Entwickler das Paket über import „github.com/labstack/echo“ in ihrem Projekt nutzen.
Fazit
Go erfindet das Rad der Webentwicklung natürlich nicht neu. Und doch: Die Sprache ist so leicht zugänglich, so performant und so typsicher, dass sie immer eine gute Wahl ist, wenn es um hohe Produktivität und eine schnelle, stabile Web-Applikation geht. Ein weiterer Pluspunkt von Go sind die guten Entwicklungswerkzeuge sowie die Vielfalt der Go-Frameworks. Das äußerst vielfältige Go muss sich damit neben Platzhirschen wie PHP, Javascript und Ruby keineswegs verstecken. Wer neugierig ist und einen ersten Einblick in Go bekommen möchte, findet sämtliche Code-Beispiele dieses Artikels auch online.