loading...
Cover image for I'm learning Go the hard way

I'm learning Go the hard way

krystofee profile image Kryštof Řeháček ・5 min read

This is my first blog post ever.

This is not tutorial to Golang, but my story why I chosen to start learning Go and how I do it.

Few days ago, I've decided to implement rest api client for RegioJet (which is one of biggest Czech private carriers, link). My first plan was to write it in Python, which I think I'm getting pretty good at it, but I've already done many rest api integrations and... there is nothing new to be surprised of when doing it again in the same environment.

Goals of this project

  1. Learning how to make web app in Golang.
  2. Having web app which will unify seat and ticket reservation across many transport carriers.

Imagine that you want to take busy train connection at afternoon which is full at the morning. I know many people that are buying tickets a month before. I'm not really that kind of guy. I like to decide when I commute at the earliest one day at the advance or, even better, on that day. If you ever experienced this, you may noticed that trains are sold out that the time you've decided. And if so, then you may notice that some people are likely to sold their tickets few hours before. I would rather say it is a rule than an exception that some people sell their tickets on the day of the trip and if you check the website of the carrier often, you can be lucky to get their seat. This is what I do and ahis also does many other people and if you are not fast enough you won't be the lucky guy.
My vision is that I will choose when and where I want to go and it will handle the reservation of the ticket for me. Yes, it is not 100% reliable that I would be able to get a seat, but it can be handled by some other carriers which do not require seat reservation (means you can ride even if the train is completely sold out).

Why in Go?

I think I'm getting pretty good at web development using Python and Django. Nowadays I have 2 years of experience with Django and DRF on a daily basis (to boast, I recently made a contribution to DRF :) ).
I could definitely make this project in Django without learning anything new, just to have the final product. Getting things done is hard, and the output will be just an app, without learning much new.
So more exciting way to do, will be writing it in a language that I don't know at the time I'm writing this post (except some basics). The development will be much more interesting, and even if I do not finish this project, I will learn a lot.

Let's jump into it

I'will start with building the backend (as expected from this post about my go). My plan is:

  1. write api client package using mainly standard library
  2. choose framework and create web REST backend (Beego or Gin)
  3. create simple frontend in React

The most interesting thing will be first point in my oppinion. Second point will be just creating some endpoints that will interact with my client. And the third point will be just writing a simple frontend in React, which I already know.

My first HTTP requests in Go

I've found this article from Abu Ashraf Masnun which clearly explains how to create simple GET, POST and other types of HTTP requests.
With that knowledge I can try interacting with the RegioJet API for the first time. There is their public api documentation.

The main.go file is our app entrypoint for now. When running go run main.go locations.go, the func main() is called. We call there GetCountries exported from locations.go which will be explained later. And then I loop over each country in returned countries and print their code.

main.go

package main

import (
    "fmt"
    "log"
)

func main() {
    log.Println("Running RegioJet API client")

    var countries = GetCountries()

    for _, country := range countries {
        fmt.Println(country.Code)
    }
}

Later in location.go are defined location structs (Country, City, Station) according to the RegioJet models. The supercool thing about parsing json in golang is that you can define structs and then use json.Unmarshall(data []byte, v interface{}) error to parse the json. I find is super useful, because the result is already typed and you immidiately know what you are working with. (I'm not used to this since I work primarily with Python and Javascript, where both of them are loosely typed languages)

locations.go

package main

import (
    "encoding/json"
    "io/ioutil"
    "log"
    "net/http"
)

var endpointUrl = "https://brn-ybus-pubapi.sa.cz/restapi/consts/locations"

type Station struct {
    ID                 string `json:"id"`
    Name               string `json:"name"`
    Fullname           string `json:"fullname"`
    Aliases            string `json:"aliases"`
    Address            string `json:"address"`
    StationTypes       string `json:"stationTypes"`
    IataCode           string `json:"iataCode"`
    StationURL         string `json:"stationUrl"`
    WheelChairPlatform string `json:"wheelChairPlatform"`
    Significance       string `json:"significance"`
}

type City struct {
    ID            string    `json:"id"`
    Name          string    `json:"name"`
    Aliases       []string  `json:"aliases"`
    StationsTypes []string  `json:"stationTypes"`
    Stations      []Station `json:"stations"`
}

type Country struct {
    Country string `json:"country"`
    Code    string `json:"code"`
    Cities  []City `json:"cities"`
}

type Countries []Country

func GetCountries() Countries {
    log.Println("... getting countries")

    resp, err := http.Get(endpointUrl)
    if err != nil {
        log.Fatalln(err)
    }

    defer resp.Body.Close()

    byteBody, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Fatalln(err)
    }

    var countries Countries
    err := json.Unmarshal(byteBody, &countries)
    if err != nil {
        log.Fatalln(err)
    }

    return countries
}

You may notice that I name all struct members explicitly for json even If I'm not supposed to, because it's taken by convention by the struct member name, so for example Country.Code without explicit json:"code" will match code or even cOdE. But I consider naming it this way as good practise and prevention from large refactoring, because all struct members are not dependent on the keys received in the JSON response.

Last but not least, let's run the code I've shown above...

Output:

$ go run locations.go main.go
2019/07/08 22:19:50 Running RegioJet API client
2019/07/08 22:19:50 ... getting countries
DE
BE
CH
LU
HR
IT
FR
UA
HU
AT
UK
CZ
SK
PL
RO
NL

Result is as expected country code per line.


Many new things to learn and many things learned. My first experience with golang was more than pleasing. I thought of it as more C-like language without any knowledge of it. This was really smooth.

I will skip the implementation of other endpoints and directly jump into how I query fetched data and I will try to think about the core functionality like getting free seats and trying to make seat reservation.

Discussion

markdown guide