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