# 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)
}
```