---
# System prepended metadata

title: Go-Training

---

# Go-Training

**[Feedbackformular](https://www.it-visions.de/Extranet/T/B?D1E58C2F-7E2E-4AE2-BFA8-FF215124B9EE)**

[rainer@software-architects.at](mailto:rainer@software-architects.at)

## Library

* [Figlet](https://github.com/mbndr/figlet4go): `github.com/mbndr/figlet4go`

## Links

* [https://gobyexample.com/](https://gobyexample.com/)
* [Build automation tools](https://github.com/avelino/awesome-go#build-automation)
    * [taskfile.dev](https://taskfile.dev/)
* [Lab: Turmrechnen](https://github.com/rstropek/golang-samples/tree/master/labs/turmrechnen)
* [Lab: Math Pyramid](https://github.com/rstropek/golang-samples/tree/master/labs/math-pyramid)
* [PokeAPI](https://pokeapi.co/)
    * Package `github.com/ozankasikci/go-image-merge`

    ```go
    type pokemon struct {
        Sprites pokemonSprites `json:"sprites"`
    }

    type pokemonSprites struct {
        BackDefault      string `json:"back_default"`
        BackFemale       string `json:"back_female"`
        BackShiny        string `json:"back_shiny"`
        BackShinyFemale  string `json:"back_shiny_female"`
        FrontDefault     string `json:"front_default"`
        FrontFemale      string `json:"front_female"`
        FrontShiny       string `json:"front_shiny"`
        FrontShinyFemale string `json:"front_shiny_female"`
    }

	go getImage(pokemonData.Sprites.BackDefault, images, wg)
	go getImage(pokemonData.Sprites.BackFemale, images, wg)
	go getImage(pokemonData.Sprites.BackShiny, images, wg)
	go getImage(pokemonData.Sprites.BackShinyFemale, images, wg)
	go getImage(pokemonData.Sprites.FrontDefault, images, wg)
	go getImage(pokemonData.Sprites.FrontFemale, images, wg)
	go getImage(pokemonData.Sprites.FrontShiny, images, wg)
	go getImage(pokemonData.Sprites.FrontShinyFemale, images, wg)

	grids := make([]*gim.Grid, 0)
	for img := range images {
		grids = append(grids, &gim.Grid{Image: img})
	}

	rgba, err := gim.New(grids, 2, int(math.Ceil(float64(len(grids))/float64(2)))).Merge()
	if err != nil {
		panic(err)
	}
    ```
* [Mockery](https://vektra.github.io/mockery/)
* [Testify](https://github.com/stretchr/testify)
* [autorest](https://github.com/Azure/autorest)
* [validator](https://github.com/go-playground/validator)
* Person-struct für API

    ```go
    type Person struct {
        ID         int        `json:"id"`
        FirstName  string     `json:"first_name"`
        MiddleName string     `json:"middle_name,omitempty"`
        LastName   string     `json:"last_name"`
        Department Department `json:"department"`
    }

    type Department struct {
        ID   int    `json:"id"`
        Name string `json:"name"`
    }
    ```
* [HTTP Status Cats](https://http.cat/)
* [RFC 7807 - Problem Details](https://www.rfc-editor.org/rfc/rfc7807)
* [Gin Framework](https://gin-gonic.com/)
* [Fiber Framework](https://github.com/gofiber/fiber)
* [Go Standard Project Layout](https://github.com/golang-standards/project-layout)
* [Hero Manager](https://github.com/rstropek/golang-samples/tree/master/hero-manager)
* [Seq](https://datalust.co/seq)
* [Adapter-Beispiel mit interfaces und funcs](https://go.dev/play/p/MP2v8p_uX_M)
* [Let's go further](https://lets-go-further.alexedwards.net/)
* [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md)
* [OPA - Open Policy Agent](https://www.openpolicyagent.org/)
* [migrate](https://github.com/golang-migrate/migrate)

## Live Coding Samples

### Struct

```go
package main

import (
	"encoding/json"
	"fmt"
)

type Point struct {
	// Note: Well-known struct tags
	X float64 `json:"x"`
	Y float64 `json:"y"`
}

func NewPoint(x, y float64) Point {
	return Point{X: x, Y: y}
}

type Rect struct {
	LeftUpper, RightLower Point
}

func (r Rect) Width() float64 {
	return r.RightLower.X - r.LeftUpper.X
}

func (r Rect) Height() float64 {
	return r.RightLower.Y - r.LeftUpper.Y
}

func (r Rect) Area() float64 {
	return r.Width() * r.Height()
}

func (r *Rect) Enlarge(factor float64) {
	r.RightLower.X = r.LeftUpper.X + r.Width()*factor
	r.RightLower.Y = r.LeftUpper.Y + r.Height()*factor
}

type Shape interface {
	Area() float64
}

func main() {
	p := NewPoint(1, 2)
	fmt.Printf("%v/%v\n", p.X, p.Y)

	r := Rect{LeftUpper: Point{X: 1, Y: 2}, RightLower: Point{X: 3, Y: 4}}
	fmt.Printf("%v has an area of %v\n", r, r.Area())
	r.Enlarge(2)
	fmt.Printf("%v has an area of %v\n", r, r.Area())

	c := Circle{Center: Point{X: 1, Y: 2}, Radius: 3}
	fmt.Printf("%v\n", c)

	jsonString, _ := json.Marshal(p)
	fmt.Printf("%s\n", jsonString)

	cc := ColoredCircle{Circle: Circle{Center: Point{X: 1, Y: 2}, Radius: 3}, Color: Red}
	fmt.Printf("Area %v, %v\n", cc.Area(), cc.Radius)

	shapes := []Shape{r, c, cc}
	for _, s := range shapes {
		fmt.Printf("%v has an area of %v\n", s, s.Area())
		if colCirc, ok := s.(ColoredCircle); ok {
			fmt.Printf("Color: %v\n", colCirc.Color)
		}
	}

	things := []any{r, c, cc, p}
	for _, t := range things {
		fmt.Printf("%v\n", t)
	}
}
```

```go
package main

import "math"

type Circle struct {
	Center Point
	Radius float64
}

func (c Circle) Area() float64 {
	return c.Radius * c.Radius * math.Pi
}

const (
	Red = 0xFF0000
	Green = 0x00FF00
	Blue = 0x0000FF
	White = 0xFFFFFF
	Black = 0x000000
)

type ColoredCircle struct {
	Circle
	Color       int
}

type Colored interface {
	GetColor() int
}

func (c ColoredCircle) GetColor() int {
	return c.Color
}
```

### Pointers

```go
package main

import "fmt"

// Structure representing a person
type Person struct {
	Name       string
	MiddleName string
	LastName   string
	Age        int
}

func main() {
	px := new(int)
	*px = 2

	fmt.Printf("x address %v, value %v\n", px, *px)
	*px *= 2
	fmt.Printf("x address %v, value %v\n", px, *px)

	func(value *int) {
		*value *= 2
	}(px)
	fmt.Printf("x address %v, value %v\n", px, *px)

	pp := &Person{"John", "Alfred", "Doe", 20}
	fmt.Printf("person address %p, value %v\n", pp, *pp)

	func(p *Person) {
		p.Name, p.MiddleName, p.LastName = p.LastName, p.Name, p.MiddleName
	}(pp)

}
```

### Error Handling

```go
package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

const (
	DivisionByZero = 0x01
	Overflow       = 0x02
)

type DivisionError struct {
	ErrorKind int
}

func (de *DivisionError) Error() string {
	switch de.ErrorKind {
	case DivisionByZero:
		return "division by zero"
	case Overflow:
		return "overflow"
	default:
		return "unknown error"
	}
}

// Divides x by y and returns the quotient.
//
// If y is zero, returns -1 and an error. The error is of type *DivisionError.
func div(x, y int) (int, error) {
	if y == 0 {
		return -1, &DivisionError{DivisionByZero}
	}

	return x / y, nil
}

func divWithPanic(x, y int) int {
	if y == 0 {
		panic(&DivisionError{DivisionByZero})
	}

	return x / y
}

func main() {
	a := 10
	b := 5
	c, err := div(a, b)
	if err != nil {
		println(err.Error())
		return
	}

	println(c)

	for {
		repl()
	}
}

func repl() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Printf("Panic: %v\n", r)
		}
	}()

	reader := bufio.NewReader(os.Stdin)
	for {
		print("X: ")
		xStr, err := reader.ReadString('\n')
		if err != nil {
			return
		}

		x, err := strconv.Atoi(strings.TrimSpace(xStr))
		if err != nil {
			println("Invalid number")
			continue
		}

		print("Y: ")
		yStr, err := reader.ReadString('\n')
		if err != nil {
			return
		}

		y, err := strconv.Atoi(strings.TrimSpace(yStr))
		if err != nil {
			println("Invalid number")
			continue
		}

		z := divWithPanic(x, y)

		println(z)
	}
}
```

### Channel Basics, Select

```go
package main

import (
	"fmt"
	"sync"
	"time"
)

func sayHello(toWhom string, wg *sync.WaitGroup) {
	fmt.Printf("Hello, %s!\n", toWhom)

	// Let's simulate a long running task by waiting for 3 seconds
	time.Sleep(3 * time.Second)
	wg.Done()
}

type Measurement struct {
	Weight         float64
	IsStable       bool
	IsBelowMinLoad bool
}

type MeasureError struct{}

func getValueFromScale(scaleId int, result chan<- Measurement, err chan<- MeasureError) {
	// Let's simulate a long running task by waiting for 3 seconds
	time.Sleep(3 * time.Second)
	//result <- Measurement{Weight: 1.2, IsStable: true, IsBelowMinLoad: false}
	err <- MeasureError{}
}

func doSomethingInBackground(done chan bool) {
	time.Sleep(3 * time.Second)
	done <- true
}

func main() {
	// var wg sync.WaitGroup
	// wg.Add(2)
	// go sayHello("Gophers", &wg)
	// go sayHello("World", &wg)
	// wg.Wait()

	result := make(chan Measurement)
	err := make(chan MeasureError)
	done := make(chan bool)
	go getValueFromScale(4711, result, err)
	go doSomethingInBackground(done)
	<-done

	select {
	case m := <-result:
		fmt.Printf("Weight: %f\n", m.Weight)
	case e := <-err:
		fmt.Printf("Error: %v\n", e)
	case <- time.After(2 * time.Second):
		fmt.Println("Timeout")
	}
}
```

### Channel Sample 1

```go
package main

import (
	"math/rand"
	"time"
)

// Method that returns the current weight from a (simulated) scale
// every 250ms. The weight is returned through a channel. Weighting
// happens until the caller sends a stop message through a separate channel.
func weight(scale chan<- int, stop <-chan bool) {
	for {
		select {
		case <-stop:
			close(scale)
			return
		default:
		}

		// Wait for 250ms
		time.Sleep(250 * time.Millisecond)

		// Send the current weight through the channel
		scale <- rand.Intn(1000)
	}
}

func main() {
	scale := make(chan int)
	stop := make(chan bool)
	
	// Send a stop message after 5 seconds
	go func() {
		time.Sleep(5 * time.Second)
		stop <- true
	}()

	go weight(scale, stop)
	for w := range scale {
		println(w)
	}
}
```

### Channel Sample 2

```go
package main

import (
	"fmt"
	"sync"
	"time"
)

type Config struct{}

type WeighingResult struct {
	ResultChan chan<- float64
}

type Scale struct {
	ConfigChannel <-chan Config
	DoWeighing    <-chan WeighingResult
	Stop          <-chan bool
}

func (s *Scale) Run(wg *sync.WaitGroup) {
	for {
		select {
		case config := <-s.ConfigChannel:
			// do something with config
			fmt.Printf("Config setting %v saved\n", config)
		case weighingResult := <-s.DoWeighing:
			// do weighing
			time.Sleep(1 * time.Second)
			weighingResult.ResultChan <- 1.0
		case <-s.Stop:
			wg.Done()
			return
		}
	}
}

func main() {
	var wg sync.WaitGroup
	wg.Add(1)

	cc := make(chan Config)
	dw := make(chan WeighingResult)
	sc := make(chan bool)

	scale := &Scale{
		ConfigChannel: cc,
		DoWeighing:    dw,
		Stop:          sc,
	}
	go scale.Run(&wg)

	cc <- Config{}

	resultChan := make(chan float64)
	dw <- WeighingResult{ResultChan: resultChan}
	fmt.Printf("Weighing result: %v\n", <-resultChan)

	sc <- true
	wg.Wait()
}
```

### Pokemon Stitch

```go
package main

import (
	"encoding/json"
	"flag"
	"image"
	"image/png"
	"io"
	"math"
	"net/http"
	"os"
	"sync"

	gim "github.com/ozankasikci/go-image-merge"
)

type pokemon struct {
	Sprites pokemonSprites `json:"sprites"`
}

type pokemonSprites struct {
	BackDefault      string `json:"back_default"`
	BackFemale       string `json:"back_female"`
	BackShiny        string `json:"back_shiny"`
	BackShinyFemale  string `json:"back_shiny_female"`
	FrontDefault     string `json:"front_default"`
	FrontFemale      string `json:"front_female"`
	FrontShiny       string `json:"front_shiny"`
	FrontShinyFemale string `json:"front_shiny_female"`
}

func main() {
	pokemonName := flag.String("pokemon", "pikachu", "pokemon name")
	flag.Parse()

	apiUrl := "https://pokeapi.co/api/v2/pokemon/" + *pokemonName
	res, err := http.Get(apiUrl)
	if err != nil {
		panic(err)
	}

	body, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}

	var pokemonData pokemon
	err = json.Unmarshal(body, &pokemonData)
	if err != nil {
		panic(err)
	}

	images := make(chan image.Image, 8)
	wg := &sync.WaitGroup{}
	wg.Add(8)
	go getImage(pokemonData.Sprites.BackDefault, images, wg)
	go getImage(pokemonData.Sprites.BackFemale, images, wg)
	go getImage(pokemonData.Sprites.BackShiny, images, wg)
	go getImage(pokemonData.Sprites.BackShinyFemale, images, wg)
	go getImage(pokemonData.Sprites.FrontDefault, images, wg)
	go getImage(pokemonData.Sprites.FrontFemale, images, wg)
	go getImage(pokemonData.Sprites.FrontShiny, images, wg)
	go getImage(pokemonData.Sprites.FrontShinyFemale, images, wg)
	wg.Wait()
	close(images)

	grids := make([]*gim.Grid, 0)
	for img := range images {
		grids = append(grids, &gim.Grid{Image: img})
	}

	rgba, err := gim.New(grids, 2, int(math.Ceil(float64(len(grids))/float64(2)))).Merge()
	if err != nil {
		panic(err)
	}

	file, err := os.Create("pokemonStitch.png")
	if err != nil {
		panic(err)
	}

	err = png.Encode(file, rgba)
	if err != nil {
		panic(err)
	}
}

func getImage(url string, out chan<- image.Image, wg *sync.WaitGroup) {
	defer wg.Done()

	if len(url) == 0 {
		return
	}

	imgRes, err := http.Get(url)
	if err != nil {
		return
	}

	img, _, err := image.Decode(imgRes.Body)
	if err != nil {
		return
	}

	out <- img
}
```

### Generics

```go
package main

import "fmt"

func Print[T fmt.Stringer](s []T) {
	for _, v := range s {
		fmt.Printf("%v\n", v)
	}
}

type Person struct {
	Name string
}

func (p Person) String() string {
	return fmt.Sprintf("Person: %s", p.Name)
}

func AddInt(a, b int) int {
	return a + b
}

func AddFloat64(a, b float64) float64 {
	return a + b
}

func AddFloat32(a, b float32) float32 {
	return a + b
}

func Add[T int | float64 | float32](a, b T) T {
	return a + b
}

func Map[T1, T2 any](items []T1, mapFunc func(T1) T2) []T2 {
	result := make([]T2, len(items))
	for i, item := range items {
		result[i] = mapFunc(item)
	}
	return result
}

func Filter[T any](items []T, filterFunc func(T) bool) []T {
	result := make([]T, 0)
	for _, item := range items {
		if filterFunc(item) {
			result = append(result, item)
		}
	}
	return result
}

type Pair[T1, T2 any] struct {
	Item1 T1
	Item2 T2
}

func concat[T any](a, b <-chan T) <-chan T {
	r := make(chan T)
	
	go func(a, b <-chan T, r chan<- T) {
		defer close(r)
		for v := range a {
			r <- v
		}
		for v := range b {
			r <- v
		}
	}(a, b, r)
	
	return r
}

func main() {
	// a := []int{1, 2, 3}
	// b := []string{"a", "b", "c"}
	// Print(a)
	// Print(b)
	Print([]Person{{"John"}, {"Jane"}})

	var a int
	var b float64
	a = 1
	b = 2.0
	_ = Add(b, float64(a))

	Print(
		Map(
			Filter([]string{"John", "Jane", "Tom"},
				   func (name string) bool { return name[0] == 'J'}), 
			func (name string) Person { return Person{name} }))

	intPair := Pair[int, int]{1, 2}
	floatPair := Pair[float64, float64]{1.0, 2.0}
	fmt.Printf("%v\n", intPair)
	fmt.Printf("%v\n", floatPair)

	c1 := make(chan string, 2)
	c2 := make(chan string, 2)

	go func() {
		c1 <- "Hello"
		c1 <- ", "
		close(c1)
		
		c2 <- "world"
		c2 <- "!"
		close(c2)
	}()

	c3 := concat(c1, c2)
	for item := range c3 {
		fmt.Print(item)
	}

	println()
}
```

### Simple Web API (People)

```go
package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"net/http"
	"strconv"

	"github.com/julienschmidt/httprouter"
)

type Person struct {
	ID         int        `json:"id"`
	FirstName  string     `json:"first_name"`
	MiddleName string     `json:"middle_name,omitempty"`
	LastName   string     `json:"last_name"`
	Department Department `json:"department"`
}

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

var people []Person

func main() {
	var port int
	flag.IntVar(&port, "port", 8080, "Port to listen on")
	flag.Parse()

	people = append(people, Person{ID: 1, FirstName: "John", LastName: "Doe", Department: Department{ID: 1, Name: "IT"}})

	protectedMux := httprouter.New()
	protectedMux.GET("/people", GetPeople)
	protectedMux.GET("/people/:id", GetPerson)
	protectedMux.POST("/people", AddPerson)
	protectedMux.DELETE("/people/:id", DeletePerson)
	protectedMux.NotFound = http.HandlerFunc(NotFound)
	
	mux := httprouter.New()
	mux.GET("/healthcheck", Healthcheck)
	mux.NotFound = basicAuth(protectedMux.ServeHTTP)

	http.ListenAndServe(fmt.Sprintf(":%d", port), mux)
}

func basicAuth(next http.HandlerFunc) http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Check auth
		user, pwd, ok := r.BasicAuth()
		
		// If ok, call inner handler
		if ok {
			// Check user and password (e.g. hashing, DB access...)
			if user == "admin" && pwd == "password" {
				ctx := context.WithValue(r.Context(), "user", user)
				next.ServeHTTP(w, r.WithContext(ctx))
				return
			}
		}

		// If not, return unauthorized
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
	})
}

func GetPeople(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	user := r.Context().Value("user").(string)
	fmt.Printf("User %s requested people\n", user)

	json.NewEncoder(w).Encode(people)
}

func GetPerson(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	idString := p.ByName("id")
	id, _ := strconv.Atoi(idString)
	for _, item := range people {
		if item.ID == id {
			json.NewEncoder(w).Encode(item)
			return
		}
	}

	http.NotFound(w, r)
}

func AddPerson(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	var person Person

	err := json.NewDecoder(r.Body).Decode(&person)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	people = append(people, person)

	w.WriteHeader(http.StatusCreated)
}

func DeletePerson(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	idString := p.ByName("id")
	id, _ := strconv.Atoi(idString)
	for index, item := range people {
		if item.ID == id {
			people = append(people[:index], people[index+1:]...)
			w.WriteHeader(http.StatusNoContent)
			return
		}
	}

	http.NotFound(w, r)
}

func NotFound(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Ups, I dont' know what you mean!\n")
}

func Healthcheck(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	w.WriteHeader(http.StatusOK)
}
```