Compila em segundos, roda como um binário só, escala com goroutines

Apostila completa de Go (Golang) para Backend e Cloud-Native

Go foi desenhado por engenharia da Google para escrever serviços de rede confiáveis, compiláveis rápido e fáceis de operar — e virou a língua da infraestrutura moderna: Docker, Kubernetes, Terraform, Prometheus são Go. Esta apostila vai da sintaxe minimalista ao modelo de concorrência CSP, à construção de serviços HTTP e gRPC de produção, ao runtime e ao profiling — com o olho no que empresas pedem em vagas de back-end, plataforma e SRE.

10 módulosGoroutines · channels · contextnet/http · slog · gRPCDocker distroless · OpenTelemetryBoxes de entrevistaExercícios com gabarito
MÓDULO 01 · BÁSICO

Por que Go existe e onde ele domina

Objetivo: entender a filosofia de design de Go, os problemas que ele resolve bem, e quando não é a ferramenta certa.

1.1 O problema que Go ataca

Go nasceu na Google (2009) para uma dor específica: escrever serviços de rede concorrentes, em times grandes, com builds lentos (C++), deploy complicado (dependências de runtime) e curva de aprendizado longa. A resposta foi uma linguagem deliberadamente pequena: poucos conceitos, uma forma canônica de fazer cada coisa, compilação quase instantânea, e um binário estático único sem dependências.

💡 A filosofia em uma frase

"Menos é exponencialmente mais." Go troca expressividade e recursos por legibilidade e uniformidade: qualquer programador Go lê código Go de qualquer outro sem esforço, porque quase não há dialetos. Não tem herança, generics foram adicionados só em 2022 e com parcimônia, e o gofmt impõe um estilo único para toda a comunidade. Isso frustra quem vem de linguagens ricas — e é exatamente o ponto: Go otimiza para o time e para o código que dura anos.

1.2 Os pontos fortes

CaracterísticaO que significa na prática
Compilação rápidaBuild de um serviço grande em segundos; ciclo de feedback curto
Binário estático únicoDeploy = copiar um arquivo; imagem Docker FROM scratch de poucos MB; sem "instalar runtime"
Concorrência de primeira classeGoroutines (baratas, milhares) + channels; modelo CSP em vez de callback hell (Módulos 5–6)
Garbage collector de baixa latênciaPausas de sub-milissegundo; sem gerência manual de memória
Biblioteca padrão fortenet/http, encoding/json, crypto, testing — dá para ir longe sem framework
Ferramental oficialgo build/test/fmt/vet, race detector, pprof, módulos — tudo na caixa
Compilação cruzada trivialGOOS=linux GOARCH=arm64 go build de qualquer máquina

1.3 Onde Go domina

1.4 Quando NÃO Go

💼 Mercado de trabalho

Go é linha de vaga constante em plataforma, infra, SRE, DevOps tooling e em back-end de empresas cloud-native (fintechs, streaming, adtech). A demanda cresceu junto com Kubernetes. Pergunta de abertura: "Por que escolher Go para um serviço?" — a resposta cita concorrência barata, deploy de binário único, compilação rápida, e o ecossistema cloud-native; e reconhece que para CRUD simples qualquer linguagem serve — o diferencial aparece em concorrência e operação.

✏️ Exercício 1 — Go ou não?

Para cada projeto, Go é boa escolha? (a) um operador de Kubernetes que reconcilia recursos custom; (b) um pipeline de treino de modelo de visão computacional; (c) uma CLI multiplataforma para desenvolvedores; (d) um dashboard web interativo; (e) um proxy reverso de alta vazão com rate limiting.

Gabarito: (a) Sim — é o caso canônico; controller-runtime e client-go são Go. (b) Não — ecossistema é Python (PyTorch). (c) Sim — binário único + cross-compile é a killer feature de CLI em Go. (d) Não para o front (é JS/TS); Go pode servir a API por trás. (e) Sim — concorrência, latência baixa, e há blocos prontos (net/http/httputil, golang.org/x/time/rate).

MÓDULO 02 · BÁSICO

A linguagem essencial

Objetivo: os blocos fundamentais — tipos, structs, slices e maps, funções com múltiplos retornos, ponteiros sem susto, zero values, e módulos.

2.1 O primeiro programa e o módulo

// go.mod define o módulo e as dependências
// go mod init github.com/voce/servico

package main

import (
    "fmt"
    "os"
)

func main() {
    if len(os.Args) < 2 {
        fmt.Fprintln(os.Stderr, "uso: saudar <nome>")
        os.Exit(1)
    }
    fmt.Printf("Olá, %s\n", os.Args[1])
}

2.2 Tipos, structs e zero values

type Usuario struct {
    ID    string
    Nome  string
    Idade int
    Ativo bool
}

u := Usuario{ID: "u1", Nome: "Ana"}   // Idade=0, Ativo=false (zero values)
var v Usuario                              // tudo zero: "", "", 0, false
💡 Zero value é utilizável

Go garante que toda variável começa "zerada" de forma útil: 0, "", false, nil, e uma struct com todos os campos zerados. Bibliotecas Go bem feitas fazem o zero value já ser um estado válido (var b bytes.Buffer já funciona; var mu sync.Mutex já está pronto). Não há "undefined" nem "null pointer" surpresa em valores — só em ponteiros, slices, maps, channels e interfaces explicitamente nil.

2.3 Slices e maps

// slice: janela dinâmica sobre um array; a estrutura de coleção que você usa 95% do tempo
nums := []int{1, 2, 3}
nums = append(nums, 4)              // append pode realocar; sempre reatribua
sub := nums[1:3]                    // [2 3] — compartilha o array subjacente!

// map: tabela hash
idade := map[string]int{"ana": 28}
idade["beto"] = 31
v, ok := idade["carol"]         // ok=false se ausente; v é o zero value
delete(idade, "ana")

2.4 Funções e múltiplos retornos

func dividir(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("divisão por zero")
    }
    return a / b, nil
}

q, err := dividir(10, 2)
if err != nil {
    // trate o erro AQUI, agora (Módulo 4)
}

O idioma valor, err := f() seguido de if err != nil é onipresente em Go — a linguagem não tem exceções para fluxo normal (Módulo 4).

2.5 Ponteiros (sem aritmética, sem medo)

func envelhecer(u *Usuario) {   // recebe ponteiro → altera o original
    u.Idade++
}
u := Usuario{Nome: "Ana", Idade: 28}
envelhecer(&u)                   // & pega o endereço
💼 Mercado de trabalho

Perguntas de screening: "O que é um zero value?", "Diferença entre array e slice; o que append pode fazer?" (realocar → sempre reatribuir; sub-slices compartilham memória), "Value ou pointer receiver?", "Por que iterar um map dá ordem diferente a cada vez?" (intencional, para não criar dependência de ordem).

✏️ Exercício 2 — O bug do slice

Este código imprime o quê e por quê? a := []int{1,2,3,4,5}; b := a[1:3]; b = append(b, 99); fmt.Println(a, b)

Gabarito: Imprime [1 2 3 99 5] [2 3 99]. b tem len=2, cap=4 (compartilha o array de a a partir do índice 1). O append ainda cabe na capacidade, então não realoca — escreve 99 na posição do array subjacente que corresponde a a[3], sobrescrevendo o 4. Lição: sub-slices compartilham memória; se você precisa isolar, use append([]int{}, a[1:3]...) ou o "full slice expression" a[1:3:3] para limitar a capacidade.

MÓDULO 03 · INTERMEDIÁRIO

Interfaces e composição

Objetivo: entender as interfaces implícitas de Go, por que "interfaces pequenas" é a regra, e como composição por embedding substitui herança.

3.1 Interfaces implícitas

Em Go, um tipo satisfaz uma interface só por ter os métodos certos — não há implements. Isso desacopla: a interface pode ser definida onde é consumida, mesmo sem o autor do tipo saber que ela existe.

type Notificador interface {
    Notificar(msg string) error
}

type EmailSender struct{ /* ... */ }
func (e EmailSender) Notificar(msg string) error { /* ... */ return nil }
// EmailSender satisfaz Notificador automaticamente — nenhuma declaração

func Alertar(n Notificador, msg string) error {
    return n.Notificar(msg)   // funciona com qualquer coisa que tenha Notificar
}

3.2 Interfaces pequenas

💡 "The bigger the interface, the weaker the abstraction"

As interfaces mais poderosas da stdlib têm um método: io.Reader (Read), io.Writer (Write), fmt.Stringer (String), error (Error). Isso permite compor: um gzip.Writer envolve um io.Writer, que pode ser um arquivo, uma resposta HTTP, um buffer... Prefira várias interfaces pequenas a uma "God interface" com 15 métodos.

3.3 Aceite interfaces, retorne structs

Idioma clássico: uma função deve aceitar a interface mais estreita que precisa (flexibilidade para quem chama) e retornar o tipo concreto (o chamador vê tudo que o tipo oferece e você não trava a API numa interface). Exceção comum: retornar error (interface) e construtores que retornam interface por design de plugin.

3.4 Composição por embedding

type Logger struct{ /* ... */ }
func (l *Logger) Info(msg string) { /* ... */ }

type Servico struct {
    *Logger              // embedding: Servico "ganha" o método Info
    db *sql.DB
}

s := &Servico{Logger: &Logger{}, db: db}
s.Info("iniciado")          // promovido do Logger embutido

3.5 Type assertion e type switch

func descrever(v any) string {   // any == interface{}
    switch x := v.(type) {
    case int:
        return fmt.Sprintf("int %d", x)
    case string:
        return "string " + x
    case Notificador:
        return "sabe notificar"
    default:
        return "desconhecido"
    }
}

Use com parcimônia — any + type switch abandona a segurança de tipos. Boa para desserialização, plugins, e casos genuinamente dinâmicos.

💼 Mercado de trabalho

Perguntas: "Como funcionam interfaces em Go?" (implícitas, satisfeitas por estrutura, definidas onde consumidas), "Por que interfaces pequenas?", "Go tem herança?" (não — composição por embedding; polimorfismo só via interface), "O que é 'aceite interfaces, retorne structs'?", "Quando um valor de interface é nil?" (a pegadinha: uma interface com tipo concreto mas ponteiro nil não é == nil).

✏️ Exercício 3 — A pegadinha do nil

Por que este código imprime não-nil? type E struct{}; func (e *E) Error() string { return "x" }; func faz() error { var e *E = nil; return e }; func main() { if faz() != nil { fmt.Println("não-nil") } }

Gabarito: Um valor de interface tem dois componentes: o tipo dinâmico e o valor. faz() retorna uma interface error cujo tipo dinâmico é *E e cujo valor é nil. A interface só é == nil quando ambos são nil. Aqui o tipo é *E (não-nil), então a interface não é nil, mesmo o ponteiro sendo nil. Lição: nunca retorne uma variável de ponteiro concreto tipada como error; retorne nil literal quando não há erro.

MÓDULO 04 · INTERMEDIÁRIO

Erros como valores

Objetivo: o modelo de erros de Go — sem exceções para fluxo normal — com wrapping, inspeção (Is/As), quando usar panic, e defer/recover.

4.1 A filosofia

Em Go, erros são valores comuns retornados pela função, não um mecanismo de fluxo separado. Você lida com eles explicitamente, ali, na hora. Isso é verboso (if err != nil por toda parte) mas torna o caminho de falha visível e local — você vê exatamente o que pode dar errado e o que é feito a respeito.

type error interface {   // a interface embutida — só isso
    Error() string
}

4.2 Criar e enriquecer erros

// erro simples
return errors.New("conexão recusada")
return fmt.Errorf("usuário %s não encontrado", id)

// wrapping: adiciona contexto SEM perder o erro original (o %w)
if err != nil {
    return fmt.Errorf("buscar pedido %s: %w", id, err)
}
// resultado: "buscar pedido o1: consultar db: connection reset"

4.3 Inspecionar erros: errors.Is e errors.As

EstratégiaComoQuando
Sentinel errorUm valor exportado (var ErrNotFound = errors.New("not found")); comparar com errors.Is(err, ErrNotFound)Condições bem conhecidas do domínio (não encontrado, sem permissão)
Tipo de erroUm struct que implementa error; extrair com errors.As(err, &target) para ler camposO erro carrega dados úteis (campo inválido, código HTTP, retryable?)
Opacoerr != nil; a mensagem é para humano/logO chamador não precisa distinguir — a maioria dos casos
if errors.Is(err, sql.ErrNoRows) {
    return nil, ErrPedidoNaoEncontrado   // traduz para o erro do seu domínio
}

var ve *ValidationError
if errors.As(err, &ve) {
    return respostaHTTP(422, ve.Campos)   // ve agora aponta para o erro tipado
}

4.4 Panic e recover

func comRecover(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if p := recover(); p != nil {
                slog.Error("panic no handler", "panic", p)
                http.Error(w, "erro interno", 500)
            }
        }()
        next.ServeHTTP(w, r)
    })
}

4.5 defer

defer agenda uma chamada para quando a função retornar (em ordem LIFO). Uso canônico: liberar recursos junto de onde são adquiridos, para não esquecer em nenhum caminho de saída.

f, err := os.Open(path)
if err != nil { return err }
defer f.Close()              // roda ao sair da função, sempre
// ... use f livremente; todo return já fecha
💼 Mercado de trabalho

Perguntas: "Como Go trata erros e por que não tem exceções?" (erros são valores; caminho de falha explícito e local), "Diferença entre errors.Is e errors.As" (Is compara com um sentinel; As extrai um tipo), "Quando usar panic?" (irrecuperável / bug; nunca para erro esperado; recover no topo de handler), "O que %w faz?" (embrulha o erro preservando a cadeia para Is/As).

✏️ Exercício 4 — Projete os erros

Um repositório de pedidos: GetByID pode não encontrar o pedido, ou falhar por erro de banco. Como você desenha os erros para que o handler HTTP consiga responder 404 vs 500 sem acoplar-se ao banco?

Gabarito: No repositório: definir var ErrNotFound = errors.New("pedido não encontrado") no pacote de domínio; em GetByID, quando errors.Is(err, sql.ErrNoRows), retornar fmt.Errorf("get pedido %s: %w", id, ErrNotFound); para outros erros de banco, fmt.Errorf("get pedido %s: %w", id, err) (opaco). No handler: if errors.Is(err, domain.ErrNotFound) { 404 } else if err != nil { log; 500 }. O handler nunca importa database/sql — o repositório traduz.

MÓDULO 05 · INTERMEDIÁRIO

Concorrência I — goroutines e channels

Objetivo: o modelo CSP de Go — goroutines baratas, channels para comunicação, select — e as primitivas de sincronização quando comunicar não basta.

5.1 Goroutines

Uma goroutine é uma função executando concorrentemente, gerida pelo runtime de Go (não pelo SO). Custa ~2 KB de stack inicial (que cresce sob demanda), então você pode ter centenas de milhares. O runtime as multiplexa sobre poucos threads do SO (Módulo 9).

go processar(pedido)        // dispara; main NÃO espera

// problema: se main() retorna, o programa acaba e a goroutine morre.
// você precisa de um mecanismo de sincronização.

5.2 Channels

Um channel é um tubo tipado por onde goroutines enviam e recebem valores, com sincronização embutida. É o mecanismo de comunicação idiomático.

ch := make(chan int)         // sem buffer: send bloqueia até alguém receber
buf := make(chan int, 10)     // com buffer 10: send bloqueia só quando cheio

go func() {
    ch <- 42            // envia
}()
v := <-ch              // recebe (bloqueia até ter valor)

close(ch)              // fecha; receber de canal fechado retorna zero, ok=false
for v := range ch {      // itera até o canal ser fechado
    _ = v
}
💡 "Don't communicate by sharing memory; share memory by communicating"

Em vez de várias goroutines acessando a mesma variável com locks, passe a propriedade do dado por um channel: quem tem o valor no channel é quem pode mexer nele. Isso elimina classes inteiras de bugs de concorrência. Nem sempre é possível/prático — daí as primitivas de sync — mas é o instinto padrão em Go.

5.3 select

select {
case v := <-entrada:
    processar(v)
case resultado <- calcular():
    // enviou
case <-time.After(2 * time.Second):
    return errTimeout          // timeout idiomático
case <-ctx.Done():
    return ctx.Err()           // cancelamento (Módulo 6)
}

select espera em vários channels ao mesmo tempo e age no primeiro que estiver pronto (aleatório se vários). É o coração de timeouts, cancelamento e multiplexação.

5.4 Quando channels não bastam: o pacote sync

PrimitivaUso
sync.Mutex / RWMutexProteger uma seção crítica / estrutura compartilhada. RWMutex permite múltiplos leitores.
sync.WaitGroupEsperar N goroutines terminarem (Add, Done, Wait)
sync.OnceExecutar uma inicialização exatamente uma vez (lazy singleton)
sync/atomicContadores e flags lock-free (atomic.Int64, CompareAndSwap)
sync.MapMap concorrente para padrões específicos (muita leitura, chaves estáveis) — raro; geralmente map + RWMutex

5.5 O race detector

Duas goroutines acessando a mesma memória sem sincronização, com pelo menos uma escrevendo = data race — comportamento indefinido. Go tem um detector embutido: go test -race, go run -race. Rode-o no CI. Ele não pega tudo (só o que executou), mas pega muito.

💼 Mercado de trabalho

Concorrência é o tema de entrevista de Go: "O que é uma goroutine e como difere de um thread?" (leve, gerida pelo runtime, multiplexada sobre threads), "Channel com e sem buffer", "Explique select", "Quando Mutex em vez de channel?" (proteger estado compartilhado simples; channels para transferir propriedade/coordenar), "O que é um data race e como você o encontra?" (-race).

✏️ Exercício 5 — Some em paralelo com segurança

Você tem 1000 URLs e quer baixar todas, somando o total de bytes. Esboce a solução com goroutines. Onde está o risco de race e como evitar?

Gabarito (esboço): Um sync.WaitGroup para esperar todas; limitar concorrência com um channel-semáforo sem := make(chan struct{}, 20) (adquire antes, libera com defer). Para somar: não incrementar uma variável total compartilhada direto (race) — use atomic.Int64.Add, OU cada goroutine envia seu resultado num channel e uma goroutine coletora soma, OU use errgroup (Módulo 6) e some no retorno. Rodar os testes com -race para confirmar.

MÓDULO 06 · AVANÇADO

Concorrência II — context e padrões

Objetivo: propagar cancelamento e deadlines com context.Context, aplicar os padrões de concorrência estruturada, e evitar goroutine leaks.

6.1 context.Context

Context carrega cancelamento, deadline/timeout e valores de escopo de requisição através da árvore de chamadas. Convenção: é o primeiro parâmetro de qualquer função que faz I/O, chamada de rede, ou trabalho longo — func Buscar(ctx context.Context, id string) (...).

ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
defer cancel()   // SEMPRE — libera recursos do context

row := db.QueryRowContext(ctx, query, id)   // a query é abortada se ctx expira

// numa goroutine que faz trabalho longo:
select {
case <-ctx.Done():
    return ctx.Err()   // context.Canceled ou context.DeadlineExceeded
case res := <-worker:
    return res
}

6.2 Padrões de concorrência

PadrãoIdeia
Worker poolN goroutines consomem tarefas de um channel; limita a concorrência e o uso de recursos
Fan-out / fan-inDistribuir trabalho para várias goroutines (fan-out) e juntar os resultados num channel (fan-in)
PipelineEstágios ligados por channels: gerar → transformar → filtrar → gravar, cada um uma goroutine
errgroupgolang.org/x/sync/errgroup: roda N funções concorrentes, cancela todas se uma falhar, e retorna o primeiro erro. É o jeito idiomático moderno de "faça essas 5 coisas em paralelo e me diga se alguma quebrou"
import "golang.org/x/sync/errgroup"

g, ctx := errgroup.WithContext(ctx)
g.SetLimit(10)                        // no máximo 10 concorrentes
for _, u := range urls {
    u := u
    g.Go(func() error {
        return baixar(ctx, u)         // se retornar erro, ctx é cancelado p/ as outras
    })
}
if err := g.Wait(); err != nil {   // espera todas; primeiro erro
    return err
}

6.3 Goroutine leaks

Uma goroutine que bloqueia para sempre (num channel que ninguém vai fechar, num send sem receptor) nunca é coletada — vaza memória e recursos. Causas e prevenções:

6.4 Graceful shutdown

srv := &http.Server{Addr: ":8080", Handler: mux}

go func() { srv.ListenAndServe() }()

// espera SIGINT/SIGTERM
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
<-ctx.Done()

// dá 15s para as requisições em andamento terminarem
shutCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
srv.Shutdown(shutCtx)
💼 Mercado de trabalho

Perguntas sénior: "Para que serve context.Context e por que é o primeiro parâmetro?", "O que é um goroutine leak e como preveni-lo?" (sempre um caminho de saída via ctx/close), "Como você limita concorrência ao chamar uma API externa?" (worker pool / semáforo / errgroup.SetLimit), "Como fazer graceful shutdown de um servidor HTTP?".

✏️ Exercício 6 — Ache o leak

Este código vaza uma goroutine em algum caso. Qual e por quê? func first(ctx context.Context, chs ...<-chan int) int { out := make(chan int); for _, c := range chs { go func(c <-chan int) { out <- <-c }(c) }; return <-out }

Gabarito: A função retorna assim que a primeira goroutine consegue enviar em out. As demais goroutines ficam bloqueadas para sempre tentando out <- ... num channel sem buffer que ninguém mais lê — leak (uma por channel restante). Correções: dar buffer suficiente a out (make(chan int, len(chs))) para que os sends não bloqueiem; e/ou usar select com case <-ctx.Done() nas goroutines para que o cancelamento as libere. O idioma correto costuma combinar buffer + context.

MÓDULO 07 · AVANÇADO

Construindo serviços HTTP

Objetivo: montar um serviço de produção com a stdlib — roteamento, middleware, logging estruturado, banco de dados e testes — sem depender de um framework pesado.

7.1 net/http e o roteador da stdlib

Desde o Go 1.22, o http.ServeMux da biblioteca padrão faz roteamento por método e path params — muitos serviços não precisam mais de um roteador externo (chi, gorilla/mux, gin) para o básico.

mux := http.NewServeMux()
mux.HandleFunc("GET /pedidos/{id}", getPedido)
mux.HandleFunc("POST /pedidos", criarPedido)

func getPedido(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    p, err := svc.Get(r.Context(), id)
    if errors.Is(err, domain.ErrNotFound) {
        problema(w, 404, "pedido não encontrado"); return
    }
    if err != nil {
        slog.Error("getPedido", "err", err); problema(w, 500, "erro interno"); return
    }
    writeJSON(w, 200, p)
}

7.2 Middleware

Middleware em Go é uma função func(http.Handler) http.Handler — você encadeia: logging, recover (Módulo 4), autenticação, rate limit, CORS, tracing.

func comLog(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        início := time.Now()
        rec := &statusRecorder{ResponseWriter: w, status: 200}
        next.ServeHTTP(rec, r)
        slog.Info("http", "method", r.Method, "path", r.URL.Path,
            "status", rec.status, "dur_ms", time.Since(início).Milliseconds())
    })
}

handler := comLog(comRecover(comTrace(mux)))

7.3 Logging estruturado com slog

Desde o Go 1.21, log/slog é o logger estruturado da stdlib: níveis, atributos chave-valor, saída JSON para produção, e context-aware.

logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
slog.SetDefault(logger)

slog.Info("pedido criado", "pedido_id", id, "valor_cents", 1990, "trace_id", tid)
// {"time":"...","level":"INFO","msg":"pedido criado","pedido_id":"o1",...}

7.4 Banco de dados

7.5 Testes

// table-driven test: o idioma de Go para cobrir muitos casos
func TestValidarCPF(t *testing.T) {
    casos := []struct {
        nome string
        in   string
        want bool
    }{
        {"válido", "11144477735", true},
        {"dígito errado", "11144477730", false},
        {"curto", "123", false},
    }
    for _, c := range casos {
        t.Run(c.nome, func(t *testing.T) {
            if got := ValidarCPF(c.in); got != c.want {
                t.Errorf("ValidarCPF(%q) = %v, quer %v", c.in, got, c.want)
            }
        })
    }
}
💼 Mercado de trabalho

Perguntas e teste prático: "Monte um endpoint com a stdlib", "O que é middleware em Go?" (func(http.Handler) http.Handler), "Como você estrutura logging?" (slog JSON + trace id no context), "Como configurar o pool de conexões e por quê?", "O que é um table-driven test?". Ter um serviço Go idiomático no portfólio (stdlib, sqlc, testcontainers) impressiona.

✏️ Exercício 7 — Ordene os middlewares

Você tem middlewares: recover, log, auth, rateLimit, trace. Em que ordem você os encadeia em volta do mux, e por quê?

Gabarito (uma boa ordem): trace(recover(log(rateLimit(auth(mux))))) — do mais externo ao mais interno: (1) trace primeiro para todo o resto ter o trace id; (2) recover logo em seguida para capturar panics de qualquer middleware interno e do handler; (3) log para registrar toda requisição com status final (inclusive as barradas depois); (4) rateLimit antes de gastar trabalho com auth; (5) auth por último antes do handler. Variações são defensáveis (alguns põem rateLimit por usuário depois do auth) — o importante é justificar.

MÓDULO 08 · AVANÇADO

Cloud-native com Go

Objetivo: empacotar e operar serviços Go como cidadãos de primeira classe da nuvem — Docker mínimo, gRPC, o ecossistema Kubernetes, e observabilidade.

8.1 Docker: imagem minúscula

# build multi-stage: compila num estágio, copia só o binário para outro
FROM golang:1.23 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app ./cmd/api

# imagem final: distroless (só libc + certs) ou scratch (nada)
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /app /app
USER nonroot
ENTRYPOINT ["/app"]

8.2 gRPC em Go

Go é cidadão de primeira classe do gRPC. Fluxo: escrever o .protoprotoc (ou buf) gera o código Go → implementar a interface do servidor → registrar no grpc.Server. Interceptors são o "middleware" do gRPC. Ver apostila de API Design para o design do contrato; aqui, a mecânica:

lis, _ := net.Listen("tcp", ":50051")
s := grpc.NewServer(
    grpc.ChainUnaryInterceptor(logInterceptor, recoverInterceptor),
)
pb.RegisterOrderServiceServer(s, &orderServer{svc: svc})
s.Serve(lis)

Para expor a mesma API em REST sem reescrever: grpc-gateway (gera um proxy HTTP/JSON a partir do proto) ou Connect (protocolo que fala gRPC, gRPC-Web e HTTP/JSON no mesmo endpoint).

8.3 O ecossistema Kubernetes

8.4 Observabilidade

8.5 12-factor em Go

💼 Mercado de trabalho

Para vagas de plataforma/SRE: "Como você faz uma imagem Docker mínima de um serviço Go?" (multi-stage + CGO_ENABLED=0 + distroless/scratch + nonroot), "O que é um operator do Kubernetes e por que se escreve em Go?" (controller-runtime, loop de reconciliação, client-go), "Como instrumentar um serviço Go?" (prometheus/client_golang + OTel + slog), "liveness vs readiness".

✏️ Exercício 8 — Diagnóstico de imagem

Um colega reclama que a imagem do serviço Go tem 900 MB e o scanner de segurança acusa dezenas de CVEs. O Dockerfile é FROM golang:1.23, COPY . ., RUN go build -o app, CMD ["./app"]. O que muda?

Gabarito: A imagem inclui o toolchain Go inteiro, o código-fonte, e um Debian completo com shell e utilitários (daí as CVEs). Solução: build multi-stage — estágio golang:1.23 AS build compila com CGO_ENABLED=0 -ldflags="-s -w"; estágio final FROM gcr.io/distroless/static-debian12:nonroot (ou scratch + copiar certs CA) recebe só o binário. Resultado: imagem de ~10–20 MB, sem shell, sem toolchain, superfície de CVE drasticamente menor, rodando como nonroot. Adicionar .dockerignore e cache de go mod download antes do COPY . ..

MÓDULO 09 · MUITO AVANÇADO

Runtime, performance e produção

Objetivo: entender o scheduler e o GC de Go, medir com pprof e benchmarks, usar generics com critério, e estruturar um repositório de produção.

9.1 O runtime: scheduler G-M-P

9.2 Garbage collector e escape analysis

9.3 Profiling e benchmarks

// benchmark
func BenchmarkParse(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = Parse(entrada)
    }
}
// go test -bench=. -benchmem   → ns/op, B/op, allocs/op

// pprof em produção: expor via net/http/pprof (rota protegida)
import _ "net/http/pprof"
// go tool pprof http://host/debug/pprof/profile?seconds=30   (CPU)
// go tool pprof http://host/debug/pprof/heap                 (memória)
// go tool pprof http://host/debug/pprof/goroutine            (leaks!)

Fluxo: meça antes de otimizar. Benchmark do trecho suspeito com -benchmem; pprof de CPU e heap em carga real; o flame graph mostra onde o tempo/memória vai. Otimização sem profiling é chute.

9.4 Generics (1.18+) — com critério

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

9.5 Estrutura de repositório e supply chain

💼 Mercado de trabalho

Perguntas sénior: "Como o scheduler de Go funciona?" (G-M-P, work stealing, preempção, netpoller), "O GC de Go otimiza para quê?" (latência; GOGC, GOMEMLIMIT), "O que é escape analysis e como reduzir alocação?", "Como você investiga um serviço Go lento / com memória crescente?" (pprof CPU/heap/goroutine, benchmark com -benchmem), "Generics: quando usar?".

✏️ Exercício 9 — Memória crescendo

Um serviço Go em produção tem RSS que sobe continuamente e nunca cai, mesmo com tráfego constante. Como você investiga e quais as duas causas mais prováveis?

Gabarito: Investigar: go tool pprof /debug/pprof/heap (com -inuse_space e -alloc_space) para ver o que retém memória; /debug/pprof/goroutine para contar goroutines ao longo do tempo. Causas prováveis: (1) goroutine leak — a contagem de goroutines cresce sem parar; alguma goroutine bloqueia para sempre segurando referências (channel sem saída, sem ctx.Done()). (2) Estrutura que só cresce — um map/slice global usado como cache sem expiração/limite, ou um sync.Pool mal usado, ou um slice grande fatiado que mantém o array subjacente vivo. Menos comum: GOMEMLIMIT alto demais fazendo o GC adiar; RSS que não devolve ao SO (comportamento normal do runtime até certo ponto — olhar heap "in use", não só RSS).

MÓDULO 10 · CARREIRA

Mercado de trabalho: roadmap, entrevistas e portfólio

Objetivo: converter o conteúdo dos módulos anteriores em aprovação — perfis, roadmap, banco de perguntas com respostas e projetos que geram entrevista.

10.1 Onde Go abre portas

PerfilFoco
Backend Engineer (Go)APIs e serviços de alta vazão; fintech, adtech, streaming, marketplaces
Platform / Infra EngineerFerramentas internas, operators, controllers, CLIs; extensões do Kubernetes
SRE / DevOpsAutomação, exporters, agentes; ler e corrigir o ecossistema CNCF (que é Go)
Cloud / Distributed SystemsBancos, filas, proxies, sistemas de coordenação

10.2 Roadmap de estudo (7–9 semanas)

SemanasFocoPrática
1Sintaxe, tipos, slices/maps, ponteiros, módulos (Módulos 1–2)"A Tour of Go" + uma CLI pequena (parser de argumentos, faz algo útil)
2Interfaces, composição, erros como valores (Módulos 3–4)Refatorar a CLI com interfaces pequenas e um modelo de erros claro
3–4Concorrência: goroutines, channels, select, context, errgroup, leaks (Módulos 5–6)Um baixador concorrente com limite de concorrência, timeout e graceful shutdown
5Serviço HTTP com a stdlib: mux 1.22, middleware, slog, pgx/sqlc, testes (Módulo 7)API REST com Postgres, table-driven tests, httptest, testcontainers, CI com -race
6Cloud-native: Docker distroless, gRPC, OTel, health checks, 12-factor (Módulo 8)Empacotar o serviço; adicionar gRPC e métricas Prometheus + tracing
7Runtime, pprof, benchmarks, generics, layout de repo, govulncheck (Módulo 9)Profilar um trecho, otimizar alocação, medir o "antes/depois"
8–9Consolidação, ler código de projeto grande (ex.: um controller), portfólioContribuição pequena num projeto Go OSS + estudo de caso publicado

10.3 Banco de perguntas de entrevista (com a resposta que aprova)

Júnior/pleno — "O que é uma goroutine? Como difere de um thread?"

Uma função executando concorrentemente, gerida pelo runtime de Go (não pelo SO). Começa com ~2 KB de stack (cresce sob demanda), então dá para ter centenas de milhares; o runtime as multiplexa sobre poucos threads do SO (modelo G-M-P). Threads do SO custam ~1 MB e trocar de contexto é caro. A comunicação idiomática entre goroutines é por channels, não por memória compartilhada.

Pleno — "Channel com buffer vs sem buffer"

Sem buffer: send bloqueia até que alguém faça receive (rendez-vous, sincroniza as duas goroutines). Com buffer N: send só bloqueia quando o buffer está cheio; desacopla produtor e consumidor até N itens. Use sem buffer para sincronização/handoff; com buffer para absorver rajadas ou quando você sabe o limite. Buffer não "resolve" backpressure — só adia.

Pleno — "Para que serve context.Context?"

Propagar cancelamento, deadline/timeout e valores de escopo de requisição pela árvore de chamadas. Convenção: primeiro parâmetro de toda função com I/O. Quando o cliente HTTP desiste, r.Context() é cancelado e o trabalho a jusante (queries, chamadas de rede) é abortado — não se desperdiça CPU. Sempre chamar o cancel() retornado (defer). Não usar WithValue para parâmetros normais.

Pleno/sénior — "O que é um goroutine leak e como evitar?"

Uma goroutine que bloqueia para sempre (channel que ninguém fecha, send sem receptor, select sem ctx.Done()) — nunca é coletada, retém memória e recursos. Evitar: toda goroutine precisa de um caminho de saída (fechar o channel de entrada, ou case <-ctx.Done()); quem escreve fecha o channel; testar com goleak; monitorar a contagem de goroutines no pprof em produção.

Sénior — "Como o GC de Go funciona e o que você ajusta?"

Concorrente, mark-and-sweep, tricolor, otimizado para latência (pausas sub-ms). GOGC controla a frequência (trade-off memória × CPU de GC); GOMEMLIMIT (1.19+) põe um teto suave — importante em contêiner para o GC ser mais agressivo antes do OOM kill. Para reduzir pressão de GC: escape analysis (-gcflags=-m), pré-alocar slices, sync.Pool para buffers, evitar alocações no caminho quente — sempre guiado por pprof/benchmark.

Sénior — "Quando Mutex e quando channel?"

Mutex: proteger uma seção crítica curta ou uma estrutura compartilhada (um cache, um contador) — mais simples e rápido para "só me deixe atualizar isto com segurança". Channel: transferir propriedade de um dado, coordenar etapas de um pipeline, distribuir trabalho, sinalizar eventos/cancelamento. O lema é "compartilhe memória comunicando", mas Mutex não é derrota — o próprio time de Go usa os dois. atomic para contadores/flags simples.

Armadilha — "Go é lento porque tem GC / não tem generics de verdade"

Go troca alguns picos de performance e expressividade por tempo de compilação, simplicidade operacional e concorrência barata — e o GC de baixa latência raramente é o gargalo de um serviço de rede (o gargalo é I/O). Generics existem desde 1.18 e cobrem bem estruturas de dados; a filosofia é usá-los com parcimônia. Para o domínio de Go (serviços cloud-native), esses trade-offs são features, não defeitos.

10.4 Projetos de portfólio que geram entrevista

  1. Serviço idiomático completo (âncora): API REST + gRPC, Postgres via sqlc, slog, middleware, graceful shutdown, table-driven tests + testcontainers, CI com -race/govulncheck, Dockerfile distroless, métricas Prometheus + tracing OTel. README explicando cada escolha.
  2. Ferramenta de concorrência: um crawler/baixador com worker pool configurável, errgroup, limite, timeout, retry, e um teste que prova ausência de leak (goleak).
  3. Operator/controller de Kubernetes: um CRD simples (ex.: provisiona um recurso) com Kubebuilder — mostra domínio do padrão de reconciliação.
  4. CLI multiplataforma: com cobra, cross-compile no CI, releases com goreleaser.
  5. Estudo de performance: um trecho profilado e otimizado, com flame graphs e números de -benchmem antes/depois.

10.5 Fontes para continuar

🏁 Síntese final da apostila

Quatro ideias sustentam Go: (1) simplicidade é uma feature — poucos conceitos, uma forma de fazer cada coisa, código que qualquer um lê; (2) erros são valores — o caminho de falha é explícito e local, não escondido em exceções; (3) concorrência por comunicação — goroutines baratas + channels + context, com sync quando comunicar não basta, e sempre um caminho de saída; (4) opere fácil — binário único, imagem mínima, 12-factor, pprof na caixa. É por isso que a nuvem moderna é escrita em Go.