Go in der Praxis

gh Github CLI

149.298 Zeilen Code

490 Contributors

1.🖥️ CLI Tools

2.🌐 Web App

3.🚢 Container Workloads

5 Fakten zu Go

  1. statisches Typsystem
  2. Garbage Collection
  3. keine Vererbung
  4. Concurrency eingebaut
  5. native Ausführung
    Linux, Win, z/OS, 386, amd64, ARM, wasm, ...

🖥️ CLI Tool
Go Proverbs

Go Proverbs

  • 18 Prinzipien zur Go Entwicklung
  • von Rob Pike 2015 vorgestellt
  • "Clear is better than clever."

CLI Proverbs ohne Argumente


                        > progo-cli
                    

                        Clear is better than clever.
                    

CLI Projekt aufsetzen


                        // 1. Go Modul erstellen (erzeugt go.mod)
                        > go mod init crossnative.com/progo-cli
                    

                        // 2. Datei main.go erstellen
                        package main

                        import "fmt"
                        
                        func main() {
                            fmt.Println("Hello Go Proverbs!")
                        }
                    
Ausführen

                                go build . // 1. Code kompilieren
                                ./progo-cli      // 2. Binary ausführen
                                

                                    go run .   // Code kompilieren und ausführen
                    

Entwicklung

JetBrains GoLand
Visual Studio Code
Vim Go

Proverbs als Go Modul

Proverbs Modul nutzen


                        // 1. Proverbs Modul Dependency
                        > go get github.com/jboursiquot/go-proverbs
                    

                        // 2. Go Modul Descriptor go.mod
                        module crossnative.com/progo-cli

                        go 1.23
                        
                        require github.com/jboursiquot/go-proverbs v0.0.2
                    

Zufälliges Proverb ausgeben


                        package main

                        import (
                            "fmt"
                            "github.com/jboursiquot/go-proverbs"
                        )
                        
                        func main() {
                            fmt.Println(proverbs.Random())
                        }
                    

                        // Ausgabe `go run .`
                        &{Make the zero value useful. http://youtube.com/322}
                    

💡 Structs und Pointer


                        package main

                        import (
                            "fmt"
                            "github.com/jboursiquot/go-proverbs"
                        )
                        
                        func main() {
                            // Pointer Variable auf Proverb Struct
                            var p *proverbs.Proverb = proverbs.Random()
                            
                            fmt.Println(p)
                        }
                    

                        // Struct statt Klasse
                        type Proverb struct {
                            Saying string
                            Link   string
                        }
                    

Zufälliges Proverb ausgeben


                        package main

                        import (
                            "fmt"
                            "github.com/jboursiquot/go-proverbs"
                        )
                        
                        func main() {
                            var p *proverbs.Proverb = proverbs.Random()

                            // Zugriff auf Property des Structs
                            fmt.Println(p.Saying)
                        }
                    

                        // Ausgabe `go run .`
                        Make the zero value useful.
                    

CLI Proverbs mit Count


                        > pro -count=3
                    

                        Clear is better than clever.
                        Documentation is for users.
                        Don't panic.
                    

Flag für Count


                        import (
                            "flag"
                            "fmt"
                            "github.com/jboursiquot/go-proverbs"
                        )
                        
                        func main() {
                            // 1. Flags definieren
                            var count int
                            flag.IntVar(&count, "count", 1, "proverb count")
                        
                            // 2. Flags parsen
                            flag.Parse()
                        
                            // 3. Ausgabe in Schleife
                            for i := 0; i < count; i++ {
                                var p *proverbs.Proverb = proverbs.Random()
                                fmt.Println(p.Saying)
                            }                        
                        }                        
                    

                        // Compile mit `go build .`, Ausgabe mit `progo-cli -count=2`
                        Make the zero value useful.
                        Reflection is never clear.
                    

CLI Proverbs mit ungültigem Count


                        > progo-cli -count=
                    

            flag needs an argument: -count
            Usage of ./progo-cli:
              -count int
                    proverb count (default 1)
           

Flag für Count Refactored


                    import (
                        "flag"
                        "fmt"
                        "github.com/jboursiquot/go-proverbs"
                    )
                    
                    func main() {
                        // 1. Flags definieren
                        var count int
                        flag.IntVar(&count, "count", 1, "proverb count")
                    
                        // 2. Flags parsen
                        flag.Parse()
                    
                        // 3. Ausgabe in Schleife
                        for range count {
                            p := proverbs.Random()
                            fmt.Println(p.Saying)
                        }                        
                    }                        
                

                    // Compile mit `go build .`, Ausgabe mit `pro -count=2`
                    Make the zero value useful.
                    Reflection is never clear.
                

🐍 Cobra

🐍 Cobra Setup

            
              # Aufbau Cobra CLIs
              progo-cli {command} {subcommand} {args..} {flags..}
            
          
            
              # Cobra CLI installieren
              go install github.com/spf13/cobra-cli@latest
            
          

🐍 Cobra Setup

            
              # Cobra Projekt aufsetzen
              cobra-cli init
            
          
            
              # Projektstruktur
              ├── cmd
              │   ├── root.go
              │   ├── cmd1.go
              │   ├── print.go
              │   └── {...}.go
              └── main.go              
            
          

🐍 Cobra Print Cmd ohne Fehlerhandling


            var printCmd = &cobra.Command{
                Use:   "print",
                Short: "Prints some proverbs",
                Long:  `Nifty tool that prints the Proverbs in your terminal.`,
                Args:  cobra.MatchAll(cobra.MaximumNArgs(2), cobra.OnlyValidArgs),
                Run: func(cmd *cobra.Command, args []string) {
                  // 1. count Flag abfragen
                  count, _ := cmd.Flags().GetInt("count")
              
                  // 2. Ausgabe in Schleife
                  for range count {
                    p := proverbs.Random()
                    fmt.Println(p.Saying)
                  }
                },
              }           

            func init() {
              rootCmd.AddCommand(printCmd)
              
              // Flags
              printCmd.Flags().IntP("count", "c", 1, "Count of proverbs to print.")
            }
            
          

🐍 Cobra Print Cmd mit Fehlerhandling


            var printCmd = &cobra.Command{
                Use:   "print",
                Short: "Prints some proverbs",
                Long:  `Nifty tool that prints the Proverbs in your terminal.`,
                Args:  cobra.MatchAll(cobra.MaximumNArgs(2), cobra.OnlyValidArgs),
                RunE: func(cmd *cobra.Command, args []string) error {
                  // 1. count Flag abfragen
                  count, err := cmd.Flags().GetInt("count")
                  if err != nil {
                    // 1a. Return mit Fehler
                    return err
                  }
              
                  // 2. Ausgabe in Schleife
                  for range count {
                    p := proverbs.Random()
                    fmt.Println(p.Saying)
                  }
              
                  // 3. Return ohne Fehler
                  return nil
                },
              }           
            
          

CLI Release bauen

👷 Goreleaser + Github Action Demo

🖥️ CLI Tools

  • Standardlib Flags
  • Cobra CLI Lib wenn's komplexer wird
  • einfache Releases mit Goreleaser
  • Github, Kubernetes sind Go CLIs

🌐 Web App Cats

Cat API mit JSON


                    # Query cat API
                    curl -s http://localhost:8080/api/cats | jq 
                    [
                      {
                        "name": "Ginger"
                      }
                    ]
                  

Cat Web mit HTML

Set up Cats App


                          # 1. Verzeichnis erstellen
                          mkdir cats
                          cd cats
                      

                            # 2. Go Modul aufsetzen
                            go mod init crossnative.com/cats
                      

                        # 3. Go Datei erstellen
                        touch main.go
                      

Cat API simple


                    func catAPIHandler(w http.ResponseWriter, r *http.Request) {
                      fmt.Fprintf(w, "Meow!")
                      w.WriteHeader(http.StatusOK)
                    }
                    
                    func main() {
                      http.HandleFunc("GET /api/cats", catAPIHandler)
                      http.ListenAndServe(":8080", nil)
                    }
                  

Cat API mit JSON


                    // 1. Struct mit JSON Tag definieren
                    type Cat struct {
                      Name string `json:"name"`
                    }
                    
                    func catAPIHandler(w http.ResponseWriter, r *http.Request) {
                      // 2. Slice erstellen
                      cats := make([]Cat, 1)

                      // 3. Struct erstellen und einfügen
                      cats[0] = Cat{Name: "Ginger"}

                      // 4. JSON rendern
                      json.NewEncoder(w).Encode(cats)
                    }
                                
                    func main() {
                      http.HandleFunc("GET /api/cats", catAPIHandler)
                      http.ListenAndServe(":8080", nil)
                    }
                  

Test Cat API cat_api_test.go


                  func TestCatAPIHandler(t *testing.T) {
                    // 1. Test Request erstellen
                    req, _ := http.NewRequest("GET", "/api/cats", nil)
                  
                    // 2. HTTP Recorder erstellen (ist http.ResponseWriter)
                    rec := httptest.NewRecorder()
                  
                    // 3. Handler aufrufen
                    catAPIHandler(rec, req)
        
                    // 4. Response prüfen
                    if rec.Code != http.StatusOK {
                      t.Errorf("got status %v expected %v", rec.Code, http.StatusOK)
                    }
                  }
                  

Test ausführen


                    go test -v ./...
                    === RUN   TestCatAPIHandler
                    --- PASS: TestCatAPIHandler (0.00s)
                    PASS
                    coverage: 50.0% of statements
                    ok      crossnative.com/cats      0.127s  coverage: 50.0% of statements
                  

Web App
Cats

Web App Cats

  
                    func indexHandler(w http.ResponseWriter, r *http.Request) {
                      tpl := template.Must(template.ParseFiles("index.html"))
                      tpl.Execute(w, nil)
                    }
        
                    func main() {
                      // 1. Router erzeugen
                      router := http.NewServeMux()

                      // 2. Handler registrieren
                      router.HandleFunc("/", indexHandler)
        
                      // 3. Server mit Router starten
                      http.ListenAndServe(":8080", router)
                    }
                  

File Server aus Binary


                    //go:embed assets
                    var assets embed.FS
        
                    func main() {
                      router := http.NewServeMux()
                      router.HandleFunc("/", indexHandler)
        
                      // Serve Files
                      router.Handle("/assets/", http.FileServer(http.FS(assets)))
        
                      http.ListenAndServe(":8080", router)
                    }
                  

Index Handler Template mit Daten

  
                        type Cat struct {
                          Name string
                        }
        
                        func indexHandler(w ResponseWriter, r *Request) {
                          // 1. Slice erstellen
                          cat := make([]Cat, 1)
                        
                          // 2. Struct erstellen und einfügen
                          cat[0] = Cat{Name: "Ginger"}
                        
                          // 3. Template rendern
                          tpl := template.Must(template.ParseFiles("index.html"))
                          tpl.Execute(w, cat)
                        }
                      
  
                        <body>
                          <h1>Cats App</h1>
                          {{ range . }}
                            <h2>{{ .Name }}</h2>
                          {{ end }}
                        </body>                
                      

Index Handler mit Cats API

Query Cats API

                        GET https://api.thecatapi.com/v1/breeds?limit=5
                      

                     [
                        {
                           "id": "abys",
                           "name": "Abyssinian",
                           "image": {
                             "url": "https://cdn2.thecatapi.com/0XYvRd7oD.jpg"
                           }
                        },
                        {
                           "id": "aege",
                           "name": "Aegean",
                           "image": {
                             "url": "https://cdn2.thecatapi.com/ozEvzdVM-.jpg"
                           }
                        },
                        ...
                     ]
                      
Map JSON auf Struct
  
                        type Cat struct {
                          ID    string `json:"id"`
                          Name  string `json:"name"`
                          Image struct {
                            URL string `json:"url"`
                          } `json:"image"`
                        }            
                      

Index Handler mit Cats API

  
                    func indexHandler(w http.ResponseWriter, r *http.Request) {
                      // 1. Cat API Aufruf (Fehler ignorieren)
                      resp, _ := http.Get("https://api.thecatapi.com/v1/breeds?limit=5")
                    
                      // 2. Slice für Cat Structs
                      cat := make([]Cat, 5)
        
                      // 3. Response Body parsen
                      defer resp.Body.Close()            
                      body, _ := ioutil.ReadAll(resp.Body)
                      json.Unmarshal(body, &cat)
                    
                      // 4. Template rendern
                      tpl.Execute(w, cat)
                    }
                   

Index Handler mit Fehlerhandling 💣

  
                    func indexHandler(w http.ResponseWriter, r *http.Request) {
                      // 1. Cat API Aufruf
                      resp, err := http.Get("https://api.thecatapi.com/v1/breeds?limit=5")
                      //    Fehler behandeln
                      if err != nil {
                        http.Error(w, "Cats API error", http.StatusInternalServerError)
                        return
                      }
                    
                      // 2. Slice für Cat Structs
                      cat := make([]Cat, 5)
        
                      // 3. Response Body parsen
                      defer resp.Body.Close()            
                      body, _ := ioutil.ReadAll(resp.Body)
                      err := json.Unmarshal(body, &cat)
                      // TODO: Fehler behandeln
                    
                      // 4. Template rendern
                      err := tpl.Execute(w, cat)
                      // TODO: Fehler behandeln
                    }
                   

🌐 Web App Cats

  • Standardlib JSON und Router
  • HTTP Tests
  • Querschnittsfeatures durch Middleware

🚢 Container Workloads

🚢 Container Workloads

  • Kubernetes Services
  • Serverless Containers
    GCP Cloud Run, AWS ECS, Azure Container Apps

Dockerfile für Cat API


          # 1. App Builder
          FROM golang AS builder
          WORKDIR /app
          ADD . /app
          RUN CGO_ENABLED=0 go build -ldflags="-w -s" -o build/cats .
          
          # 2. App Container
          FROM gcr.io/distroless/static
          COPY --from=builder /app/build/cats /usr/bin/
          EXPOSE 8080
          ENTRYPOINT ["/usr/bin/cats"]
        
🚢 Google Distroless Image 8,5 MB

Go Cat Container
Zahlen, Daten, Fakten

  • schlankes Container Image 8,5 MB
  • Container Cold Start in 50-60 ms
  • Hauptspeicher 25 MB
  • Antwortzeit 15 ms

🖥️
CLI Tools

  • Projekt Setup
  • Flags
  • Cobra
  • Goreleaser

🌐
Web App

  • Web Server
  • JSON API
  • HTML Template
  • Unit Test
  • Fehlerhandling

🚢
Container Workloads

  • Docker Container
  • Resourcen Verbrauch

3 Gründe für Go

  1. Einfach
  2. Mächtig
  3. Langweilig

Go liebt

Microservices
Serverless Functions
Kommandozeilen-Tools
DevOps und Cloud

Go in der Praxis