MFormations
Modern Go Engineering

Chapitre 19

19 - Corrections

> Corrigés détaillés des 40 exercices du chapitre 17, avec code Go complet, explications, et analyses.

Cours 19 : Corrigés détaillés des 40 exercices

300+ lignes de corrigés avec code Go complet.


Exercice 01 : FizzBuzz

Analyse

L'exercice demande une fonction FizzBuzz avec gestion d'erreur. Les cas d'erreur incluent les nombres négatifs, nuls, et les overflow potentiels. La fonction doit retourner une error descriptive.

Code complet

package main

import (
    "fmt"
    "math"
)

func FizzBuzz(n int) (string, error) {
    if n < 0 {
        return "", fmt.Errorf("negative number: %d", n)
    }
    if n == 0 {
        return "", fmt.Errorf("zero is not allowed")
    }
    if n > math.MaxInt32 {
        return "", fmt.Errorf("number too large: %d", n)
    }

    switch {
    case n%15 == 0:
        return "FizzBuzz", nil
    case n%3 == 0:
        return "Fizz", nil
    case n%5 == 0:
        return "Buzz", nil
    default:
        return fmt.Sprintf("%d", n), nil
    }
}

Tests

func TestFizzBuzz(t *testing.T) {
    tests := []struct {
        name string
        n    int
        want string
        err  bool
    }{
        {"divisible by 3", 3, "Fizz", false},
        {"divisible by 5", 5, "Buzz", false},
        {"divisible by 15", 15, "FizzBuzz", false},
        {"not divisible", 7, "7", false},
        {"negative", -1, "", true},
        {"zero", 0, "", true},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := FizzBuzz(tt.n)
            if (err != nil) != tt.err {
                t.Errorf("FizzBuzz(%d) error = %v, wantErr %v", tt.n, err, tt.err)
            }
            if got != tt.want {
                t.Errorf("FizzBuzz(%d) = %v, want %v", tt.n, got, tt.want)
            }
        })
    }
}

Erreurs fréquentes

  • Oublier le cas n%15 == 0 (testé en premier car 15 est divisible par 3 et 5)
  • Utiliser else if au lieu de switch
  • Ne pas gérer l'erreur pour n == 0

Exercice 02 : Palindrome Unicode

Analyse

La difficulté est la gestion de l'Unicode. Les chaînes Go sont en UTF-8. Il faut ignorer la casse et les caractères non-lettres.

Code complet

package main

import (
    "strings"
    "unicode"
)

func IsPalindrome(s string) bool {
    clean := strings.Map(func(r rune) rune {
        if !unicode.IsLetter(r) && !unicode.IsDigit(r) {
            return -1
        }
        return unicode.ToLower(r)
    }, s)

    runes := []rune(clean)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        if runes[i] != runes[j] {
            return false
        }
    }
    return true
}

Tests

func TestIsPalindrome(t *testing.T) {
    tests := []struct {
        input string
        want  bool
    }{
        {"A man, a plan, a canal: Panama", true},
        {"racecar", true},
        {"hello", false},
        {"été", true},
        {"世界", true},   // palindrome?
        {"世界界世", true},
        {"", true},
        {"a", true},
    }
    for _, tt := range tests {
        t.Run(tt.input, func(t *testing.T) {
            if got := IsPalindrome(tt.input); got != tt.want {
                t.Errorf("IsPalindrome(%q) = %v, want %v", tt.input, got, tt.want)
            }
        })
    }
}

Points clés

  • strings.Map avec retour -1 pour supprimer un caractère
  • Conversion en []rune pour itérer correctement sur l'Unicode
  • Comparaison depuis les extrémités vers le centre

Exercice 03 : Shape Interface

Analyse

Implémentation d'une interface et de plusieurs types concrets. Utilisation du duck typing et composition.

Code complet

package main

import (
    "fmt"
    "math"
    "sort"
)

type Shape interface {
    Area() float64
    Perimeter() float64
}

type Circle struct {
    Radius float64
}

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

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

type Rectangle struct {
    Width, Height float64
}

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

func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

type Triangle struct {
    A, B, C float64
}

func (t Triangle) Area() float64 {
    s := (t.A + t.B + t.C) / 2
    return math.Sqrt(s * (s - t.A) * (s - t.B) * (s - t.C))
}

func (t Triangle) Perimeter() float64 {
    return t.A + t.B + t.C
}

func TotalArea(shapes ...Shape) float64 {
    var total float64
    for _, s := range shapes {
        total += s.Area()
    }
    return total
}

// Tri par aire
type ByArea []Shape

func (a ByArea) Len() int           { return len(a) }
func (a ByArea) Less(i, j int) bool { return a[i].Area() < a[j].Area() }
func (a ByArea) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }

func main() {
    shapes := []Shape{
        Circle{Radius: 5},
        Rectangle{Width: 3, Height: 4},
        Triangle{A: 3, B: 4, C: 5},
    }

    fmt.Printf("Total area: %.2f\n", TotalArea(shapes...))

    sort.Sort(ByArea(shapes))
    for _, s := range shapes {
        fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
    }
}

Exercice 04 : JSON Processing

Code complet

package main

import (
    "encoding/json"
    "fmt"
)

type Address struct {
    City    string `json:"city"`
    Country string `json:"country"`
}

type Person struct {
    Name    string   `json:"name"`
    Age     int      `json:"age"`
    Emails  []string `json:"emails,omitempty"`
    Address *Address `json:"address,omitempty"`
}

func (p Person) MarshalJSON() ([]byte, error) {
    type Alias Person
    return json.Marshal(&struct {
        Age string `json:"age"`
        *Alias
    }{
        Age:   fmt.Sprintf("%d years", p.Age),
        Alias: (*Alias)(&p),
    })
}

func (p *Person) UnmarshalJSON(data []byte) error {
    type Alias Person
    aux := &struct {
        Age string `json:"age"`
        *Alias
    }{
        Alias: (*Alias)(p),
    }
    if err := json.Unmarshal(data, aux); err != nil {
        return err
    }
    // Parse age from "XX years" format - simplified
    fmt.Sscanf(aux.Age, "%d years", &p.Age)
    return nil
}

Exercice 05 : Custom Sorting

Code complet

package main

import (
    "fmt"
    "math/rand"
    "sort"
)

type Product struct {
    Name   string
    Price  float64
    Rating float64
}

func main() {
    products := []Product{
        {"Laptop", 999.99, 4.5},
        {"Mouse", 29.99, 4.8},
        {"Keyboard", 79.99, 4.2},
        {"Monitor", 299.99, 4.6},
    }

    // Tri par nom (ascendant)
    sort.Slice(products, func(i, j int) bool {
        return products[i].Name < products[j].Name
    })

    // Tri par prix (descendant)
    sort.Slice(products, func(i, j int) bool {
        return products[i].Price > products[j].Price
    })

    // Tri par rating (ascendant)
    sort.Slice(products, func(i, j int) bool {
        return products[i].Rating < products[j].Rating
    })

    // Tri composé : prix * rating
    sort.Slice(products, func(i, j int) bool {
        scoreI := products[i].Price * products[i].Rating
        scoreJ := products[j].Price * products[j].Rating
        return scoreI > scoreJ
    })
}

Exercice 06 : Worker Pool

Code complet

package main

import (
    "context"
    "fmt"
    "sync"
)

type Job func(ctx context.Context) (interface{}, error)

type Result struct {
    Value interface{}
    Err   error
}

type Pool struct {
    jobs    chan Job
    results chan Result
    wg      sync.WaitGroup
}

func NewPool(ctx context.Context, workers int) *Pool {
    p := &Pool{
        jobs:    make(chan Job),
        results: make(chan Result),
    }

    for i := 0; i < workers; i++ {
        p.wg.Add(1)
        go p.worker(ctx, i)
    }

    return p
}

func (p *Pool) worker(ctx context.Context, id int) {
    defer p.wg.Done()
    for {
        select {
        case job, ok := <-p.jobs:
            if !ok {
                return
            }
            value, err := job(ctx)
            p.results <- Result{Value: value, Err: err}
        case <-ctx.Done():
            return
        }
    }
}

func (p *Pool) Submit(job Job) {
    p.jobs <- job
}

func (p *Pool) Results() <-chan Result {
    return p.results
}

func (p *Pool) Wait() {
    close(p.jobs)
    p.wg.Wait()
    close(p.results)
}

Exercice 07 : Pipeline

Code complet

package main

import "fmt"

func Generate(numbers ...int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range numbers {
            out <- n
        }
        close(out)
    }()
    return out
}

func MultiplyBy2(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            out <- n * 2
        }
        close(out)
    }()
    return out
}

func FilterEven(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            if n%2 != 0 {
                out <- n
            }
        }
        close(out)
    }()
    return out
}

func main() {
    numbers := Generate(1, 2, 3, 4, 5, 6)
    multiplied := MultiplyBy2(numbers)
    filtered := FilterEven(multiplied)

    for result := range filtered {
        fmt.Println(result)
    }
}

Exercice 08 : Rate Limiter

Code complet

package main

import (
    "context"
    "time"
)

type RateLimiter struct {
    tokens chan struct{}
    ticker *time.Ticker
}

func NewRateLimiter(rate int, burst int) *RateLimiter {
    rl := &RateLimiter{
        tokens: make(chan struct{}, burst),
        ticker: time.NewTicker(time.Second / time.Duration(rate)),
    }

    // Remplir le bucket initial
    for i := 0; i < burst; i++ {
        rl.tokens <- struct{}{}
    }

    go func() {
        for range rl.ticker.C {
            select {
            case rl.tokens <- struct{}{}:
            default:
                // Bucket plein
            }
        }
    }()

    return rl
}

func (rl *RateLimiter) Allow() bool {
    select {
    case <-rl.tokens:
        return true
    default:
        return false
    }
}

func (rl *RateLimiter) Wait(ctx context.Context) error {
    select {
    case <-rl.tokens:
        return nil
    case <-ctx.Done():
        return ctx.Err()
    }
}

func (rl *RateLimiter) Stop() {
    rl.ticker.Stop()
}

Exercice 09 : Timeout avec Context

Code complet

package main

import (
    "context"
    "fmt"
    "time"
)

func FetchWithTimeout(ctx context.Context, url string, timeout time.Duration) (string, error) {
    ctx, cancel := context.WithTimeout(ctx, timeout)
    defer cancel()

    resultCh := make(chan string, 1)
    errCh := make(chan error, 1)

    go func() {
        // Simulation d'un appel HTTP long
        select {
        case <-time.After(2 * time.Second):
            resultCh <- fmt.Sprintf("response from %s", url)
        case <-ctx.Done():
            errCh <- ctx.Err()
        }
    }()

    select {
    case result := <-resultCh:
        return result, nil
    case err := <-errCh:
        return "", err
    case <-ctx.Done():
        return "", ctx.Err()
    }
}

Exercice 10 : Merge Channels

Code complet

package main

import (
    "sync"
)

// Approche 1 : avec sync.WaitGroup
func Merge(channels ...<-chan int) <-chan int {
    out := make(chan int)
    var wg sync.WaitGroup

    for _, ch := range channels {
        wg.Add(1)
        go func(c <-chan int) {
            defer wg.Done()
            for v := range c {
                out <- v
            }
        }(ch)
    }

    go func() {
        wg.Wait()
        close(out)
    }()

    return out
}

// Approche 2 : avec reflect.Select (dynamique)
func MergeReflect(channels ...<-chan int) <-chan int {
    // Implémentation avec reflect.Select pour le cas où
    // le nombre de channels est connu dynamiquement
    out := make(chan int)
    go func() {
        defer close(out)
        cases := make([]reflect.SelectCase, len(channels))
        for i, ch := range channels {
            cases[i] = reflect.SelectCase{
                Dir:  reflect.SelectRecv,
                Chan: reflect.ValueOf(ch),
            }
        }

        for len(cases) > 0 {
            i, v, ok := reflect.Select(cases)
            if !ok {
                cases = append(cases[:i], cases[i+1:]...)
                continue
            }
            out <- int(v.Int())
        }
    }()
    return out
}

Exercice 11 : Prime Sieve

Code complet

package main

import "fmt"

func Generate(ch chan<- int) {
    for i := 2; ; i++ {
        ch <- i
    }
}

func Filter(in <-chan int, out chan<- int, prime int) {
    for {
        n := <-in
        if n%prime != 0 {
            out <- n
        }
    }
}

func Sieve(limit int) []int {
    ch := make(chan int)
    go Generate(ch)

    var primes []int
    for i := 0; i < limit; i++ {
        prime := <-ch
        primes = append(primes, prime)
        ch1 := make(chan int)
        go Filter(ch, ch1, prime)
        ch = ch1
    }
    return primes
}

func main() {
    primes := Sieve(20)
    fmt.Println(primes)
}

Exercice 12 : Pub/Sub

Code complet

package main

import (
    "sync"
)

type PubSub struct {
    mu     sync.RWMutex
    subs   map[string][]chan interface{}
    closed bool
}

func NewPubSub() *PubSub {
    return &PubSub{
        subs: make(map[string][]chan interface{}),
    }
}

func (ps *PubSub) Subscribe(topic string) <-chan interface{} {
    ps.mu.Lock()
    defer ps.mu.Unlock()

    ch := make(chan interface{}, 1)
    ps.subs[topic] = append(ps.subs[topic], ch)
    return ch
}

func (ps *PubSub) Publish(topic string, msg interface{}) {
    ps.mu.RLock()
    defer ps.mu.RUnlock()

    if ps.closed {
        return
    }

    for _, ch := range ps.subs[topic] {
        ch <- msg
    }
}

func (ps *PubSub) Unsubscribe(topic string, ch <-chan interface{}) {
    ps.mu.Lock()
    defer ps.mu.Unlock()

    subs := ps.subs[topic]
    for i, sub := range subs {
        if sub == ch {
            ps.subs[topic] = append(subs[:i], subs[i+1:]...)
            close(sub)
            break
        }
    }
}

func (ps *PubSub) Close() {
    ps.mu.Lock()
    defer ps.mu.Unlock()

    if ps.closed {
        return
    }
    ps.closed = true
    for _, subs := range ps.subs {
        for _, ch := range subs {
            close(ch)
        }
    }
}

Exercice 13 : Circuit Breaker

Code complet

package main

import (
    "errors"
    "sync"
    "time"
)

type State int

const (
    StateClosed State = iota
    StateOpen
    StateHalfOpen
)

type CircuitBreaker struct {
    mu           sync.Mutex
    state        State
    failures     int
    maxFailures  int
    resetTimeout time.Duration
    lastFailure  time.Time
}

func NewCircuitBreaker(maxFailures int, resetTimeout time.Duration) *CircuitBreaker {
    return &CircuitBreaker{
        state:       StateClosed,
        maxFailures: maxFailures,
        resetTimeout: resetTimeout,
    }
}

func (cb *CircuitBreaker) Execute(fn func() error) error {
    cb.mu.Lock()

    switch cb.state {
    case StateOpen:
        if time.Since(cb.lastFailure) > cb.resetTimeout {
            cb.state = StateHalfOpen
        } else {
            cb.mu.Unlock()
            return errors.New("circuit breaker is open")
        }
    case StateHalfOpen:
        // Allow one request through
    }

    cb.mu.Unlock()

    err := fn()

    cb.mu.Lock()
    defer cb.mu.Unlock()

    if err != nil {
        cb.failures++
        cb.lastFailure = time.Now()
        if cb.failures >= cb.maxFailures {
            cb.state = StateOpen
        }
        return err
    }

    // Success
    cb.failures = 0
    if cb.state == StateHalfOpen {
        cb.state = StateClosed
    }
    return nil
}

Exercice 14 : Fan-Out/Fan-In

Code complet

package main

import (
    "fmt"
    "sync"
)

func FanOut(in <-chan string, workers int) []<-chan string {
    channels := make([]<-chan string, workers)
    for i := 0; i < workers; i++ {
        ch := make(chan string)
        channels[i] = ch
        go func(out chan<- string, id int) {
            for word := range in {
                out <- fmt.Sprintf("worker %d processed: %s (len=%d)", id, word, len(word))
            }
            close(out)
        }(ch, i)
    }
    return channels
}

func FanIn(channels ...<-chan string) <-chan string {
    out := make(chan string)
    var wg sync.WaitGroup

    for _, ch := range channels {
        wg.Add(1)
        go func(c <-chan string) {
            defer wg.Done()
            for msg := range c {
                out <- msg
            }
        }(ch)
    }

    go func() {
        wg.Wait()
        close(out)
    }()

    return out
}

Exercice 15 : Pipeline with Error Handling

Code complet

package main

import (
    "fmt"
    "time"

    "golang.org/x/sync/errgroup"
)

func Step1(in <-chan int) (<-chan int, <-chan error) {
    out := make(chan int)
    errCh := make(chan error, 1)
    go func() {
        defer close(out)
        defer close(errCh)
        for n := range in {
            if n < 0 {
                errCh <- fmt.Errorf("negative number: %d", n)
                return
            }
            out <- n * 2
        }
    }()
    return out, errCh
}

func retry(fn func() error, attempts int) error {
    var err error
    for i := 0; i < attempts; i++ {
        if err = fn(); err == nil {
            return nil
        }
        time.Sleep(time.Duration(100*(i+1)) * time.Millisecond)
    }
    return fmt.Errorf("failed after %d attempts: %w", attempts, err)
}

func main() {
    var g errgroup.Group
    numbers := []int{1, 2, 3, 4, -5, 6}

    for _, n := range numbers {
        n := n
        g.Go(func() error {
            return retry(func() error {
                if n < 0 {
                    return fmt.Errorf("invalid: %d", n)
                }
                return nil
            }, 3)
        })
    }

    if err := g.Wait(); err != nil {
        fmt.Printf("Pipeline errors: %v\n", err)
    }
}

Exercice 16 : REST API CRUD

Code complet

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "sync"
    "time"

    "github.com/google/uuid"
    "github.com/gorilla/mux"
)

type Book struct {
    ID        string    `json:"id"`
    Title     string    `json:"title"`
    Author    string    `json:"author"`
    ISBN      string    `json:"isbn"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

type BookStore struct {
    mu    sync.RWMutex
    books map[string]*Book
}

func NewBookStore() *BookStore {
    return &BookStore{books: make(map[string]*Book)}
}

func (s *BookStore) Create(book *Book) {
    s.mu.Lock()
    defer s.mu.Unlock()
    book.ID = uuid.New().String()
    book.CreatedAt = time.Now()
    book.UpdatedAt = time.Now()
    s.books[book.ID] = book
}

func (s *BookStore) Get(id string) (*Book, bool) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    book, ok := s.books[id]
    return book, ok
}

func (s *BookStore) List() []*Book {
    s.mu.RLock()
    defer s.mu.RUnlock()
    result := make([]*Book, 0, len(s.books))
    for _, book := range s.books {
        result = append(result, book)
    }
    return result
}

func (s *BookStore) Update(book *Book) bool {
    s.mu.Lock()
    defer s.mu.Unlock()
    if _, ok := s.books[book.ID]; !ok {
        return false
    }
    book.UpdatedAt = time.Now()
    s.books[book.ID] = book
    return true
}

func (s *BookStore) Delete(id string) bool {
    s.mu.Lock()
    defer s.mu.Unlock()
    if _, ok := s.books[id]; !ok {
        return false
    }
    delete(s.books, id)
    return true
}

type BookHandler struct {
    store *BookStore
}

func (h *BookHandler) Create(w http.ResponseWriter, r *http.Request) {
    var book Book
    if err := json.NewDecoder(r.Body).Decode(&book); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    h.store.Create(&book)
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(book)
}

func (h *BookHandler) Get(w http.ResponseWriter, r *http.Request) {
    id := mux.Vars(r)["id"]
    book, ok := h.store.Get(id)
    if !ok {
        http.Error(w, "not found", http.StatusNotFound)
        return
    }
    json.NewEncoder(w).Encode(book)
}

func (h *BookHandler) List(w http.ResponseWriter, r *http.Request) {
    books := h.store.List()
    json.NewEncoder(w).Encode(books)
}

func (h *BookHandler) Update(w http.ResponseWriter, r *http.Request) {
    id := mux.Vars(r)["id"]
    var book Book
    if err := json.NewDecoder(r.Body).Decode(&book); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    book.ID = id
    if !h.store.Update(&book) {
        http.Error(w, "not found", http.StatusNotFound)
        return
    }
    json.NewEncoder(w).Encode(book)
}

func (h *BookHandler) Delete(w http.ResponseWriter, r *http.Request) {
    id := mux.Vars(r)["id"]
    if !h.store.Delete(id) {
        http.Error(w, "not found", http.StatusNotFound)
        return
    }
    w.WriteHeader(http.StatusNoContent)
}

func main() {
    store := NewBookStore()
    handler := &BookHandler{store: store}

    r := mux.NewRouter()
    r.HandleFunc("/api/books", handler.List).Methods("GET")
    r.HandleFunc("/api/books", handler.Create).Methods("POST")
    r.HandleFunc("/api/books/{id}", handler.Get).Methods("GET")
    r.HandleFunc("/api/books/{id}", handler.Update).Methods("PUT")
    r.HandleFunc("/api/books/{id}", handler.Delete).Methods("DELETE")

    log.Fatal(http.ListenAndServe(":8080", r))
}

Exercice 17 : Middleware Chain

Code complet

package main

import (
    "log"
    "net/http"
    "time"
)

type responseWriter struct {
    http.ResponseWriter
    status int
}

func (rw *responseWriter) WriteHeader(code int) {
    rw.status = code
    rw.ResponseWriter.WriteHeader(code)
}

func LoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        rw := &responseWriter{ResponseWriter: w, status: http.StatusOK}
        next.ServeHTTP(rw, r)
        log.Printf("%s %s %d %v", r.Method, r.URL.Path, rw.status, time.Since(start))
    })
}

func RecoveryMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                http.Error(w, "Internal Server Error", http.StatusInternalServerError)
                log.Printf("panic recovered: %v", err)
            }
        }()
        next.ServeHTTP(w, r)
    })
}

func CORSMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
        if r.Method == "OPTIONS" {
            w.WriteHeader(http.StatusOK)
            return
        }
        next.ServeHTTP(w, r)
    })
}

Exercice 18 : Graceful Shutdown

Code complet

package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        time.Sleep(2 * time.Second) // Simulate long request
        w.Write([]byte("done"))
    })

    srv := &http.Server{
        Addr:         ":8080",
        Handler:      mux,
        ReadTimeout:  15 * time.Second,
        WriteTimeout: 15 * time.Second,
        IdleTimeout:  60 * time.Second,
    }

    // Channel for shutdown signals
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

    go func() {
        log.Printf("Server listening on %s", srv.Addr)
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("listen: %s", err)
        }
    }()

    <-quit
    log.Println("Shutting down server...")

    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    if err := srv.Shutdown(ctx); err != nil {
        log.Fatalf("server forced to shutdown: %v", err)
    }

    log.Println("Server exited gracefully")
}

Exercice 19 : REST avec chi

Code complet

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "time"

    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
    "github.com/go-playground/validator/v10"
)

type CreateBookRequest struct {
    Title  string `json:"title" validate:"required,min=1,max=200"`
    Author string `json:"author" validate:"required,min=2,max=100"`
    ISBN   string `json:"isbn" validate:"required,len=13"`
}

type Book struct {
    ID        string    `json:"id"`
    Title     string    `json:"title"`
    Author    string    `json:"author"`
    ISBN      string    `json:"isbn"`
    CreatedAt time.Time `json:"created_at"`
}

var validate = validator.New()

func main() {
    r := chi.NewRouter()

    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)
    r.Use(middleware.RealIP)
    r.Use(middleware.RequestID)
    r.Use(middleware.Timeout(30 * time.Second))

    r.Post("/api/books", func(w http.ResponseWriter, r *http.Request) {
        var req CreateBookRequest
        if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }
        if err := validate.Struct(req); err != nil {
            http.Error(w, err.Error(), http.StatusUnprocessableEntity)
            return
        }
        w.WriteHeader(http.StatusCreated)
        json.NewEncoder(w).Encode(req)
    })

    log.Fatal(http.ListenAndServe(":8080", r))
}

Exercice 20 : PostgreSQL CRUD

Code complet

package main

import (
    "context"
    "fmt"
    "time"

    "github.com/jackc/pgx/v5/pgxpool"
    "github.com/google/uuid"
)

type User struct {
    ID        uuid.UUID `json:"id"`
    Name      string    `json:"name"`
    Email     string    `json:"email"`
    CreatedAt time.Time `json:"created_at"`
}

type UserRepository struct {
    pool *pgxpool.Pool
}

func (r *UserRepository) Create(ctx context.Context, user *User) error {
    user.ID = uuid.New()
    user.CreatedAt = time.Now()
    _, err := r.pool.Exec(ctx,
        `INSERT INTO users (id, name, email, created_at) VALUES ($1, $2, $3, $4)`,
        user.ID, user.Name, user.Email, user.CreatedAt,
    )
    return err
}

func (r *UserRepository) GetByID(ctx context.Context, id uuid.UUID) (*User, error) {
    user := &User{}
    err := r.pool.QueryRow(ctx,
        `SELECT id, name, email, created_at FROM users WHERE id = $1`, id,
    ).Scan(&user.ID, &user.Name, &user.Email, &user.CreatedAt)
    if err != nil {
        return nil, fmt.Errorf("get user: %w", err)
    }
    return user, nil
}

func (r *UserRepository) List(ctx context.Context, limit, offset int) ([]User, error) {
    rows, err := r.pool.Query(ctx,
        `SELECT id, name, email, created_at FROM users ORDER BY created_at DESC LIMIT $1 OFFSET $2`,
        limit, offset,
    )
    if err != nil {
        return nil, err
    }
    defer rows.Close()

    var users []User
    for rows.Next() {
        var u User
        if err := rows.Scan(&u.ID, &u.Name, &u.Email, &u.CreatedAt); err != nil {
            return nil, err
        }
        users = append(users, u)
    }
    return users, nil
}

Exercice 21 : Redis Caching

Code complet

package main

import (
    "context"
    "encoding/json"
    "time"

    "github.com/redis/go-redis/v9"
)

type Cache struct {
    client *redis.Client
    ttl    time.Duration
}

func NewCache(addr string, password string, db int, ttl time.Duration) *Cache {
    return &Cache{
        client: redis.NewClient(&redis.Options{
            Addr:     addr,
            Password: password,
            DB:       db,
        }),
        ttl: ttl,
    }
}

func (c *Cache) Get(ctx context.Context, key string, dest interface{}) error {
    data, err := c.client.Get(ctx, key).Bytes()
    if err != nil {
        return err
    }
    return json.Unmarshal(data, dest)
}

func (c *Cache) Set(ctx context.Context, key string, value interface{}) error {
    data, err := json.Marshal(value)
    if err != nil {
        return err
    }
    return c.client.Set(ctx, key, data, c.ttl).Err()
}

func (c *Cache) Delete(ctx context.Context, key string) error {
    return c.client.Del(ctx, key).Err()
}

// Distributed lock
func (c *Cache) AcquireLock(ctx context.Context, key string, ttl time.Duration) (bool, error) {
    return c.client.SetNX(ctx, key, "locked", ttl).Result()
}

func (c *Cache) ReleaseLock(ctx context.Context, key string) error {
    return c.client.Del(ctx, key).Err()
}

Exercice 22 : Repository Pattern

Code complet

package main

import (
    "context"
    "fmt"
    "sync"

    "github.com/google/uuid"
)

type User struct {
    ID    uuid.UUID
    Name  string
    Email string
}

type UserRepository interface {
    Create(ctx context.Context, user *User) error
    GetByID(ctx context.Context, id uuid.UUID) (*User, error)
    List(ctx context.Context) ([]User, error)
    Update(ctx context.Context, user *User) error
    Delete(ctx context.Context, id uuid.UUID) error
}

type InMemoryUserRepository struct {
    mu    sync.RWMutex
    users map[uuid.UUID]*User
}

func NewInMemoryUserRepository() *InMemoryUserRepository {
    return &InMemoryUserRepository{users: make(map[uuid.UUID]*User)}
}

func (r *InMemoryUserRepository) Create(ctx context.Context, user *User) error {
    r.mu.Lock()
    defer r.mu.Unlock()
    user.ID = uuid.New()
    r.users[user.ID] = user
    return nil
}

func (r *InMemoryUserRepository) GetByID(ctx context.Context, id uuid.UUID) (*User, error) {
    r.mu.RLock()
    defer r.mu.RUnlock()
    user, ok := r.users[id]
    if !ok {
        return nil, fmt.Errorf("user not found: %s", id)
    }
    return user, nil
}

func (r *InMemoryUserRepository) List(ctx context.Context) ([]User, error) {
    r.mu.RLock()
    defer r.mu.RUnlock()
    users := make([]User, 0, len(r.users))
    for _, u := range r.users {
        users = append(users, *u)
    }
    return users, nil
}

func (r *InMemoryUserRepository) Update(ctx context.Context, user *User) error {
    r.mu.Lock()
    defer r.mu.Unlock()
    if _, ok := r.users[user.ID]; !ok {
        return fmt.Errorf("user not found: %s", user.ID)
    }
    r.users[user.ID] = user
    return nil
}

func (r *InMemoryUserRepository) Delete(ctx context.Context, id uuid.UUID) error {
    r.mu.Lock()
    defer r.mu.Unlock()
    if _, ok := r.users[id]; !ok {
        return fmt.Errorf("user not found: %s", id)
    }
    delete(r.users, id)
    return nil
}

Exercice 23 : Transaction Management

Code complet

package main

import (
    "context"
    "fmt"

    "github.com/jackc/pgx/v5/pgxpool"
)

type AccountService struct {
    pool *pgxpool.Pool
}

func (s *AccountService) Transfer(ctx context.Context, fromID, toID string, amount float64) error {
    tx, err := s.pool.Begin(ctx)
    if err != nil {
        return fmt.Errorf("begin tx: %w", err)
    }
    defer tx.Rollback(ctx)

    _, err = tx.Exec(ctx,
        `UPDATE accounts SET balance = balance - $1 WHERE id = $2 AND balance >= $1`,
        amount, fromID,
    )
    if err != nil {
        return fmt.Errorf("debit: %w", err)
    }

    _, err = tx.Exec(ctx,
        `UPDATE accounts SET balance = balance + $1 WHERE id = $2`,
        amount, toID,
    )
    if err != nil {
        return fmt.Errorf("credit: %w", err)
    }

    _, err = tx.Exec(ctx,
        `INSERT INTO transfers (from_id, to_id, amount) VALUES ($1, $2, $3)`,
        fromID, toID, amount,
    )
    if err != nil {
        return fmt.Errorf("log transfer: %w", err)
    }

    return tx.Commit(ctx)
}

Exercice 24 : Table-Driven Tests

Code complet

package main

import (
    "testing"
)

func CalculateTax(amount float64, rate float64) (float64, error) {
    if amount < 0 {
        return 0, fmt.Errorf("negative amount: %f", amount)
    }
    if rate < 0 || rate > 1 {
        return 0, fmt.Errorf("invalid rate: %f", rate)
    }
    return amount * rate, nil
}

func TestCalculateTax(t *testing.T) {
    tests := []struct {
        name    string
        amount  float64
        rate    float64
        want    float64
        wantErr bool
    }{
        {"zero amount", 0, 0.2, 0, false},
        {"standard rate", 100, 0.2, 20, false},
        {"full rate", 100, 1, 100, false},
        {"negative amount", -100, 0.2, 0, true},
        {"negative rate", 100, -0.1, 0, true},
        {"over 100% rate", 100, 1.5, 0, true},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := CalculateTax(tt.amount, tt.rate)
            if (err != nil) != tt.wantErr {
                t.Errorf("CalculateTax() error = %v, wantErr %v", err, tt.wantErr)
                return
            }
            if got != tt.want {
                t.Errorf("CalculateTax() = %v, want %v", got, tt.want)
            }
        })
    }
}

Exercice 25 : Mocking

Code complet

package main

import (
    "testing"

    "github.com/stretchr/testify/mock"
)

type EmailSender interface {
    Send(to, subject, body string) error
}

type MockEmailSender struct {
    mock.Mock
}

func (m *MockEmailSender) Send(to, subject, body string) error {
    args := m.Called(to, subject, body)
    return args.Error(0)
}

type NotificationService struct {
    sender EmailSender
}

func (s *NotificationService) Notify(email, message string) error {
    return s.sender.Send(email, "Notification", message)
}

func TestNotificationService_Notify(t *testing.T) {
    mockSender := new(MockEmailSender)
    service := &NotificationService{sender: mockSender}

    mockSender.On("Send", "test@example.com", "Notification", "Hello").
        Return(nil)

    err := service.Notify("test@example.com", "Hello")
    assert.NoError(t, err)
    mockSender.AssertExpectations(t)
}

Exercice 26 : Integration Tests

Code complet

package main

import (
    "context"
    "testing"

    "github.com/testcontainers/testcontainers-go"
    "github.com/testcontainers/testcontainers-go/wait"
)

func TestPostgresIntegration(t *testing.T) {
    if testing.Short() {
        t.Skip("skipping integration test")
    }

    ctx := context.Background()
    req := testcontainers.ContainerRequest{
        Image:        "postgres:16-alpine",
        ExposedPorts: []string{"5432/tcp"},
        Env: map[string]string{
            "POSTGRES_DB":       "testdb",
            "POSTGRES_USER":     "test",
            "POSTGRES_PASSWORD": "test",
        },
        WaitingFor: wait.ForLog("database system is ready"),
    }

    postgres, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
        ContainerRequest: req,
        Started:          true,
    })
    if err != nil {
        t.Fatal(err)
    }
    defer postgres.Terminate(ctx)

    // Use the container in tests
    // repo := NewUserRepository(postgres.GetConnectionString())
}

Exercice 27 : Fuzzing

Code complet

package main

import (
    "errors"
    "regexp"
    "testing"
)

var phoneRegex = regexp.MustCompile(`^\+?(\d{1,3})?[-. ]?\(?\d{1,4}\)?[-. ]?\d{1,4}[-. ]?\d{1,9}$`)

func ParsePhoneNumber(s string) error {
    if len(s) > 20 {
        return errors.New("too long")
    }
    if !phoneRegex.MatchString(s) {
        return errors.New("invalid format")
    }
    return nil
}

func FuzzParsePhoneNumber(f *testing.F) {
    seedCorpora := []string{
        "+1-555-123-4567",
        "+33 6 12 34 56 78",
        "1234567890",
        "+1 (555) 123-4567",
    }
    for _, seed := range seedCorpora {
        f.Add(seed)
    }

    f.Fuzz(func(t *testing.T, input string) {
        err := ParsePhoneNumber(input)
        if err != nil {
            t.Skip()
        }
    })
}

Exercice 28 : CLI Todo avec Cobra

Code complet

package main

import (
    "encoding/json"
    "fmt"
    "os"
    "path/filepath"

    "github.com/spf13/cobra"
)

type Task struct {
    ID   int    `json:"id"`
    Done bool   `json:"done"`
    Text string `json:"text"`
}

type TodoList struct {
    Tasks  []Task `json:"tasks"`
    NextID int    `json:"next_id"`
}

var dataFile string

func loadTasks() (*TodoList, error) {
    data, err := os.ReadFile(dataFile)
    if err != nil {
        if os.IsNotExist(err) {
            return &TodoList{NextID: 1}, nil
        }
        return nil, err
    }
    var list TodoList
    if err := json.Unmarshal(data, &list); err != nil {
        return nil, err
    }
    return &list, nil
}

func saveTasks(list *TodoList) error {
    data, err := json.MarshalIndent(list, "", "  ")
    if err != nil {
        return err
    }
    return os.WriteFile(dataFile, data, 0644)
}

func main() {
    home, _ := os.UserHomeDir()
    dataFile = filepath.Join(home, ".todo", "tasks.json")
    os.MkdirAll(filepath.Dir(dataFile), 0755)

    var rootCmd = &cobra.Command{Use: "todo"}

    var addCmd = &cobra.Command{
        Use:   "add [task]",
        Short: "Add a new task",
        Args:  cobra.ExactArgs(1),
        Run: func(cmd *cobra.Command, args []string) {
            list, _ := loadTasks()
            task := Task{ID: list.NextID, Text: args[0]}
            list.Tasks = append(list.Tasks, task)
            list.NextID++
            saveTasks(list)
            fmt.Printf("Added task %d: %s\n", task.ID, task.Text)
        },
    }

    var listCmd = &cobra.Command{
        Use:   "list",
        Short: "List all tasks",
        Run: func(cmd *cobra.Command, args []string) {
            list, _ := loadTasks()
            for _, t := range list.Tasks {
                status := " "
                if t.Done {
                    status = "✓"
                }
                fmt.Printf("[%s] %d: %s\n", status, t.ID, t.Text)
            }
        },
    }

    rootCmd.AddCommand(addCmd, listCmd)
    rootCmd.Execute()
}

Exercice 29 : CLI with Config

Code complet

package main

import (
    "fmt"
    "log"

    "github.com/spf13/cobra"
    "github.com/spf13/viper"
)

func main() {
    viper.SetConfigName("config")
    viper.SetConfigType("yaml")
    viper.AddConfigPath("$HOME/.app")
    viper.AddConfigPath(".")
    viper.AutomaticEnv()
    viper.SetEnvPrefix("APP")

    viper.SetDefault("server.port", 8080)
    viper.SetDefault("database.host", "localhost")

    if err := viper.ReadInConfig(); err != nil {
        if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
            log.Fatalf("error reading config: %s", err)
        }
    }

    var rootCmd = &cobra.Command{Use: "app"}

    var serveCmd = &cobra.Command{
        Use:   "serve",
        Short: "Start the server",
        Run: func(cmd *cobra.Command, args []string) {
            port := viper.GetInt("server.port")
            dbHost := viper.GetString("database.host")
            fmt.Printf("Starting server on port %d (db: %s)\n", port, dbHost)
        },
    }

    rootCmd.AddCommand(serveCmd)
    rootCmd.Execute()
}

Exercice 30 : Multi-Command CLI

Code complet

package main

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
)

var outputDir string
var buildTags string
var environment string

func main() {
    var rootCmd = &cobra.Command{
        Use:   "project",
        Short: "Project management CLI",
    }

    var initCmd = &cobra.Command{
        Use:   "init [name]",
        Short: "Initialize a new project",
        Args:  cobra.ExactArgs(1),
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Printf("Initializing project: %s\n", args[0])
        },
    }

    var buildCmd = &cobra.Command{
        Use:   "build",
        Short: "Build the project",
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Printf("Building with output=%s tags=%s\n", outputDir, buildTags)
        },
    }

    buildCmd.Flags().StringVarP(&outputDir, "output", "o", "./dist", "Output directory")
    buildCmd.Flags().StringVarP(&buildTags, "tags", "t", "", "Build tags")

    var deployCmd = &cobra.Command{
        Use:   "deploy",
        Short: "Deploy the project",
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Printf("Deploying to environment: %s\n", environment)
        },
    }

    deployCmd.Flags().StringVarP(&environment, "env", "e", "dev", "Deployment environment")
    deployCmd.MarkFlagRequired("env")

    rootCmd.AddCommand(initCmd, buildCmd, deployCmd)
    rootCmd.Execute()
}

Exercice 31 : gRPC Unary

Code complet

package main

// Proto definition:
// service UserService {
//   rpc CreateUser (CreateUserRequest) returns (User);
//   rpc GetUser (GetUserRequest) returns (User);
//   rpc ListUsers (ListUsersRequest) returns (ListUsersResponse);
// }
//
// message User {
//   string id = 1;
//   string name = 2;
//   string email = 3;
// }
// message CreateUserRequest { string name = 1; string email = 2; }
// message GetUserRequest { string id = 1; }
// message ListUsersRequest {}
// message ListUsersResponse { repeated User users = 1; }

import (
    "context"
    "log"
    "net"

    "google.golang.org/grpc"
    "google.golang.org/grpc/reflection"
    pb "path/to/proto"
)

type server struct {
    pb.UnimplementedUserServiceServer
    users map[string]*pb.User
}

func (s *server) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.User, error) {
    user := &pb.User{
        Id:    uuid.New().String(),
        Name:  req.Name,
        Email: req.Email,
    }
    s.users[user.Id] = user
    return user, nil
}

func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
    user, ok := s.users[req.Id]
    if !ok {
        return nil, status.Errorf(codes.NotFound, "user not found")
    }
    return user, nil
}

func main() {
    lis, _ := net.Listen("tcp", ":50051")
    s := grpc.NewServer()
    pb.RegisterUserServiceServer(s, &server{users: make(map[string]*pb.User)})
    reflection.Register(s)
    log.Fatal(s.Serve(lis))
}

Exercice 32 : gRPC Server Streaming

Code complet

package main

import (
    "log"
    "net"
    "time"

    "google.golang.org/grpc"
    pb "path/to/proto"
)

type LogServer struct {
    pb.UnimplementedLogServiceServer
}

func (s *LogServer) StreamLogs(req *pb.StreamLogsRequest, stream pb.LogService_StreamLogsServer) error {
    for i := 0; i < 100; i++ {
        entry := &pb.LogEntry{
            Timestamp: time.Now().Unix(),
            Level:     "INFO",
            Message:   fmt.Sprintf("log entry %d", i),
        }
        if err := stream.Send(entry); err != nil {
            return err
        }
        time.Sleep(100 * time.Millisecond)
    }
    return nil
}

Exercice 33 : gRPC Bidirectional Streaming

Code complet

package main

import (
    "io"
    "log"
    "net"

    "google.golang.org/grpc"
    pb "path/to/proto"
)

type ChatServer struct {
    pb.UnimplementedChatServiceServer
}

func (s *ChatServer) Chat(stream pb.ChatService_ChatServer) error {
    for {
        msg, err := stream.Recv()
        if err == io.EOF {
            return nil
        }
        if err != nil {
            return err
        }
        // Broadcast to all connected clients
        stream.Send(&pb.ChatMessage{
            User:    msg.User,
            Message: msg.Message,
        })
    }
}

Exercice 34 : Generic Stack

Code complet

package main

import "sync"

type Stack[T any] struct {
    mu    sync.RWMutex
    items []T
}

func New[T any]() *Stack[T] {
    return &Stack[T]{}
}

func (s *Stack[T]) Push(item T) {
    s.mu.Lock()
    defer s.mu.Unlock()
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    s.mu.Lock()
    defer s.mu.Unlock()
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item, true
}

func (s *Stack[T]) Peek() (T, bool) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    return s.items[len(s.items)-1], true
}

func (s *Stack[T]) Len() int {
    s.mu.RLock()
    defer s.mu.RUnlock()
    return len(s.items)
}

func (s *Stack[T]) IsEmpty() bool {
    return s.Len() == 0
}

Exercice 35 : Generic Map/Reduce/Filter

Code complet

package main

func Map[T, U any](s []T, f func(T) U) []U {
    result := make([]U, len(s))
    for i, v := range s {
        result[i] = f(v)
    }
    return result
}

func Filter[T any](s []T, f func(T) bool) []T {
    var result []T
    for _, v := range s {
        if f(v) {
            result = append(result, v)
        }
    }
    return result
}

func Reduce[T, U any](s []T, init U, f func(U, T) U) U {
    result := init
    for _, v := range s {
        result = f(result, v)
    }
    return result
}

Exercice 36 : Generic Cache

Code complet

package main

import (
    "sync"
    "time"
)

type cacheItem[V any] struct {
    value    V
    expiresAt time.Time
}

type Cache[K comparable, V any] struct {
    mu       sync.RWMutex
    items    map[K]cacheItem[V]
    ttl      time.Duration
    stopCh   chan struct{}
}

func NewCache[K comparable, V any](ttl time.Duration, cleanupInterval time.Duration) *Cache[K, V] {
    c := &Cache[K, V]{
        items:  make(map[K]cacheItem[V]),
        ttl:    ttl,
        stopCh: make(chan struct{}),
    }
    go c.cleanup(cleanupInterval)
    return c
}

func (c *Cache[K, V]) Get(key K) (V, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    item, ok := c.items[key]
    if !ok || time.Now().After(item.expiresAt) {
        var zero V
        return zero, false
    }
    return item.value, true
}

func (c *Cache[K, V]) Set(key K, value V) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.items[key] = cacheItem[V]{
        value:     value,
        expiresAt: time.Now().Add(c.ttl),
    }
}

func (c *Cache[K, V]) Delete(key K) {
    c.mu.Lock()
    defer c.mu.Unlock()
    delete(c.items, key)
}

func (c *Cache[K, V]) cleanup(interval time.Duration) {
    ticker := time.NewTicker(interval)
    defer ticker.Stop()
    for {
        select {
        case <-ticker.C:
            c.mu.Lock()
            now := time.Now()
            for k, v := range c.items {
                if now.After(v.expiresAt) {
                    delete(c.items, k)
                }
            }
            c.mu.Unlock()
        case <-c.stopCh:
            return
        }
    }
}

Exercice 37 : CPU/Memory Profiling

Code complet

package main

import (
    "bytes"
    "encoding/json"
    "os"
    "runtime/pprof"
)

type Data struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Value string `json:"value"`
}

func generateData(n int) []Data {
    data := make([]Data, n)
    for i := 0; i < n; i++ {
        data[i] = Data{
            ID:    i,
            Name:  "item-" + string(rune(i)),
            Value: "value-" + string(rune(i)),
        }
    }
    return data
}

func main() {
    f, _ := os.Create("cpu.prof")
    pprof.StartCPUProfile(f)
    defer pprof.StopCPUProfile()

    data := generateData(100000)
    var buf bytes.Buffer
    json.NewEncoder(&buf).Encode(data)
}

Exercice 38 : Trace Execution

Code complet

package main

import (
    "context"
    "os"
    "runtime/trace"
    "sync"
)

func worker(ctx context.Context, id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs {
        results <- job * 2
    }
}

func main() {
    f, _ := os.Create("trace.out")
    trace.Start(f)
    defer trace.Stop()

    ctx := context.Background()
    jobs := make(chan int, 100)
    results := make(chan int, 100)
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go worker(ctx, i, jobs, results, &wg)
    }

    for i := 0; i < 100; i++ {
        jobs <- i
    }
    close(jobs)
    wg.Wait()
    close(results)
}

Exercice 39 : Docker Multi-stage Build

Dockerfile

# Stage 1: Build
FROM golang:1.23-alpine AS builder
RUN apk add --no-cache git ca-certificates
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/server

# Stage 2: Runtime
FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /app/server /server
USER 65534:65534
EXPOSE 8080
ENTRYPOINT ["/server"]

Exercice 40 : Kubernetes Deployment

k8s/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: go-api
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  selector:
    matchLabels:
      app: go-api
  template:
    metadata:
      labels:
        app: go-api
    spec:
      containers:
      - name: api
        image: myapp:latest
        ports:
        - containerPort: 8080
        envFrom:
        - configMapRef:
            name: go-api-config
        readinessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 10
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 15
          periodSeconds: 20
        resources:
          requests:
            memory: "64Mi"
            cpu: "100m"
          limits:
            memory: "128Mi"
            cpu: "200m"
---
apiVersion: v1
kind: Service
metadata:
  name: go-api
spec:
  selector:
    app: go-api
  ports:
  - port: 80
    targetPort: 8080
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: go-api-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: go-api
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

Ceci conclut les 40 corrigés d'exercices. Chaque solution met l'accent sur les idiomes Go, la gestion correcte des erreurs, et les bonnes pratiques. Consultez le chapitre 20 pour les ressources complémentaires.