Niedawno zacząłem interesować się językiem programowania Go. Znalazłem wiele interesujących informacji na jego temat, a także postawiłem w nim pierwsze kroki, pisząc bardzo prostą aplikację. Poniżej opisuję jaką miałem motywację by sprawdzić Go, krótko przedstawiam czym jest ten język, jakie były moje pierwsze wrażenia, jakie ma zastosowania, jak napisać prostą aplikację, jak wygląda jego popularność oraz czego można się od niego nauczyć.

Dlaczego zainteresowałem się Go?

Na początek chciałbym przedstawić skąd w ogóle pomysł poznania Golang. W ostatnim czasie bardzo popularne stały się mikroserwisy. Często możemy usłyszeć, że w swoich aplikacjach jesteśmy bardzo niezależni i możemy korzystać z modułów napisanych w różnych językach programowania. To budzi chęć do spróbowania czegoś nowego. Z tym że nie brzmi to jak pragmatyczne podejście do tematu. Dlatego pomyślałem, że przynajmniej powierzchownie postaram się poznać inne technologie, poszerzyć horyzonty, poznać nowe narzędzia, a może część elementów postarać się przenieść do mojej głównej technologii, czyli Javy.

Na początku spróbowałem poznać język Scala, który jest potężny i wraz z nim zaznajomiłem się trochę konceptami związanymi z językami funkcyjnymi, które coraz częściej zaczynają być stosowane. Z Golangiem natomiast udałem się na przeciwny biegun, bo o ile Scala daje wolność, a co za tym idzie wymaga większej odpowiedzialności, to Golang jest o wiele bardziej restrykcyjny i prosty, służący do wykonania określonych zadań i chyba w tym drzemie jego siła.

Ciekawe jest również to, że czytając przeróżne nowinki IT prędzej napotkamy informacje o Node.js, Reactive Programming, Scala niż o Golangu, który według mnie wkrada się do powszechnego użycia nieco tylnymi drzwiami. Wielu programistów zaczyna korzystać z narzędzi napisanych w Go jak Docker, Kubernetes, Consul i na bazie dobrych doświadczeń sami zaczynają próbować swoich sił w Go. Dość późno zaczynam przygodę z Go, ponieważ na początku gdy się mu przyjrzałem wydał mi się prymitywny i bardzo ubogi wobec np. Scali. Ale być może to jest jego zaleta? Podsumowując, szukam odpowiednich narzędzi do odpowiednich zadań.

Czym jest język Go?

Język Go ujrzał światło dzienne w 2009 roku za sprawą firmy Google. Próbując go w którymś miejscu umiejscowić stwierdziłbym, że leży gdzieś pomiędzy językiem C a Java, będąc bliżej tego pierwszego. Przekładanie koncepcji OOP do niego raczej nie ma większego sensu. W dużej mierze Go kieruje się myślą przewodnią - “preferuj kompozycję ponad dziedziczenie”. Według mnie od samego początku w języku Go czuć ideę pragmatyzmu i próbę utrzymania wszystkiego w jak największej prostocie, która czasem jest mylona z wygodą.

Golang ma być również szybki, łatwy w nauce, zwiększać produktywność programistów, a projekty w nim napisane nie mają opierać się na "magii". Bardzo często można spotkać się ze zdaniem, że gdy programista pierwszy raz zaczyna pracę nad projektem Go, to natychmiast wie, co się w nim dzieje, nie trzeba poświęcać wiele czasu na konfigurację czy wdrożenie nowej osoby. W większości przypadków sam język powinien być wystarczającym narzędziem by wykonać odpowiednie zadania. Sugeruje się raczej, żeby używać prostych bibliotek niż frameworków, ale jeśli jest to konieczne to również jest ich pod dostatkiem.

Mimo że Go nie jest jeszcze zbyt popularne to posiada bardzo dobrą społeczność i wiele bibliotek open source. Najważniejszą cechą języka Go oprócz prostoty jest model współbieżności (oparty o CSP), który jest dużo łatwiejszy niż w wielu innych językach. Niskim kosztem mamy uzyskać wydajne rozwiązania. Jeśli poszukamy testów wydajności języka Go, to zazwyczaj okupuje on pierwsze miejsca, zachowując przy tym solidność, rzadko z kolei ulegając awariom. Sam język jednak raczej nie posiada nic szczególnie innowacyjnego. Większość można spotkać w innych językach programowania. Go jednak umiejętnie "pożycza" i ma szacunek do starszych kolegów po fachu.

Ogólne wrażenia

Moim pierwszym pomysłem na to co napisać w języku Go, żeby oswoić się z tą technologią był prosty szkielet serwera aplikacji webowej - CRUD z połączeniem do bazy danych, bez żadnego frontendu. Aplikacja do pobrania z Github. Zachęcam do Code Review oraz składania Pull Request'ów.

Wystartować można bardzo szybko, w sieci możemy znaleźć mnóstwo przykładów, również oficjalny tutorial jest bardzo przyjemny. Na początku moją uwagę zwrócił fakt, że nie potrzebuję żadnych dodatkowych narzędzi do napisania tego, co potrzebuję, ostatecznie zdecydowałem się na kilka prostych bibliotek (gorilla/mux, sqlx, pkg.errors oraz testify), którym daleko do frameworków. Kolejna rzecz to jak szybko uruchamia się nasza aplikacja, w zasadzie od razu. Bardzo mnie również cieszy, że razem z biblioteką standardową dostajemy narzędzie do prostego testowania i benchmarkowania aplikacji. Na mój gust do pisania asercji jednak warto zaciągnąć zewnętrzną bibliotekę. Uruchamianie testów to czysta przyjemność, trwa to bardzo krótko i daje szybką informację zwrotną. Od razu pomyślałem, że bardzo dobrze nadaje się to do programowania w stylu Test Driven Development.

Jeśli chodzi o narzędzia wokół języka Go to według mnie mamy dobry wybór, od edytorów tekstowych jak Vim, po Intellij IDEA z odpowiednią wtyczką. Mimo wszystko czuć, że język ten był tworzony z myślą o terminalu. Go oferuje nam również minimalne środowisko developerskie, które pozwala utrzymać projekt w ryzach i jest to np. formatowanie kodu, sprawdzanie jego stylu czy zarządzanie zależnościami. Generalnie pierwsze wrażenia są pozytywne, a próg wejścia stosunkowo niski.

Jak napisać prostą REST aplikację w Go?

Być może ktoś z Was był kiedyś w sytuacji gdy trzeba było napisać niedużą, ale wydajną usługę "na wczoraj", która będzie przyjmowała bardzo dużą liczbę żądań http? Myślę, że w takiej sytuacji język Go może przyjść z pomocą. Po przejściu poniższych kroków uświadomisz sobie, że development z użyciem języka Go jest wygodny, szybko można stać się produktywnym. A oprócz tego niewielkim kosztem otrzymujemy wydajne rozwiązanie.

Po zainstalowaniu języka Go możemy przystąpić do pracy. Napiszemy bardzo prostą usługę, która będzie zwracać obiekt typu json do przeglądarki. Potrzebne będą nam do tego dwie struktury. Person z atrybutami id oraz name, a także People, który będzie przetrzymywał ich listę.

type Person struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

type People []Person
 

Następnie tworzymy najprostszy REST handler. Ustawiamy mu potrzebne nagłówki, dla ułatwienia przykładu uzupełnimy listę People w tym miejscu i za pomocą json.NewEncoder z pakietu encoding/json zwrócimy dane do przeglądarki. Obsłużymy też błąd, jeśli wystąpi. Jak można łatwo się domyślić json.NewDecoder działa w drugą stronę i służy do odczytania json body z żądania.

func getPersonByIDHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    people := People{
        Person{ID: 1, Name: "Janusz"},
        Person{ID: 2, Name: "Bożena"},
    }
    if err := json.NewEncoder(w).Encode(people); err != nil {
        http.Error(w, "something went wrong", http.StatusInternalServerError)
    }
}
 

A w naszej metodzie main przy pomocy net/http ustawiamy pod jakim adresem nasz handler będzie obsługiwany oraz na jakim porcie nasz serwer ma startować:

func main() {
    startServer()
}

func startServer() {
    http.HandleFunc("/rest/people/", getPersonByIDHandler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}
 

Gotowe. Uruchamiamy naszą aplikację np. z Intellij IDEA, lub budując aplikację poleceniem go install i uruchamiamy z terminala. Wchodzimy w przeglądarce na adres localhost:8080/rest/people/ i naszym oczom ukazuje się nasza lista:

[
  {
    "id": 1,
    "name": "Janusz"
  },
  {
    "id": 2,
    "name": "Bożena"
  }
]
 

Parę linijek kodu i serwer uruchamia się błyskawicznie. Możemy napisać test do naszego handlera, korzystając z httptest z biblioteki standardowej:

func TestGetPersonByIDHandlerHandler(t *testing.T) {
    req, err := http.NewRequest("GET", "/rest/people/", nil)
    if err != nil {
        t.Fatal(err)
    }
    rr := httptest.NewRecorder()
    handler := http.HandlerFunc(getPersonByIDHandler)
    handler.ServeHTTP(rr, req)

    if status := rr.Code; status != http.StatusOK {
        t.Errorf("Wrong status code: got %v want %v",
            status, http.StatusOK)
    }
    var people []Person
    json.NewDecoder(rr.Body).Decode(&people)
    assert.Len(t, people, 2)
}
 

W łatwy sposób również możemy napisać REST klienta:

func getPeople() (People, error) {
    resp, err := http.Get("http://localhost:8080/rest/people/")
    if err != nil {
        return nil, err
    }
    var people []Person
    if err := json.NewDecoder(resp.Body).Decode(&people); err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    return people, nil
}
 

Możemy na przykład wywołać go w teście, używając biblioteki testify, a serwer startując przy pomocy metody init() i goroutine, pozwoli to nam uruchomić serwer w tle:

func init() {
    go startServer()
}

func TestGetPersonByIDHandlerHandler(t *testing.T) {
    people, err := getPeople()
    if err != nil {
        t.Fatal(err)
    }
    assert.Len(t, people, 2)
}
 

Nasze dane musimy gdzieś przechowywać dlatego możemy połączyć naszą aplikację z bazą danych, użyłem MySQL, język Go jednak bez problemu działa z innymi bazami danych za pomocą odpowiednich driver'ów. Załóżmy, że bazę zainstalowaliśmy i działa pod adresem localhost:3306, parę rzeczy musimy sobie przygotować. Korzystam z biblioteki sqlx, która rozszerza możliwości biblioteki standardowej database/sql.

import (
    "log"

    _ "github.com/go-sql-driver/mysql" // rejestracja drivera db
    "github.com/jmoiron/sqlx"
)
const (
    sqlConnection = "admin:Admin.123@tcp(localhost:3306)/people_db?charset=utf8"
    mysql         = "mysql"
)

type Person struct {
    ID   int    `db:"id"`
    Name string `db:"name"`
}

type PeopleAPI struct {
    db *sqlx.DB
}

func NewPeopleAPI() (*PeopleAPI, error) {
    db, err := sqlx.Connect(mysql, sqlConnection)
    if err != nil {
        return nil, err
    }
    return &PeopleAPI{db: db}, nil
}

 

Następnie konkretne zapytania do bazy danych. Poniżej przykład zapytania Select oraz Insert. Pierwsze zwraca listę People a drugie tworzy nowy wpis w bazie i zwraca ID nowego wpisu. Dla ułatwienia tylko loguję błędy, jednak te błędy w kodzie produkcyjnym koniecznie trzeba obsłużyć.

func getPeopleFromDB(p *PeopleAPI) []Person {
    var people []Person
    if err := p.db.Select(&people, "SELECT * FROM people"); err != nil {
        log.Print(err)
    }
    return people
}

func createPersonInDB(p *PeopleAPI, name string) int {
    result, err := p.db.Exec("INSERT into people (name) VALUES (?)", name)
    if err != nil {
        log.Print(err)
    }
    lastID, err := result.LastInsertId()
    if err != nil {
        log.Print(err)
    }
    return int(lastID)
}
 

Następnie możemy wywołać funkcje, które zwrócą nam odpowiednie rezultaty lub podłączyć je pod nasze REST handlery, wtedy będziemy mogli wysyłać żądania do naszego prostego serwera i otrzymywać wyniki z bazy danych. Ważne jest to, żebyśmy tylko raz inicjowali PeopleAPI. Cały kod tutaj.

func main() {
    db, err := NewPeopleAPI()
    if err != nil {
        log.Fatal(err)
    }
    createPersonInDB(db, "Heniek")
    people := getPeopleFromDB(db)
    log.Print(people)
}
 

Oczywiście po raz kolejny możemy to łatwo przetestować.

package main

import (
    "fmt"
    "log"
    "testing"

    "github.com/stretchr/testify/assert"
)

var DBAccess *PeopleAPI

func init() {
    db, err := NewPeopleAPI()
    if err != nil {
        log.Fatal(fmt.Errorf("FATAL: %v", err))
    }
    DBAccess = db
}

func TestGetPeople(t *testing.T) {
    createPersonInDB(DBAccess, "Marek")
    createPersonInDB(DBAccess, "Zdzisiek")
    people := getPeopleFromDB(DBAccess)
    assert.Len(t, people, 2, "Expected size is 2")
}

 

Należy zauważyć, że pokazane tutaj testy to testy integracyjne. Uruchamianie ich w Intellij IDEA jest jednak banalnie proste i bardzo szybkie, bardziej przypomina testy jednostkowe. W języku Go piszemy szybko, a działa on jeszcze szybiej.

Jakie Go ma zastosowania?

Język Go możemy obecnie spotkać wszędzie tam, gdzie wymagana była obsługa bardzo wielu żądań na sekundę. Na chwilę obecną z korzystają z jego możliwości: (oczywiście) Google, Amazon, Apple, Facebook, Intel, Netflix, Bitbucket, GitHub, Mozilla i wiele innych. Gorąco zachęcam do prześledzenia przykładów (użycia) aplikacji napisanych w Golangu, wiele z nich mówi sama za siebie.

  • Malwarebytes: obsługa miliona żądań na sekundę w systemie zbierającym dane analityczne
  • Monzo/Mondo: bankowa infrastruktura w oparciu o Go
  • Allegro: bardzo szybkie narzędzie o nazwie BigCache
  • Uber: 'Geofence, high query per second service' oraz wiele core services
  • Sourcegraph: 'fast, global, semantic code search and cross-reference engine', można sprawdzić jak to działa po zainstalowaniu pluginu do przeglądarki
  • Dropbox: duża część infrastruktury przeniesiona została na Go
  • Riot Games, League of Legends: zbieranie statystyk graczy komputerowych
  • Docker, tego narzędzia przedstawiać nie trzeba

Ostatecznie zidentyfikowałem, że język Go posiada następujące zastosowania:

  • Serwery aplikacji webowych (Backend)
  • Mikroserwisy
  • Narzędzia linii poleceń
  • Automatyzacja zadań
  • Devops
  • Programowanie współbieżne
  • Aplikacje o bardzo dużym ruchu sieciowym
  • Programowanie systemowe
  • Aplikacje wymagające działania w czasie rzeczywistym

Popularność języka Go na chwilę obecną

Popularność Go zdecydowanie wzrasta, szczególnie widać to w 2016 roku, prawdopodobnie za sprawą popularyzacji narzędzi takich jak Docker. Liczba przydatnych aplikacji i bibliotek napisanych w Go jest całkiem duża: GoGogs, Kubernetes, Chaos Monkey, Terraform, Consul itd. Wypada on też bardzo dobrze w wielu rankingach wśród programistów, takich jak ‘ulubiony język programowania’ czy ‘jakiego języka mam zamiar się uczyć w najbliższym czasie’. Jego popularność jest jednak znacznie większa na zachodzie niż w Polsce. Liczba ofert pracy jest bardzo niska. Według mnie o niczym to nie świadczy, ponieważ pracodawcy są świadomi, że znaleźć takich specjalistów na rynku pracy będzie trudno. Język Go raczej nie będzie stanowił problemu dla osób znających języki takie jak Java, C#, C++ itp.

Czego możemy nauczyć się od Go?

Przede wszystkim prostoty i pragmatyzmu. Dobrze jeśli w projekcie większość rzeczy jest oczywista a nowym osobom jest łatwo rozpocząć z nim pracę. Być może też do pewnych zadań np. do napisania czegoś małego, wydajnego i nietrudnego nie zawsze warto zaciągać do pracy wielkie frameworki pełne wewnętrznej "magii"? Może szybki i mało zasobożerny Go będzie wystarczający dla naszych potrzeb? W pracy chcemy się kierować wymyślnymi aspektami technologii czy raczej chcemy szybko osiągnąć efekt niskim nakładem pracy? Szukając informacji o Go napotkamy również kwestię tego, jak wyglądają mikroserwisy w wielkich systemach informatycznych. Są to często polyglot microservices. Każda technologia ma swoje odpowiednie zastosowanie, Golang też znajduje tam swoje miejsce.

Podsumowanie

Polecam każdemu spróbować Go i wyrobić sobie o nim opinię. Według mnie warto. Oczekuję, że Go będzie piął się w rankingach i że wiele ciekawych projektów jeszcze w nim powstanie. Należy też zauważyć, że Golang z wersji na wersję jest coraz lepszy i szybszy. Mam nadzieję, że w niedalekiej przyszłości będę mógł wziąć udział w projekcie napisanym w Go, jeśli będzie miało to uzasadnione zastosowanie. Pomyślę również nad działalnością open source, ponieważ istnieje wiele takich projektów, które wydają się interesujące i kuszą, żeby do nich dołączyć. Golang również trafia w takie moje branżowe zainteresowania jak mikroserwisy i aspekty związane ze skalowalnością. Na zakończenie polecam zapoznać się z Go Proverbs, CodeReviewComments, Solid Go Design oraz What Golang Is and Is Not. Zachęcam też do napisania swojego pierwszego Hello World w Go Playground.

Wykorzystana w artykule grafika pochodzi z projektu: https://github.com/gengo/goship