Tutoriais Go em Portugues

Tutoriais praticos com codigo completo para aprender Go construindo projetos reais.

Web e APIs

Banco de Dados

DevOps e Infra

Arquitetura

Mensageria e Streaming

Observabilidade

Testes e CI/CD

Seguranca

  • Security — Boas praticas de seguranca
  • OPA — Policy as Code

Mais


Veja tambem: Aprenda Go do zero | Vagas Go | Empresas que usam Go

Go e HashiCorp Vault: Gerenciamento de Segredos

Implemente gerenciamento seguro de segredos com HashiCorp Vault em aplicações Go. Aprenda autenticação, políticas, secrets dinâmicos e integração completa.

Go e HashiCorp Vault: Gerenciamento de Segredos

HashiCorp Vault é a solução líder para gerenciamento de segredos em ambientes cloud-native. Sua integração com Go permite proteger credenciais, APIs keys, certificados e dados sensíveis de forma segura e auditável.

Neste guia completo, você aprenderá a integrar Vault com aplicações Go, implementar padrões de segurança, usar secrets dinâmicos e automatizar rotação de credenciais.

Índice

  1. Fundamentos do Vault
  2. Instalação e Configuração
  3. Cliente Go do Vault
  4. Autenticação
  5. Leitura e Escrita de Secrets
  6. Secrets Dinâmicos
  7. Rotação Automática
  8. Padrões de Produção

Fundamentos do Vault

Arquitetura

┌─────────────────────────────────────────────────────────────┐
│                      Aplicação Go                          │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              Vault Client Go                        │   │
│  │  • Autenticação                                     │   │
│  │  • Cache de tokens                                  │   │
│  │  • Renovação automática                             │   │
│  └─────────────────────────┬───────────────────────────┘   │
└────────────────────────────┼────────────────────────────────┘
                             │ HTTPS/TLS
┌─────────────────────────────────────────────────────────────┐
│                    Vault Server                            │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │   Autenticação│  │   Policies   │  │  Secret      │      │
│  │   Methods     │  │   (ACL)      │  │  Engines     │      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
│                                                             │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │   Transit    │  │   Database   │  │   PKI        │      │
│  │   (Encryption)│  │   (Dynamic)  │  │  (Certificates)     │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
└─────────────────────────────────────────────────────────────┘

Key Features

1. Secrets Estáticos Key-Value store para credenciais, API keys, certificados.

Ler mais →

Go e Kafka: Processamento de Streaming

Aprenda a usar Apache Kafka com Go para streaming de eventos. Tutorial completo com exemplos práticos de producers, consumers, topics, partitions e padrões de processamento.

Go e Kafka: Processamento de Streaming

Apache Kafka é a plataforma de streaming de eventos mais popular do mundo, usada por milhares de empresas para processar trilhões de eventos diariamente. Com Go, você pode construir aplicações de streaming de alta performance e baixa latência.

Neste guia completo, você vai aprender a usar Kafka com Go desde o básico até padrões avançados de processamento.

O Que Você Vai Aprender

  • Fundamentos do Apache Kafka
  • Configuração do cliente Kafka em Go
  • Implementação de producers e consumers
  • Gerenciamento de topics e partitions
  • Consumer groups para escalabilidade
  • Tratamento de erros e retries
  • Casos de uso do mundo real

Por Que Kafka?

O Problema: Processamento em Lote vs Streaming

Tradicionalmente, sistemas processavam dados em lotes (batch):

Ler mais →

Go e AWS: Deploy de Aplicações na Nuvem

Guia completo de deployment de aplicações Go na AWS: EC2, Lambda, S3, RDS e melhores práticas de infraestrutura em nuvem

Go e AWS: Deploy de Aplicações na Nuvem

A AWS (Amazon Web Services) é a maior plataforma de cloud computing do mundo, oferecendo mais de 200 serviços. Quando combinada com Go, uma linguagem compilada com excelente performance e baixo consumo de recursos, você obtém uma stack poderosa e econômica para aplicações em produção.

Neste guia completo, você aprenderá desde os conceitos básicos até técnicas avançadas para deploy de aplicações Go na AWS, incluindo EC2, Lambda, S3, RDS e muito mais.

Ler mais →

Go e Dapr: Distributed Application Runtime

Simplifique microsserviços com Dapr em Go. Aprenda service invocation, state management, pub/sub e bindings com exemplos práticos.

Go e Dapr: Distributed Application Runtime

Dapr (Distributed Application Runtime) é uma plataforma portátil e event-driven que facilita a construção de aplicações distribuídas e microsserviços. Projetado pela Microsoft, é usado por empresas como Alibaba, Tencent e Ignition Group.

Neste guia, você aprenderá a usar Dapr com Go para resolver desafios comuns de microsserviços de forma simples e portátil.

Índice

  1. O que é Dapr?
  2. Arquitetura e Sidecar
  3. Configuração
  4. Service Invocation
  5. State Management
  6. Pub/Sub Messaging
  7. Bindings
  8. Observability

O que é Dapr?

Building Blocks

Dapr fornece 8 building blocks que resolvem problemas comuns:

Ler mais →

Go e MinIO: Object Storage com S3-Compatible API

Aprenda a usar MinIO em Go para armazenamento de objetos. Guia completo de buckets, uploads, presigned URLs e padrões de produção com SDK AWS S3.

Go e MinIO: Object Storage com S3-Compatible API

MinIO é o servidor de object storage de código aberto mais popular, compatível com a API S3 da AWS. Escrito em Go, oferece alta performance e é ideal para aplicações que precisam de storage escalável sem depender de serviços cloud proprietários.

Neste guia, você aprenderá a integrar MinIO com aplicações Go, desde operações básicas até padrões avançados de produção.

Índice

  1. Por que MinIO?
  2. Configuração do Ambiente
  3. SDK AWS S3 para Go
  4. Operações Básicas
  5. Uploads Avançados
  6. Presigned URLs
  7. Streaming e Large Objects
  8. Padrões de Produção

Por que MinIO?

Vantagens

1. Compatibilidade S3 100% compatível com API S3 da AWS. Mesmo código funciona com MinIO, AWS S3, ou qualquer outro storage S3-compatible.

Ler mais →

Go e Open Policy Agent (OPA): Policy as Code

Implemente authorization e policy management com OPA em Go. Aprenda Rego language, Go SDK e padrões de Policy as Code para microsserviços.

Go e Open Policy Agent (OPA): Policy as Code

Open Policy Agent (OPA) é um motor de políticas open-source que permite unificar policy management em toda a stack. Desde API authorization até Kubernetes admission control, OPA proporciona uma linguagem declarativa (Rego) para definir políticas.

Neste guia, você aprenderá a integrar OPA com aplicações Go para implementar authorization flexível e auditável.

Índice

  1. O que é OPA?
  2. Linguagem Rego
  3. Go SDK
  4. API Authorization
  5. Policy Testing
  6. Integração com Middleware
  7. Bundles e Atualização Dinâmica

O que é OPA?

Casos de Uso

  • API Authorization: Permitir/negar acesso a endpoints
  • Data Filtering: Filtrar dados baseado em permissões
  • Kubernetes: Admission control policies
  • Terraform: Policy enforcement para infraestrutura
  • Service Mesh: Envoy/Istio authorization

Arquitetura

┌─────────────────────────────────────────────────────────────┐
│                   Aplicação Go                             │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  HTTP Request + JWT                                 │   │
│  │  {user: "alice", action: "read", resource: "document"}│   │
│  └──────────────────────┬──────────────────────────────┘   │
│                         │ Query                             │
│                         ▼                                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              OPA (via Go SDK)                       │   │
│  │  ┌──────────────┐      ┌───────────────────────┐    │   │
│  │  │  Rego Policy │      │       Data            │    │   │
│  │  │              │      │  {users, roles, etc}  │    │   │
│  │  │ allow { ... }│      │                       │    │   │
│  │  └──────────────┘      └───────────────────────┘    │   │
│  └──────────────────────┬──────────────────────────────┘   │
│                         │ Result: true/false                │
│                         ▼                                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              Response (Allow/Deny)                  │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

Linguagem Rego

Fundamentos

# policy/authz.rego
package authz

# Importa dados
default allow := false

# Regra básica: admin pode tudo
allow {
    input.user.role == "admin"
}

# Regra: owner pode acessar seus próprios recursos
allow {
    input.user.id == input.resource.owner_id
}

# Regra: usuários com permissão específica
allow {
    permission := data.permissions[input.user.id][_]
    permission.action == input.action
    permission.resource == input.resource.type
}

# Helper: check role
check_role(role) {
    input.user.role == role
}

Políticas de API

# policy/api.rego
package api

import future.keywords.if
import future.keywords.in

# Default deny
default allow := false

# GET /users - apenas admins ou usuários autenticados
allow if {
    input.method == "GET"
    input.path == ["users"]
    input.user.authenticated
}

# GET /users/:id - próprio usuário ou admin
allow if {
    input.method == "GET"
    input.path == ["users", user_id]
    input.user.id == user_id
}

allow if {
    input.method == "GET"
    input.path == ["users", _]
    input.user.role == "admin"
}

# POST /orders - qualquer usuário autenticado
allow if {
    input.method == "POST"
    input.path == ["orders"]
    input.user.authenticated
}

# Rate limiting check
rate_limit_ok if {
    not data.rate_limits[input.user.id].count > 100
}

# Composição de políticas
allow if {
    check_permission
    rate_limit_ok
    business_hours
}

check_permission if {
    some permission in data.permissions[input.user.role]
    permission.resource == input.resource
    permission.action == input.action
}

business_hours if {
    to_number(input.time) >= 9
    to_number(input.time) <= 18
}

Go SDK

Instalação

go get github.com/open-policy-agent/opa/rego
go get github.com/open-policy-agent/opa/storage/inmem

Uso Básico

package opa

import (
    "context"
    "embed"
    "fmt"

    "github.com/open-policy-agent/opa/rego"
    "github.com/open-policy-agent/opa/storage/inmem"
)

//go:embed policy/*.rego
var policyFS embed.FS

type Authorizer struct {
    query rego.PreparedEvalQuery
}

func NewAuthorizer() (*Authorizer, error) {
    // Carrega políticas do embed.FS
    policy, err := policyFS.ReadFile("policy/authz.rego")
    if err != nil {
        return nil, err
    }

    // Compila e prepara query
    r := rego.New(
        rego.Query("data.authz.allow"),
        rego.Module("authz.rego", string(policy)),
    )

    query, err := r.PrepareForEval(context.Background())
    if err != nil {
        return nil, fmt.Errorf("falha ao compilar políticas: %w", err)
    }

    return &Authorizer{query: query}, nil
}

func (a *Authorizer) Authorize(ctx context.Context, input map[string]interface{}) (bool, error) {
    results, err := a.query.Eval(ctx, rego.EvalInput(input))
    if err != nil {
        return false, err
    }

    if len(results) == 0 {
        return false, nil
    }

    // Extrai resultado booleano
    allowed, ok := results[0].Expressions[0].Value.(bool)
    if !ok {
        return false, fmt.Errorf("resultado inválido")
    }

    return allowed, nil
}

Com Dados Dinâmicos

package opa

import (
    "context"
    "encoding/json"

    "github.com/open-policy-agent/opa/ast"
    "github.com/open-policy-agent/opa/rego"
    "github.com/open-policy-agent/opa/storage"
    "github.com/open-policy-agent/opa/storage/inmem"
)

type PolicyEngine struct {
    store storage.Store
}

func NewPolicyEngine() *PolicyEngine {
    return &PolicyEngine{
        store: inmem.NewFromObject(map[string]interface{}{}),
    }
}

func (pe *PolicyEngine) LoadData(ctx context.Context, path string, data interface{}) error {
    jsonData, err := json.Marshal(data)
    if err != nil {
        return err
    }

    var jsonValue interface{}
    if err := json.Unmarshal(jsonData, &jsonValue); err != nil {
        return err
    }

    txn := storage.NewTransactionOrDie(ctx, pe.store, storage.WriteParams)
    defer pe.store.Abort(ctx, txn)

    if err := pe.store.Write(ctx, txn, storage.AddOp, storage.MustParsePath(path), jsonValue); err != nil {
        return err
    }

    return pe.store.Commit(ctx, txn)
}

func (pe *PolicyEngine) Evaluate(ctx context.Context, query string, input interface{}) (rego.ResultSet, error) {
    r := rego.New(
        rego.Query(query),
        rego.Store(pe.store),
        rego.Input(input),
    )

    return r.Eval(ctx)
}

API Authorization

Middleware de Autorização

package middleware

import (
    "context"
    "encoding/json"
    "net/http"
    "strings"

    "myapp/opa"
)

type contextKey string

const userContextKey contextKey = "user"

type AuthMiddleware struct {
    authorizer *opa.Authorizer
}

func NewAuthMiddleware(authorizer *opa.Authorizer) *AuthMiddleware {
    return &AuthMiddleware{authorizer: authorizer}
}

func (m *AuthMiddleware) Authorize(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Extrai usuário do context (setado por auth middleware anterior)
        user, ok := r.Context().Value(userContextKey).(User)
        if !ok {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }

        // Prepara input para OPA
        input := map[string]interface{}{
            "method":   r.Method,
            "path":     splitPath(r.URL.Path),
            "user":     user,
            "headers":  r.Header,
            "time":     getCurrentHour(),
        }

        // Avalia política
        allowed, err := m.authorizer.Authorize(r.Context(), input)
        if err != nil {
            http.Error(w, "Internal Server Error", http.StatusInternalServerError)
            return
        }

        if !allowed {
            http.Error(w, "Forbidden", http.StatusForbidden)
            return
        }

        next.ServeHTTP(w, r)
    })
}

func splitPath(path string) []string {
    return strings.Split(strings.Trim(path, "/"), "/")
}

Uso em Handlers

package main

import (
    "net/http"

    "myapp/middleware"
    "myapp/opa"
)

func main() {
    authorizer, _ := opa.NewAuthorizer()
    authMiddleware := middleware.NewAuthMiddleware(authorizer)

    mux := http.NewServeMux()

    // Protege rotas
    mux.Handle("/api/", authMiddleware.Authorize(apiHandler()))
    mux.Handle("/admin/", authMiddleware.Authorize(adminHandler()))

    http.ListenAndServe(":8080", mux)
}

Policy Testing

Testes em Rego

# policy/api_test.rego
package api

test_allow_get_users_if_admin {
    allow with input as {
        "method": "GET",
        "path": ["users"],
        "user": {"role": "admin", "authenticated": true}
    }
}

test_deny_get_users_if_not_authenticated {
    not allow with input as {
        "method": "GET",
        "path": ["users"],
        "user": {"authenticated": false}
    }
}

test_allow_get_own_user {
    allow with input as {
        "method": "GET",
        "path": ["users", "user-123"],
        "user": {"id": "user-123", "authenticated": true}
    }
}

test_deny_access_outside_business_hours {
    not allow with input as {
        "method": "POST",
        "path": ["orders"],
        "user": {"authenticated": true},
        "time": 22
    }
}

Testes em Go

package opa_test

import (
    "context"
    "testing"

    "myapp/opa"
)

func TestAuthorizer(t *testing.T) {
    auth, err := opa.NewAuthorizer()
    if err != nil {
        t.Fatal(err)
    }

    tests := []struct {
        name     string
        input    map[string]interface{}
        expected bool
    }{
        {
            name: "admin_can_access_anything",
            input: map[string]interface{}{
                "method": "DELETE",
                "path":   []string{"users", "123"},
                "user":   map[string]interface{}{"role": "admin"},
            },
            expected: true,
        },
        {
            name: "user_can_access_own_resource",
            input: map[string]interface{}{
                "method": "GET",
                "path":   []string{"users", "user-123"},
                "user": map[string]interface{}{
                    "id": "user-123",
                },
                "resource": map[string]interface{}{
                    "owner_id": "user-123",
                },
            },
            expected: true,
        },
        {
            name: "user_cannot_access_others_resource",
            input: map[string]interface{}{
                "method": "GET",
                "path":   []string{"users", "user-456"},
                "user": map[string]interface{}{
                    "id": "user-123",
                },
                "resource": map[string]interface{}{
                    "owner_id": "user-456",
                },
            },
            expected: false,
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result, err := auth.Authorize(context.Background(), tt.input)
            if err != nil {
                t.Fatal(err)
            }
            if result != tt.expected {
                t.Errorf("expected %v, got %v", tt.expected, result)
            }
        })
    }
}

Bundles e Atualização Dinâmica

OPA Server

package main

import (
    "context"
    "log"
    "os"
    "os/signal"
    "syscall"

    "github.com/open-policy-agent/opa/sdk"
)

func main() {
    ctx := context.Background()

    // Configura OPA com bundle remoto
    config := []byte(`
{
    "services": {
        "bundle_service": {
            "url": "https://bundles.example.com",
            "credentials": {
                "bearer": {
                    "token": "${TOKEN}"
                }
            }
        }
    },
    "bundles": {
        "authz": {
            "service": "bundle_service",
            "resource": "bundles/authz.tar.gz",
            "polling": {
                "min_delay_seconds": 60,
                "max_delay_seconds": 120
            }
        }
    }
}
`)

    opa, err := sdk.New(ctx, sdk.Options{
        ID:     "opa-instance",
        Config: bytes.NewReader(config),
    })
    if err != nil {
        log.Fatal(err)
    }
    defer opa.Stop(ctx)

    // Usa OPA para queries
    if result, err := opa.Decision(ctx, sdk.DecisionOptions{
        Path:  "authz/allow",
        Input: map[string]interface{}{"user": "alice"},
    }); err != nil {
        log.Fatal(err)
    } else {
        log.Printf("Decision: %v", result.Result)
    }

    // Aguarda sinal de término
    sig := make(chan os.Signal, 1)
    signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
    <-sig
}

Hot Reload

package opa

import (
    "context"
    "sync"
    "time"

    "github.com/fsnotify/fsnotify"
    "github.com/open-policy-agent/opa/rego"
)

type HotReloadAuthorizer struct {
    mu        sync.RWMutex
    query     rego.PreparedEvalQuery
    policyDir string
}

func NewHotReloadAuthorizer(policyDir string) (*HotReloadAuthorizer, error) {
    ha := &HotReloadAuthorizer{policyDir: policyDir}
    
    if err := ha.reload(); err != nil {
        return nil, err
    }

    // Inicia watcher
    go ha.watch()

    return ha, nil
}

func (ha *HotReloadAuthorizer) reload() error {
    // Carrega e compila políticas
    modules := make(map[string]string)
    // ... carrega arquivos .rego

    r := rego.New(
        rego.Query("data.authz.allow"),
        rego.LoadedModules(modules),
    )

    query, err := r.PrepareForEval(context.Background())
    if err != nil {
        return err
    }

    ha.mu.Lock()
    ha.query = query
    ha.mu.Unlock()

    return nil
}

func (ha *HotReloadAuthorizer) watch() {
    watcher, _ := fsnotify.NewWatcher()
    defer watcher.Close()

    watcher.Add(ha.policyDir)

    for {
        select {
        case event, ok := <-watcher.Events:
            if !ok {
                return
            }
            if event.Op&fsnotify.Write == fsnotify.Write {
                // Recarrega após delay (debounce)
                time.Sleep(100 * time.Millisecond)
                ha.reload()
            }
        }
    }
}

func (ha *HotReloadAuthorizer) Authorize(ctx context.Context, input map[string]interface{}) (bool, error) {
    ha.mu.RLock()
    query := ha.query
    ha.mu.RUnlock()

    results, err := query.Eval(ctx, rego.EvalInput(input))
    // ... processa resultado
}

Conclusão

Neste guia, você aprendeu:

Ler mais →

Go e Prometheus: Métricas e Alertas em Produção

Implemente métricas e alertas com Prometheus em aplicações Go. Guia completo de instrumentação, Grafana dashboards e alerting com exemplos práticos.

Go e Prometheus: Métricas e Alertas em Produção

Prometheus tornou-se o padrão de facto para monitoramento de aplicações cloud-native. Sua combinação com Go é natural - ambos foram criados pela SoundCloud e compartilham filosofias de simplicidade e performance.

Neste guia, você aprenderá a instrumentar aplicações Go com métricas Prometheus, criar dashboards no Grafana e configurar alertas inteligentes.

Índice

  1. Fundamentos do Prometheus
  2. Bibliotecas Go para Prometheus
  3. Tipos de Métricas
  4. Instrumentando Aplicações
  5. Métricas de Runtime Go
  6. Tracing e Context
  7. Dashboards no Grafana
  8. Alertas com Alertmanager
  9. Padrões de Produção

Fundamentos do Prometheus

Arquitetura

┌─────────────────────────────────────────────────────────────┐
│                      Aplicações Go                          │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │   App 1     │  │   App 2     │  │   App 3     │         │
│  │ :8080/metrics│  │ :8081/metrics│  │ :8082/metrics│        │
│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘         │
└─────────┼────────────────┼────────────────┼─────────────────┘
          │                │                │
          ▼                ▼                ▼
┌─────────────────────────────────────────────────────────────┐
│                     Prometheus Server                       │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │  Retriever   │  │    TSDB      │  │ Query Engine │      │
│  │  (Pull)      │  │  (Storage)   │  │  (PromQL)    │      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
└──────────┬──────────────────────────────────────────────────┘
           │ HTTP
┌─────────────────────────────────────────────────────────────┐
│                       Grafana                               │
│              (Visualização e Dashboards)                   │
└─────────────────────────────────────────────────────────────┘
           │ Webhook
┌─────────────────────────────────────────────────────────────┐
│                     Alertmanager                            │
│              (Roteamento e Notificações)                    │
└─────────────────────────────────────────────────────────────┘

Modelo de Dados

Prometheus usa um modelo de dados dimensional:

Ler mais →

Go e Temporal: Workflows Resilientes e Duráveis

Construa workflows resilientes com Temporal em Go. Aprenda about retries, sagas, signals e padrões de orquestração de processos distribuídos.

Go e Temporal: Workflows Resilientes e Duráveis

Temporal é uma plataforma de orquestração de workflows que permite criar processos de negócio resilientes, duráveis e escaláveis. Desenvolvido pelos criadores do Cadence (Uber), é amplamente adotado por empresas como Netflix, Stripe e Shopify.

Neste guia, você aprenderá a construir workflows complexos em Go com garantias de execução, retries automáticos e compensação (Saga pattern).

Índice

  1. Conceitos Fundamentais
  2. Configuração do Ambiente
  3. Primeiro Workflow
  4. Activities e Retries
  5. Saga Pattern
  6. Signals e Queries
  7. Timers e Cron Jobs
  8. Padrões Avançados

Conceitos Fundamentais

Arquitetura Temporal

┌─────────────────────────────────────────────────────────────┐
│                   Temporal Server                          │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │  Frontend    │  │   History    │  │   Matching   │      │
│  │   (gRPC)     │  │   (Events)   │  │   (Tasks)    │      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
│                                                             │
│  Persistence: PostgreSQL / MySQL / Cassandra / Elasticsearch│
└─────────────────────────────────────────────────────────────┘
           │ gRPC
┌─────────────────────────────────────────────────────────────┐
│                   Workers (Go)                             │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │   Workflow   │  │   Activity   │  │   Activity   │      │
│  │   Engine     │  │   Executor   │  │   Executor   │      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
│                                                             │
│  Workflows são determinísticos (replayable)                │
│  Activities contêm side-effects (I/O, APIs)                │
└─────────────────────────────────────────────────────────────┘

Conceitos Chave

Workflows: Funções Go que definem a lógica de negócio. São determinísticas e podem ser “replayed” indefinidamente.

Ler mais →

Go e Terraform: Infrastructure as Code com Go

Aprenda a criar providers Terraform personalizados em Go para automatizar sua infraestrutura. Guia completo com exemplos práticos e padrões de produção.

Go e Terraform: Infrastructure as Code com Go

O Terraform revolucionou a forma como gerenciamos infraestrutura, permitindo definir recursos como código. Embora o Terraform use sua própria linguagem de configuração (HCL), a linguagem Go é fundamental para estender suas capacidades através de providers personalizados.

Neste guia completo, você aprenderá a criar providers Terraform em Go, integrar Terraform com aplicações Go, e automatizar infraestrutura usando as melhores práticas de produção.

Índice

  1. Por que Go e Terraform?
  2. Arquitetura de Providers Terraform
  3. Criando seu Primeiro Provider
  4. Gerenciando Recursos com Go
  5. Testando Providers
  6. Integração com Aplicações Go
  7. Padrões de Produção
  8. Deploy e Distribuição

Por que Go e Terraform?

Vantagens da Combinação

1. Performance Nativa Providers Terraform são executados como binários nativos, e Go oferece excelente performance com compilação estática.

Ler mais →

Go GraphQL: Criando APIs com gqlgen

Aprenda a construir APIs GraphQL robustas em Go usando gqlgen. Tutorial completo com exemplos práticos, schema definition, resolvers, mutations e subscriptions.

Go GraphQL: Criando APIs com gqlgen

GraphQL revolucionou a forma como construímos APIs, oferecendo uma alternativa flexível ao REST tradicional. Com Go e gqlgen, você pode criar APIs GraphQL de alto desempenho com type-safety e excelente experiência de desenvolvimento.

Neste guia completo, você vai aprender a construir uma API GraphQL do zero usando gqlgen, o framework mais popular para GraphQL em Go.

O Que Você Vai Aprender

  • Diferenças entre GraphQL e REST
  • Configuração do gqlgen
  • Definição de schema GraphQL
  • Implementação de resolvers
  • Mutations e queries
  • Subscriptions em tempo real
  • Testes de APIs GraphQL

GraphQL vs REST: Por Que Migrar?

Problemas do REST

GET /users/1
GET /users/1/posts
GET /users/1/posts/123/comments

No REST, você frequentemente precisa de múltiplas requisições para obter dados relacionados:

Ler mais →

Go e gRPC: Comunicação entre Serviços Completo

Aprenda a criar APIs eficientes com gRPC em Go. Tutorial completo cobrindo Protocol Buffers, streaming, interceptores e testes.

Go e gRPC: Comunicação entre Serviços

O gRPC tornou-se o padrão de facto para comunicação entre microserviços em ambientes de alta performance. Desenvolvido pelo Google, ele oferece vantagens significativas sobre REST tradicional, especialmente quando combinado com a eficiência do Go.

Neste tutorial completo, você vai aprender a construir serviços gRPC robustos em Go, desde o básico até técnicas avançadas como streaming bidirecional e interceptores.

Por Que Usar gRPC em Go?

gRPC vs REST: Comparativo

CaracterísticaREST/HTTP JSONgRPC
FormatoJSON (texto)Protocol Buffers (binário)
Performance~15x mais lento~15x mais rápido
PayloadVerbos, repetitivoCompacto, eficiente
TipagemFraca (runtime)Forte (compile-time)
StreamingComplicado (SSE/WebSocket)Nativo e simples
Code GenerationManualAutomático
Browser SupportNativoRequer gRPC-Web

Quando escolher gRPC:

Ler mais →

Go e Elasticsearch: Busca Full-Text Avançada

Aprenda a implementar busca full-text poderosa com Go e Elasticsearch. Guia completo de indexação, queries, agregações e casos de uso reais.

Go e Elasticsearch: Busca Full-Text Avançada

Elasticsearch é o motor de busca mais popular do mundo, usado desde pequenas aplicações até sistemas como Netflix e Uber. Neste guia, você aprenderá a integrar Elasticsearch com Go para criar buscas rápidas, relevantes e escaláveis.

Índice

  1. O que é Elasticsearch?
  2. Configuração do Cliente Go
  3. Indexando Documentos
  4. Realizando Buscas
  5. Queries Avançadas
  6. Agregações
  7. Mapeamentos e Análise
  8. Exemplo Prático: Catálogo de Produtos
  9. Performance e Otimização

O que é Elasticsearch?

Elasticsearch é um motor de busca e análise distribuído baseado no Apache Lucene. Ele proporciona:

Ler mais →

Go Security: Boas Práticas de Segurança

Aprenda boas práticas de segurança para aplicações Go. Guia completo sobre validação de input, autenticação, autorização, vulnerabilidades comuns e OWASP para Go.

Go Security: Boas Práticas de Segurança

Introdução

Segurança em aplicações Go é fundamental para proteger dados sensíveis, prevenir ataques e garantir conformidade com regulamentações como LGPD e GDPR. Embora Go seja uma linguagem segura por design (memory safety, type safety), vulnerabilidades de segurança ainda ocorrem principalmente devido a práticas de codificação inadequadas.

Neste guia, você vai aprender as melhores práticas de segurança para desenvolver aplicações Go robustas e protegidas contra as ameaças mais comuns.

Ler mais →

Go e RabbitMQ: Mensageria Assíncrona Completa

Aprenda a usar RabbitMQ com Go para mensageria assíncrona. Tutorial completo com exemplos práticos de publishers, consumers, exchanges e padrões de mensageria.

Go e RabbitMQ: Mensageria Assíncrona Completa

Introdução

RabbitMQ é um dos brokers de mensagens mais populares do mundo, usado por empresas como Uber, Reddit e Stripe para processar bilhões de mensagens diariamente. Quando combinado com Go, criamos sistemas altamente performáticos, confiáveis e escaláveis.

Neste guia completo, você vai aprender desde os conceitos fundamentais até padrões avançados de mensageria com Go e RabbitMQ.

O que é RabbitMQ?

RabbitMQ é um message broker (corretor de mensagens) de código aberto que implementa o protocolo AMQP (Advanced Message Queuing Protocol). Ele atua como um intermediário entre aplicações, permitindo comunicação assíncrona e desacoplada.

Ler mais →

Go e Kubernetes: Deploy de Containers

Guia completo para fazer deploy de aplicações Go em Kubernetes. Aprenda a containerizar apps Go, criar manifests K8s e implementar health checks.

Go e Kubernetes: Deploy de Containers

Neste guia completo, você aprenderá a fazer deploy de aplicações Go em Kubernetes, desde a containerização até a configuração de health checks e boas práticas de produção. Se você está construindo microserviços ou APIs REST em Go, o Kubernetes é a plataforma ideal para orquestrar seus containers.

Por que Kubernetes para Go?

Go foi projetada para a era dos containers. Sua compilação para binário único, baixo consumo de memória e startup instantâneo tornam aplicações Go perfeitas para Kubernetes:

Ler mais →

Go CLI: Criando Ferramentas de Linha de Comando Poderosas

Aprenda a construir CLI tools profissionais em Go. Domine a biblioteca Cobra, gerenciamento de flags, subcomandos, arquivos de configuração e crie ferramentas de linha de comando com excelente UX.

Ferramentas de linha de comando (CLI) são essenciais no arsenal de qualquer desenvolvedor. Desde gerenciadores de pacotes como npm e pip até infraestrutura como kubectl e terraform, CLIs bem projetadas aumentam a produtividade exponencialmente. Go é a linguagem perfeita para criar CLIs rápidas, portáteis e eficientes.

Neste guia completo, você aprenderá a construir CLI tools profissionais usando Go, desde flags simples até ferramentas complexas com subcomandos e auto-complete.

Por Que Go para CLI Tools?

Vantagens Competitivas

┌─────────────────────────────────────────────────────────────────┐
│  CARACTERÍSTICA     │  GO    │  PYTHON  │  NODE.JS  │  RUST   │
├─────────────────────────────────────────────────────────────────┤
│  Startup            │  ~5ms  │  ~100ms  │  ~200ms   │  ~10ms  │
├─────────────────────────────────────────────────────────────────┤
│  Binário único      │  ✅    │  ❌      │  ❌       │  ✅     │
├─────────────────────────────────────────────────────────────────┤
│  Cross-compile      │  ✅    │  ❌      │  ❌       │  ✅     │
├─────────────────────────────────────────────────────────────────┤
│  Memória            │  Baixa │  Alta    │  Média    │  Baixa  │
├─────────────────────────────────────────────────────────────────┤
│  Curva de aprend.   │  Média │  Baixa   │  Baixa    │  Alta   │
└─────────────────────────────────────────────────────────────────┘

Casos de Sucesso

  • Docker: Container runtime escrito em Go
  • Kubernetes: Orquestração de containers
  • Hugo: Gerador de sites estáticos
  • Terraform: Infrastructure as Code
  • Cobra: Framework CLI usado por Kubernetes, etcd, e muitos outros

Fundamentos de CLI em Go

Flags Nativas com flag Package

Go inclui um pacote flag na biblioteca padrão para parsing de argumentos:

Ler mais →

Go e Docker: Containerização de Aplicações com Multi-Stage Builds

Aprenda a containerizar aplicações Go com Docker. Guia completo de Dockerfiles otimizados, multi-stage builds, Docker Compose para desenvolvimento, boas práticas de produção e segurança. Reduza imagens de 1GB para 15MB.

Go e Docker são uma combinação poderosa. Enquanto aplicações em outras linguagens precisam de runtimes pesados, Go compila para binários nativos que rodam em containers minimalistas de 10-20MB. Neste guia, você vai aprender a criar imagens Docker otimizadas, seguras e prontas para produção.

Por Que Docker com Go?

Vantagens da Combinação

AspectoGo + DockerOutras Linguagens
Tamanho da Imagem10-50MB100MB-1GB+
Tempo de Startup< 100ms1-30 segundos
Memory Footprint10-50MB100MB-1GB
RuntimeNenhum (binário nativo)JVM, Node, Python
Security SurfaceMínima (scratch/alpine)Grande (SO completo)

Casos de Uso

  • Microserviços: Deploy rápido e escalável
  • CI/CD: Builds reproduzíveis
  • Desenvolvimento: Ambiente consistente
  • Produção: Alta densidade de containers

Dockerfile Básico para Go

O Problema: Dockerfile Inicial

# ❌ NÃO FAÇA ISSO - Imagem gigante (~1GB)
FROM golang:1.21

WORKDIR /app
COPY . .
RUN go build -o main .

CMD ["./main"]

Problemas:

Ler mais →

Go e MongoDB: CRUD e Agregações Completas

Aprenda a integrar Go com MongoDB de forma profissional. Domine operações CRUD, aggregation pipeline, indexing, transações e padrões de produção com o driver oficial.

MongoDB é o banco de dados NoSQL mais popular do mundo, ideal para aplicações modernas que precisam de flexibilidade e escalabilidade. Combinado com Go, você tem uma stack poderosa para lidar com dados não estruturados e semi-estruturados. Neste guia, você aprenderá tudo sobre MongoDB em Go, desde operações básicas até agregações complexas.

Por Que MongoDB com Go?

Quando Escolher MongoDB

┌─────────────────────────────────────────────────────────────────┐
│  MONGODB É IDEAL PARA:                                          │
├─────────────────────────────────────────────────────────────────┤
│  ✅ Dados flexíveis e em evolução                               │
│  ✅ Alta velocidade de escrita                                  │
│  ✅ Escalabilidade horizontal (sharding)                        │
│  ✅ Documentos JSON-like nativos                                │
│  ✅ Geolocalização e queries espaciais                          │
│  ✅ Prototipagem rápida                                         │
├─────────────────────────────────────────────────────────────────┤
│  MONGODB + GO = Type-safe + Flexibilidade                       │
└─────────────────────────────────────────────────────────────────┘

MongoDB vs PostgreSQL com Go

AspectoMongoDBPostgreSQL
EsquemaFlexível (schema-less)Rígido (schema-full)
RelacionamentosEmbutidos ou referênciasJOINs nativos
EscalabilidadeHorizontal (sharding)Vertical + Replicação
Casos de usoCMS, IoT, AnalyticsFinanceiro, ERP, ACID
PerformanceExcelente para readsExcelente para ACID

Configuração do Projeto

Instalação do Driver

# Driver oficial MongoDB para Go
go get go.mongodb.org/mongo-driver/v2/mongo

# BSON utilities
go get go.mongodb.org/mongo-driver/v2/bson

# Options e configs
go get go.mongodb.org/mongo-driver/v2/mongo/options

Estrutura do Projeto

mongo-project/
├── cmd/
│   └── api/
│       └── main.go
├── internal/
│   ├── config/
│   │   └── mongodb.go
│   ├── models/
│   │   └── user.go
│   ├── repository/
│   │   └── user_repository.go
│   └── service/
│       └── user_service.go
├── go.mod
└── .env

Conexão com MongoDB

Configuração da Conexão

// internal/config/mongodb.go
package config

import (
	"context"
	"fmt"
	"log"
	"os"
	"time"

	"go.mongodb.org/mongo-driver/v2/mongo"
	"go.mongodb.org/mongo-driver/v2/mongo/options"
)

// MongoConfig armazena configurações de conexão
type MongoConfig struct {
	URI      string
	Database string
	Timeout  time.Duration
}

// NewMongoConfig cria configuração a partir de variáveis de ambiente
func NewMongoConfig() *MongoConfig {
	return &MongoConfig{
		URI:      getEnv("MONGODB_URI", "mongodb://localhost:27017"),
		Database: getEnv("MONGODB_DATABASE", "myapp"),
		Timeout:  10 * time.Second,
	}
}

// Connect estabelece conexão com MongoDB
func (cfg *MongoConfig) Connect() (*mongo.Client, error) {
	ctx, cancel := context.WithTimeout(context.Background(), cfg.Timeout)
	defer cancel()

	// Configurar opções do cliente
	clientOptions := options.Client().
		ApplyURI(cfg.URI).
		SetMaxPoolSize(100).
		SetMinPoolSize(10).
		SetMaxConnIdleTime(30 * time.Second).
		SetServerSelectionTimeout(5 * time.Second)

	// Conectar
	client, err := mongo.Connect(clientOptions)
	if err != nil {
		return nil, fmt.Errorf("falha ao conectar ao MongoDB: %w", err)
	}

	// Verificar conexão
	if err := client.Ping(ctx, nil); err != nil {
		return nil, fmt.Errorf("falha ao ping MongoDB: %w", err)
	}

	log.Println("✅ Conectado ao MongoDB")
	return client, nil
}

// GetCollection retorna uma coleção específica
func (cfg *MongoConfig) GetCollection(client *mongo.Client, name string) *mongo.Collection {
	return client.Database(cfg.Database).Collection(name)
}

// Disconnect fecha a conexão gracefully
func Disconnect(client *mongo.Client) error {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	
	if err := client.Disconnect(ctx); err != nil {
		return fmt.Errorf("falha ao desconectar: %w", err)
	}
	
	log.Println("🔌 Desconectado do MongoDB")
	return nil
}

func getEnv(key, defaultValue string) string {
	if value := os.Getenv(key); value != "" {
		return value
	}
	return defaultValue
}

String de Conexão (Connection String)

// Exemplos de URI para diferentes cenários

// Local
mongodb://localhost:27017/myapp

// Com autenticação
mongodb://user:password@localhost:27017/myapp?authSource=admin

// Replica Set
mongodb://user:password@host1:27017,host2:27017,host3:27017/myapp?replicaSet=rs0

// MongoDB Atlas (cloud)
mongodb+srv://user:password@cluster.mongodb.net/myapp?retryWrites=true&w=majority

// Com opções adicionais
mongodb://localhost:27017/myapp?maxPoolSize=100&serverSelectionTimeoutMS=5000

Modelos e Estruturas

Definindo Documentos

// internal/models/user.go
package models

import (
	"time"

	"go.mongodb.org/mongo-driver/v2/bson/primitive"
)

// User representa um usuário no MongoDB
type User struct {
	ID        primitive.ObjectID `bson:"_id,omitempty" json:"id"`
	Email     string             `bson:"email" json:"email" validate:"required,email"`
	Name      string             `bson:"name" json:"name" validate:"required,min=3,max=100"`
	Password  string             `bson:"password" json:"-"` // Não serializar
	Profile   UserProfile        `bson:"profile" json:"profile"`
	Roles     []string           `bson:"roles" json:"roles"`
	Active    bool               `bson:"active" json:"active"`
	Tags      []string           `bson:"tags,omitempty" json:"tags,omitempty"`
	Metadata  map[string]interface{} `bson:"metadata,omitempty" json:"metadata,omitempty"`
	CreatedAt time.Time          `bson:"created_at" json:"created_at"`
	UpdatedAt time.Time          `bson:"updated_at" json:"updated_at"`
}

// UserProfile dados aninhados
type UserProfile struct {
	Bio       string    `bson:"bio,omitempty" json:"bio,omitempty"`
	Avatar    string    `bson:"avatar,omitempty" json:"avatar,omitempty"`
	BirthDate time.Time `bson:"birth_date,omitempty" json:"birth_date,omitempty"`
	Location  string    `bson:"location,omitempty" json:"location,omitempty"`
	Website   string    `bson:"website,omitempty" json:"website,omitempty"`
}

// UserCreate DTO para criação
type UserCreate struct {
	Email    string      `json:"email" validate:"required,email"`
	Name     string      `json:"name" validate:"required,min=3,max=100"`
	Password string      `json:"password" validate:"required,min=8"`
	Profile  UserProfile `json:"profile,omitempty"`
	Tags     []string    `json:"tags,omitempty"`
}

// UserUpdate DTO para atualização parcial
type UserUpdate struct {
	Name     *string      `json:"name,omitempty"`
	Email    *string      `json:"email,omitempty"`
	Profile  *UserProfile `json:"profile,omitempty"`
	Roles    []string     `json:"roles,omitempty"`
	Tags     []string     `json:"tags,omitempty"`
	Active   *bool        `json:"active,omitempty"`
}

// CollectionName retorna o nome da coleção
func (User) CollectionName() string {
	return "users"
}

Trabalhando com ObjectID

// Criar novo ObjectID
id := primitive.NewObjectID()

// Converter string para ObjectID
id, err := primitive.ObjectIDFromHex("507f1f77bcf86cd799439011")
if err != nil {
    // Handle error
}

// Converter ObjectID para string
idString := id.Hex()

// Timestamp do ObjectID
timestamp := id.Timestamp()

Operações CRUD

Repository Pattern

// internal/repository/user_repository.go
package repository

import (
	"context"
	"errors"
	"fmt"
	"time"

	"go.mongodb.org/mongo-driver/v2/bson"
	"go.mongodb.org/mongo-driver/v2/bson/primitive"
	"go.mongodb.org/mongo-driver/v2/mongo"
	"go.mongodb.org/mongo-driver/v2/mongo/options"
	"project/internal/models"
)

var (
	ErrUserNotFound = errors.New("usuário não encontrado")
	ErrEmailExists  = errors.New("email já existe")
	ErrInvalidID    = errors.New("ID inválido")
)

// UserRepository interface
type UserRepository interface {
	Create(ctx context.Context, user *models.UserCreate) (*models.User, error)
	GetByID(ctx context.Context, id string) (*models.User, error)
	GetByEmail(ctx context.Context, email string) (*models.User, error)
	List(ctx context.Context, filter ListFilter) ([]*models.User, int64, error)
	Update(ctx context.Context, id string, update *models.UserUpdate) (*models.User, error)
	Delete(ctx context.Context, id string) error
	Count(ctx context.Context, filter bson.M) (int64, error)
	Exists(ctx context.Context, email string) (bool, error)
}

// userRepo implementação
type userRepo struct {
	collection *mongo.Collection
}

// NewUserRepository cria nova instância
func NewUserRepository(db *mongo.Database) UserRepository {
	return &userRepo{
		collection: db.Collection(models.User{}.CollectionName()),
	}
}

CREATE (Inserção)

// Create insere novo usuário
func (r *userRepo) Create(ctx context.Context, user *models.UserCreate) (*models.User, error) {
	now := time.Now()
	
	newUser := models.User{
		ID:        primitive.NewObjectID(),
		Email:     user.Email,
		Name:      user.Name,
		Password:  hashPassword(user.Password),
		Profile:   user.Profile,
		Roles:     []string{"user"},
		Active:    true,
		Tags:      user.Tags,
		CreatedAt: now,
		UpdatedAt: now,
	}

	// Inserir documento
	result, err := r.collection.InsertOne(ctx, newUser)
	if err != nil {
		// Verificar erro de duplicação
		if mongo.IsDuplicateKeyError(err) {
			return nil, ErrEmailExists
		}
		return nil, fmt.Errorf("falha ao criar usuário: %w", err)
	}

	// O InsertedID já é o ID do documento
	newUser.ID = result.InsertedID.(primitive.ObjectID)
	
	// Limpar senha antes de retornar
	newUser.Password = ""
	
	return &newUser, nil
}

// CreateMany insere múltiplos documentos
func (r *userRepo) CreateMany(ctx context.Context, users []*models.UserCreate) ([]string, error) {
	docs := make([]interface{}, len(users))
	now := time.Now()
	
	for i, user := range users {
		docs[i] = models.User{
			ID:        primitive.NewObjectID(),
			Email:     user.Email,
			Name:      user.Name,
			Password:  hashPassword(user.Password),
			Active:    true,
			CreatedAt: now,
			UpdatedAt: now,
		}
	}

	result, err := r.collection.InsertMany(ctx, docs)
	if err != nil {
		return nil, fmt.Errorf("falha ao criar usuários: %w", err)
	}

	// Converter IDs para strings
	ids := make([]string, len(result.InsertedIDs))
	for i, id := range result.InsertedIDs {
		ids[i] = id.(primitive.ObjectID).Hex()
	}
	
	return ids, nil
}

func hashPassword(password string) string {
	// Use bcrypt em produção!
	// import "golang.org/x/crypto/bcrypt"
	return "hashed_" + password
}

READ (Consultas)

// GetByID busca por ID
func (r *userRepo) GetByID(ctx context.Context, id string) (*models.User, error) {
	objectID, err := primitive.ObjectIDFromHex(id)
	if err != nil {
		return nil, ErrInvalidID
	}

	var user models.User
	err = r.collection.FindOne(ctx, bson.M{"_id": objectID}).Decode(&user)
	
	if err != nil {
		if errors.Is(err, mongo.ErrNoDocuments) {
			return nil, ErrUserNotFound
		}
		return nil, fmt.Errorf("falha ao buscar usuário: %w", err)
	}

	user.Password = "" // Não retornar senha
	return &user, nil
}

// GetByEmail busca por email
func (r *userRepo) GetByEmail(ctx context.Context, email string) (*models.User, error) {
	var user models.User
	err := r.collection.FindOne(ctx, bson.M{"email": email}).Decode(&user)
	
	if err != nil {
		if errors.Is(err, mongo.ErrNoDocuments) {
			return nil, ErrUserNotFound
		}
		return nil, fmt.Errorf("falha ao buscar usuário: %w", err)
	}

	return &user, nil // Retornar com senha para autenticação
}

// ListFilter filtros para listagem
type ListFilter struct {
	Active   *bool
	Roles    []string
	Tags     []string
	Search   string
	SortBy   string
	SortDesc bool
	Page     int64
	Limit    int64
}

// List retorna lista paginada
func (r *userRepo) List(ctx context.Context, filter ListFilter) ([]*models.User, int64, error) {
	// Construir filtro
	query := bson.M{}
	
	if filter.Active != nil {
		query["active"] = *filter.Active
	}
	
	if len(filter.Roles) > 0 {
		query["roles"] = bson.M{"$in": filter.Roles}
	}
	
	if len(filter.Tags) > 0 {
		query["tags"] = bson.M{"$in": filter.Tags}
	}
	
	if filter.Search != "" {
		query["$or"] = []bson.M{
			{"name": bson.M{"$regex": filter.Search, "$options": "i"}},
			{"email": bson.M{"$regex": filter.Search, "$options": "i"}},
		}
	}

	// Contar total
	total, err := r.collection.CountDocuments(ctx, query)
	if err != nil {
		return nil, 0, fmt.Errorf("falha ao contar documentos: %w", err)
	}

	// Configurar paginação
	if filter.Page < 1 {
		filter.Page = 1
	}
	if filter.Limit < 1 || filter.Limit > 100 {
		filter.Limit = 10
	}
	skip := (filter.Page - 1) * filter.Limit

	// Configurar ordenação
	sortOrder := 1 // Ascendente
	if filter.SortDesc {
		sortOrder = -1
	}
	sortField := filter.SortBy
	if sortField == "" {
		sortField = "created_at"
	}

	opts := options.Find().
		SetSkip(skip).
		SetLimit(filter.Limit).
		SetSort(bson.D{{Key: sortField, Value: sortOrder}})

	// Executar query
	cursor, err := r.collection.Find(ctx, query, opts)
	if err != nil {
		return nil, 0, fmt.Errorf("falha ao listar usuários: %w", err)
	}
	defer cursor.Close(ctx)

	var users []*models.User
	if err := cursor.All(ctx, &users); err != nil {
		return nil, 0, fmt.Errorf("falha ao decodificar resultados: %w", err)
	}

	// Limpar senhas
	for _, u := range users {
		u.Password = ""
	}

	return users, total, nil
}

// Exists verifica se email existe
func (r *userRepo) Exists(ctx context.Context, email string) (bool, error) {
	count, err := r.collection.CountDocuments(ctx, bson.M{"email": email})
	if err != nil {
		return false, err
	}
	return count > 0, nil
}

// Count retorna contagem com filtro
func (r *userRepo) Count(ctx context.Context, filter bson.M) (int64, error) {
	return r.collection.CountDocuments(ctx, filter)
}

UPDATE (Atualização)

// Update atualiza usuário parcialmente
func (r *userRepo) Update(ctx context.Context, id string, update *models.UserUpdate) (*models.User, error) {
	objectID, err := primitive.ObjectIDFromHex(id)
	if err != nil {
		return nil, ErrInvalidID
	}

	// Construir update dinâmico
	setFields := bson.M{
		"updated_at": time.Now(),
	}
	
	unsetFields := bson.M{}

	if update.Name != nil {
		setFields["name"] = *update.Name
	}
	if update.Email != nil {
		setFields["email"] = *update.Email
	}
	if update.Active != nil {
		setFields["active"] = *update.Active
	}
	if update.Profile != nil {
		setFields["profile"] = *update.Profile
	}
	if update.Roles != nil {
		setFields["roles"] = update.Roles
	}
	if update.Tags != nil {
		if len(update.Tags) == 0 {
			unsetFields["tags"] = ""
		} else {
			setFields["tags"] = update.Tags
		}
	}

	updateDoc := bson.M{"$set": setFields}
	if len(unsetFields) > 0 {
		updateDoc["$unset"] = unsetFields
	}

	opts := options.FindOneAndUpdate().
		SetReturnDocument(options.After)

	var user models.User
	err = r.collection.FindOneAndUpdate(
		ctx,
		bson.M{"_id": objectID},
		updateDoc,
		opts,
	).Decode(&user)

	if err != nil {
		if errors.Is(err, mongo.ErrNoDocuments) {
			return nil, ErrUserNotFound
		}
		if mongo.IsDuplicateKeyError(err) {
			return nil, ErrEmailExists
		}
		return nil, fmt.Errorf("falha ao atualizar usuário: %w", err)
	}

	user.Password = ""
	return &user, nil
}

// UpdateMany atualiza múltiplos documentos
func (r *userRepo) UpdateMany(ctx context.Context, filter bson.M, update bson.M) (int64, error) {
	result, err := r.collection.UpdateMany(ctx, filter, update)
	if err != nil {
		return 0, err
	}
	return result.ModifiedCount, nil
}

// Replace substitui documento completo
func (r *userRepo) Replace(ctx context.Context, id string, user *models.User) error {
	objectID, err := primitive.ObjectIDFromHex(id)
	if err != nil {
		return ErrInvalidID
	}

	user.UpdatedAt = time.Now()
	
	result, err := r.collection.ReplaceOne(ctx, bson.M{"_id": objectID}, user)
	if err != nil {
		return err
	}
	
	if result.MatchedCount == 0 {
		return ErrUserNotFound
	}
	return nil
}

DELETE (Exclusão)

// Delete remove usuário
func (r *userRepo) Delete(ctx context.Context, id string) error {
	objectID, err := primitive.ObjectIDFromHex(id)
	if err != nil {
		return ErrInvalidID
	}

	result, err := r.collection.DeleteOne(ctx, bson.M{"_id": objectID})
	if err != nil {
		return fmt.Errorf("falha ao deletar usuário: %w", err)
	}

	if result.DeletedCount == 0 {
		return ErrUserNotFound
	}

	return nil
}

// SoftDelete (delete lógico)
func (r *userRepo) SoftDelete(ctx context.Context, id string) error {
	objectID, err := primitive.ObjectIDFromHex(id)
	if err != nil {
		return ErrInvalidID
	}

	update := bson.M{
		"$set": bson.M{
			"active":     false,
			"deleted_at": time.Now(),
			"updated_at": time.Now(),
		},
	}

	result, err := r.collection.UpdateOne(ctx, bson.M{"_id": objectID}, update)
	if err != nil {
		return err
	}
	
	if result.MatchedCount == 0 {
		return ErrUserNotFound
	}
	return nil
}

// DeleteMany remove múltiplos documentos
func (r *userRepo) DeleteMany(ctx context.Context, filter bson.M) (int64, error) {
	result, err := r.collection.DeleteMany(ctx, filter)
	if err != nil {
		return 0, err
	}
	return result.DeletedCount, nil
}

Aggregation Pipeline

Pipeline Básica

// AggregateUsersStats estatísticas de usuários
func (r *userRepo) AggregateUsersStats(ctx context.Context) (*UserStats, error) {
	pipeline := mongo.Pipeline{
		// Agrupar por status ativo
		{{Key: "$group", Value: bson.M{
			"_id": "$active",
			"count": bson.M{"$sum": 1},
			"avgAge": bson.M{"$avg": "$profile.age"},
		}}},
		// Ordenar
		{{Key: "$sort", Value: bson.M{"_id": 1}}},
	}

	cursor, err := r.collection.Aggregate(ctx, pipeline)
	if err != nil {
		return nil, err
	}
	defer cursor.Close(ctx)

	var results []bson.M
	if err := cursor.All(ctx, &results); err != nil {
		return nil, err
	}

	// Processar resultados
	stats := &UserStats{}
	for _, r := range results {
		active := r["_id"].(bool)
		count := r["count"].(int32)
		if active {
			stats.ActiveUsers = int(count)
		} else {
			stats.InactiveUsers = int(count)
		}
	}

	return stats, nil
}

type UserStats struct {
	ActiveUsers   int `json:"active_users"`
	InactiveUsers int `json:"inactive_users"`
}

Pipeline Avançada com Join (Lookup)

// GetUsersWithPosts usuários com seus posts (simulação de JOIN)
func (r *userRepo) GetUsersWithPosts(ctx context.Context, userID string) (*UserWithPosts, error) {
	objectID, err := primitive.ObjectIDFromHex(userID)
	if err != nil {
		return nil, ErrInvalidID
	}

	pipeline := mongo.Pipeline{
		// Match usuário específico
		{{Key: "$match", Value: bson.M{"_id": objectID}}},
		
		// Lookup posts do usuário (coleção separada)
		{{Key: "$lookup", Value: bson.M{
			"from":         "posts",
			"localField":   "_id",
			"foreignField": "author_id",
			"as":           "posts",
		}}},
		
		// Adicionar contagem de posts
		{{Key: "$addFields", Value: bson.M{
			"posts_count": bson.M{"$size": "$posts"},
		}}},
		
		// Projetar campos desejados
		{{Key: "$project", Value: bson.M{
			"password": 0, // Excluir senha
		}}},
	}

	cursor, err := r.collection.Aggregate(ctx, pipeline)
	if err != nil {
		return nil, err
	}
	defer cursor.Close(ctx)

	var results []UserWithPosts
	if err := cursor.All(ctx, &results); err != nil {
		return nil, err
	}

	if len(results) == 0 {
		return nil, ErrUserNotFound
	}

	return &results[0], nil
}

type UserWithPosts struct {
	models.User `bson:",inline"`
	Posts       []Post `bson:"posts" json:"posts"`
	PostsCount  int    `bson:"posts_count" json:"posts_count"`
}

type Post struct {
	ID       primitive.ObjectID `bson:"_id" json:"id"`
	Title    string             `bson:"title" json:"title"`
	Content  string             `bson:"content" json:"content"`
	AuthorID primitive.ObjectID `bson:"author_id" json:"author_id"`
}
// FacetedSearch busca com facets
func (r *userRepo) FacetedSearch(ctx context.Context, query string) (*FacetedResult, error) {
	pipeline := mongo.Pipeline{
		// Match na busca
		{{Key: "$match", Value: bson.M{
			"$or": []bson.M{
				{"name": bson.M{"$regex": query, "$options": "i"}},
				{"email": bson.M{"$regex": query, "$options": "i"}},
			},
		}}},
		
		// Facet para múltiplas agregações
		{{Key: "$facet", Value: bson.M{
			"results": []bson.M{
				{"$limit": 20},
				{"$project": bson.M{"password": 0}},
			},
			"totalCount": []bson.M{
				{"$count": "count"},
			},
			"byRole": []bson.M{
				{"$unwind": "$roles"},
				{"$group": bson.M{
					"_id":   "$roles",
					"count": bson.M{"$sum": 1},
				}},
			},
			"byStatus": []bson.M{
				{"$group": bson.M{
					"_id":   "$active",
					"count": bson.M{"$sum": 1},
				}},
			},
		}},
	}

	cursor, err := r.collection.Aggregate(ctx, pipeline)
	if err != nil {
		return nil, err
	}
	defer cursor.Close(ctx)

	var results []FacetedResult
	if err := cursor.All(ctx, &results); err != nil {
		return nil, err
	}

	if len(results) == 0 {
		return &FacetedResult{}, nil
	}

	return &results[0], nil
}

type FacetedResult struct {
	Results    []models.User `bson:"results" json:"results"`
	TotalCount []struct {
		Count int `bson:"count" json:"count"`
	} `bson:"totalCount" json:"total_count"`
	ByRole []struct {
		Role  string `bson:"_id" json:"role"`
		Count int    `bson:"count" json:"count"`
	} `bson:"byRole" json:"by_role"`
	ByStatus []struct {
		Active bool `bson:"_id" json:"active"`
		Count  int  `bson:"count" json:"count"`
	} `bson:"byStatus" json:"by_status"`
}

Indexing Strategies

Criar Índices

// CreateIndexes cria índices necessários
func (r *userRepo) CreateIndexes(ctx context.Context) error {
	// Índice único para email
	emailIndex := mongo.IndexModel{
		Keys: bson.D{
			{Key: "email", Value: 1},
		},
		Options: options.Index().SetUnique(true),
	}

	// Índice composto para busca
	nameIndex := mongo.IndexModel{
		Keys: bson.D{
			{Key: "name", Value: "text"},
			{Key: "email", Value: "text"},
		},
		Options: options.Index().
			SetName("text_search").
			SetWeights(bson.M{"name": 10, "email": 5}),
	}

	// Índice para filtro de ativo + data
	activeDateIndex := mongo.IndexModel{
		Keys: bson.D{
			{Key: "active", Value: 1},
			{Key: "created_at", Value: -1},
		},
	}

	// Índice TTL para sessões (exemplo)
	ttlIndex := mongo.IndexModel{
		Keys: bson.D{
			{Key: "expires_at", Value: 1},
		},
		Options: options.Index().
			SetExpireAfterSeconds(0).
			SetName("session_ttl"),
	}

	indexes := []mongo.IndexModel{
		emailIndex,
		nameIndex,
		activeDateIndex,
		ttlIndex,
	}

	names, err := r.collection.Indexes().CreateMany(ctx, indexes)
	if err != nil {
		return fmt.Errorf("falha ao criar índices: %w", err)
	}

	log.Printf("✅ Índices criados: %v", names)
	return nil
}

Gerenciar Índices

// ListIndexes lista todos os índices
func (r *userRepo) ListIndexes(ctx context.Context) ([]bson.M, error) {
	cursor, err := r.collection.Indexes().List(ctx)
	if err != nil {
		return nil, err
	}
	defer cursor.Close(ctx)

	var indexes []bson.M
	if err := cursor.All(ctx, &indexes); err != nil {
		return nil, err
	}

	return indexes, nil
}

// DropIndex remove um índice
func (r *userRepo) DropIndex(ctx context.Context, name string) error {
	_, err := r.collection.Indexes().DropOne(ctx, name)
	return err
}

Transações

Transações Multi-Documento

// TransferSubscription transação com múltiplas operações
func (r *userRepo) TransferSubscription(
	ctx context.Context,
	fromUserID, toUserID string,
	plan string,
) error {
	// Converter IDs
	fromID, err := primitive.ObjectIDFromHex(fromUserID)
	if err != nil {
		return ErrInvalidID
	}
	toID, err := primitive.ObjectIDFromHex(toUserID)
	if err != nil {
		return ErrInvalidID
	}

	// Iniciar sessão para transação
	session, err := r.collection.Database().Client().StartSession()
	if err != nil {
		return fmt.Errorf("falha ao iniciar sessão: %w", err)
	}
	defer session.EndSession(ctx)

	// Callback da transação
	callback := func(sessCtx mongo.SessionContext) (interface{}, error) {
		// 1. Remover plano do usuário origem
		_, err := r.collection.UpdateOne(
			sessCtx,
			bson.M{"_id": fromID},
			bson.M{
				"$pull": bson.M{"subscriptions": bson.M{"plan": plan}},
				"$set":  bson.M{"updated_at": time.Now()},
			},
		)
		if err != nil {
			return nil, fmt.Errorf("falha ao remover do origem: %w", err)
		}

		// 2. Adicionar plano ao usuário destino
		_, err = r.collection.UpdateOne(
			sessCtx,
			bson.M{"_id": toID},
			bson.M{
				"$push": bson.M{"subscriptions": bson.M{
					"plan":      plan,
					"transferred_at": time.Now(),
				}},
				"$set": bson.M{"updated_at": time.Now()},
			},
		)
		if err != nil {
			return nil, fmt.Errorf("falha ao adicionar ao destino: %w", err)
		}

		// 3. Registrar transferência (outra coleção)
		transfers := r.collection.Database().Collection("transfers")
		_, err = transfers.InsertOne(sessCtx, bson.M{
			"from_user_id": fromID,
			"to_user_id":   toID,
			"plan":         plan,
			"created_at":   time.Now(),
		})
		if err != nil {
			return nil, fmt.Errorf("falha ao registrar transferência: %w", err)
		}

		return nil, nil
	}

	// Executar transação
	_, err = session.WithTransaction(ctx, callback)
	if err != nil {
		return fmt.Errorf("transação falhou: %w", err)
	}

	return nil
}

Boas Práticas

1. Context com Timeout

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

user, err := repo.GetByID(ctx, id)

2. Connection Pool

clientOptions := options.Client().
    SetMaxPoolSize(100).
    SetMinPoolSize(10).
    SetMaxConnIdleTime(30 * time.Second)

3. Tratamento de Erros

func mapMongoError(err error) error {
    if err == nil {
        return nil
    }
    
    if errors.Is(err, mongo.ErrNoDocuments) {
        return ErrNotFound
    }
    
    if mongo.IsDuplicateKeyError(err) {
        return ErrDuplicate
    }
    
    if mongo.IsTimeout(err) {
        return ErrTimeout
    }
    
    return err
}

4. Paginação Eficiente

// Use cursor-based pagination para grandes datasets
func (r *userRepo) ListWithCursor(ctx context.Context, lastID string, limit int) ([]*models.User, string, error) {
    filter := bson.M{}
    if lastID != "" {
        id, _ := primitive.ObjectIDFromHex(lastID)
        filter["_id"] = bson.M{"$gt": id}
    }
    
    opts := options.Find().
        SetLimit(int64(limit + 1)). // Buscar um extra para saber se há mais
        SetSort(bson.M{"_id": 1})
    
    cursor, err := r.collection.Find(ctx, filter, opts)
    if err != nil {
        return nil, "", err
    }
    defer cursor.Close(ctx)
    
    var users []*models.User
    if err := cursor.All(ctx, &users); err != nil {
        return nil, "", err
    }
    
    var nextCursor string
    if len(users) > limit {
        nextCursor = users[limit].ID.Hex()
        users = users[:limit]
    }
    
    return users, nextCursor, nil
}

5. Projeções para Performance

// Retornar apenas campos necessários
opts := options.Find().SetProjection(bson.M{
    "password": 0,      // Excluir
    "metadata": 0,      // Excluir
    "name":     1,      // Incluir
    "email":    1,      // Incluir
})

Checklist para Produção

  • Índices criados para queries frequentes
  • Connection pool configurado adequadamente
  • Timeouts em todas as operações
  • Transações para operações multi-documento
  • Projeções para limitar dados transferidos
  • Paginação para grandes datasets
  • Replica Set configurado (não standalone)
  • Autenticação habilitada
  • TLS/SSL para conexões
  • Backup automatizado

Próximos Passos

Aprofunde seus conhecimentos:

Ler mais →

Go e PostgreSQL: CRUD Completo com Boas Práticas

Aprenda a integrar Go com PostgreSQL de forma profissional. Domine CRUD operations, transações, migrations, query builders e a ferramenta sqlc para type-safe SQL.

PostgreSQL é um dos bancos de dados relacionais mais robustos e populares do mundo. Combinado com Go, você tem uma stack poderosa para aplicações de alta performance. Neste guia, você aprenderá todas as técnicas essenciais para trabalhar com PostgreSQL em Go, desde operações básicas até padrões avançados de produção.

Por Que PostgreSQL com Go?

O Match Perfeito

┌─────────────────────────────────────────────────────────────────┐
│  POSTGRESQL          │  GO                                     │
├─────────────────────────────────────────────────────────────────┤
│  Confiável           │  Type-safe, compilação rigorosa         │
├─────────────────────────────────────────────────────────────────┤
│  ACID transactions   │  Error handling explícito               │
├─────────────────────────────────────────────────────────────────┤
│  Alto desempenho     │  Runtime eficiente, baixa latência      │
├─────────────────────────────────────────────────────────────────┤
│  Extensível          │  Interfaces flexíveis                   │
├─────────────────────────────────────────────────────────────────┤
│  Open source         │  Open source                            │
└─────────────────────────────────────────────────────────────────┘

Configuração do Projeto

Instalação das Dependências

# Driver padrão PostgreSQL
go get -u github.com/lib/pq

# Driver alternativo (mais performático)
go get -u github.com/jackc/pgx/v5

# sqlc - type-safe SQL
go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest

# Migration tool
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest

Estrutura do Projeto

project/
├── cmd/
│   └── api/
│       └── main.go
├── internal/
│   ├── config/
│   │   └── database.go
│   ├── models/
│   │   └── user.go
│   ├── repository/
│   │   └── user_repository.go
│   └── service/
│       └── user_service.go
├── migrations/
│   ├── 001_create_users_table.up.sql
│   ├── 001_create_users_table.down.sql
│   └── ...
├── queries/
│   └── user.sql
├── sqlc.yaml
├── go.mod
└── .env

Conexão com PostgreSQL

Usando database/sql (Padrão)

// internal/config/database.go
package config

import (
	"database/sql"
	"fmt"
	"log"
	"os"
	"time"

	_ "github.com/lib/pq"
)

// Configurações do banco
type DBConfig struct {
	Host     string
	Port     int
	User     string
	Password string
	Database string
	SSLMode  string
}

func NewDBConfig() *DBConfig {
	return &DBConfig{
		Host:     getEnv("DB_HOST", "localhost"),
		Port:     getEnvAsInt("DB_PORT", 5432),
		User:     getEnv("DB_USER", "postgres"),
		Password: getEnv("DB_PASSWORD", "password"),
		Database: getEnv("DB_NAME", "myapp"),
		SSLMode:  getEnv("DB_SSLMODE", "disable"),
	}
}

func (c *DBConfig) ConnectionString() string {
	return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
		c.Host, c.Port, c.User, c.Password, c.Database, c.SSLMode)
}

// Conexão com pool
func NewDB(cfg *DBConfig) (*sql.DB, error) {
	db, err := sql.Open("postgres", cfg.ConnectionString())
	if err != nil {
		return nil, fmt.Errorf("falha ao abrir conexão: %w", err)
	}

	// Configurar pool de conexões
	db.SetMaxOpenConns(25)           // Máximo de conexões abertas
	db.SetMaxIdleConns(10)           // Máximo de conexões idle
	db.SetConnMaxLifetime(time.Hour) // Tempo máximo de vida
	db.SetConnMaxIdleTime(30 * time.Minute)

	// Verificar conexão
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := db.PingContext(ctx); err != nil {
		return nil, fmt.Errorf("falha ao ping banco de dados: %w", err)
	}

	log.Println("✅ Conectado ao PostgreSQL")
	return db, nil
}

func getEnv(key, defaultValue string) string {
	if value := os.Getenv(key); value != "" {
		return value
	}
	return defaultValue
}

func getEnvAsInt(key string, defaultValue int) int {
	if value := os.Getenv(key); value != "" {
		var intValue int
		fmt.Sscanf(value, "%d", &intValue)
		return intValue
	}
	return defaultValue
}

Usando pgx (Recomendado)

// internal/config/pgx_database.go
package config

import (
	"context"
	"fmt"
	"log"

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

func NewPgxPool(cfg *DBConfig) (*pgxpool.Pool, error) {
	connString := fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=%s&pool_max_conns=25",
		cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.Database, cfg.SSLMode)

	pool, err := pgxpool.New(context.Background(), connString)
	if err != nil {
		return nil, fmt.Errorf("falha ao criar pool: %w", err)
	}

	// Verificar conexão
	if err := pool.Ping(context.Background()); err != nil {
		return nil, fmt.Errorf("falha ao ping: %w", err)
	}

	log.Println("✅ Conectado ao PostgreSQL com pgx")
	return pool, nil
}

Modelos e Estruturas

// internal/models/user.go
package models

import (
	"time"
)

// User representa um usuário no banco
type User struct {
	ID        int64     `json:"id"`
	Email     string    `json:"email"`
	Name      string    `json:"name"`
	Password  string    `json:"-"` // Não serializar
	Active    bool      `json:"active"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

// UserCreate é usado para criar usuários
type UserCreate struct {
	Email    string `json:"email" validate:"required,email"`
	Name     string `json:"name" validate:"required,min=3,max=100"`
	Password string `json:"password" validate:"required,min=8"`
}

// UserUpdate é usado para atualizar usuários
type UserUpdate struct {
	Email  *string `json:"email,omitempty"`
	Name   *string `json:"name,omitempty"`
	Active *bool   `json:"active,omitempty"`
}

// TableName retorna o nome da tabela
func (User) TableName() string {
	return "users"
}

Operações CRUD

Repository Pattern

// internal/repository/user_repository.go
package repository

import (
	"context"
	"database/sql"
	"errors"
	"fmt"

	"project/internal/models"
)

var (
	ErrUserNotFound = errors.New("usuário não encontrado")
	ErrEmailExists  = errors.New("email já existe")
)

// UserRepository define operações de banco
type UserRepository interface {
	Create(ctx context.Context, user *models.UserCreate) (*models.User, error)
	GetByID(ctx context.Context, id int64) (*models.User, error)
	GetByEmail(ctx context.Context, email string) (*models.User, error)
	List(ctx context.Context, limit, offset int) ([]*models.User, error)
	Update(ctx context.Context, id int64, update *models.UserUpdate) (*models.User, error)
	Delete(ctx context.Context, id int64) error
	Count(ctx context.Context) (int64, error)
}

// userRepo implementa UserRepository
type userRepo struct {
	db *sql.DB
}

// NewUserRepository cria nova instância
func NewUserRepository(db *sql.DB) UserRepository {
	return &userRepo{db: db}
}

CREATE

// Create insere novo usuário
func (r *userRepo) Create(ctx context.Context, user *models.UserCreate) (*models.User, error) {
	query := `
		INSERT INTO users (email, name, password, active, created_at, updated_at)
		VALUES ($1, $2, $3, $4, NOW(), NOW())
		RETURNING id, email, name, active, created_at, updated_at
	`

	// Em produção, faça hash da senha!
	hashedPassword := hashPassword(user.Password)

	var created models.User
	err := r.db.QueryRowContext(ctx, query,
		user.Email,
		user.Name,
		hashedPassword,
		true,
	).Scan(
		&created.ID,
		&created.Email,
		&created.Name,
		&created.Active,
		&created.CreatedAt,
		&created.UpdatedAt,
	)

	if err != nil {
		// Verificar erro de duplicação
		if isDuplicateKeyError(err) {
			return nil, ErrEmailExists
		}
		return nil, fmt.Errorf("falha ao criar usuário: %w", err)
	}

	return &created, nil
}

func hashPassword(password string) string {
	// Use bcrypt em produção!
	// import "golang.org/x/crypto/bcrypt"
	// hashed, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	// return string(hashed)
	return "hashed_" + password
}

func isDuplicateKeyError(err error) bool {
	if err == nil {
		return false
	}
	// Verificar código de erro PostgreSQL para duplicação
	return strings.Contains(err.Error(), "duplicate key")
}

READ

// GetByID busca usuário por ID
func (r *userRepo) GetByID(ctx context.Context, id int64) (*models.User, error) {
	query := `
		SELECT id, email, name, active, created_at, updated_at
		FROM users
		WHERE id = $1
	`

	var user models.User
	err := r.db.QueryRowContext(ctx, query, id).Scan(
		&user.ID,
		&user.Email,
		&user.Name,
		&user.Active,
		&user.CreatedAt,
		&user.UpdatedAt,
	)

	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, ErrUserNotFound
		}
		return nil, fmt.Errorf("falha ao buscar usuário: %w", err)
	}

	return &user, nil
}

// GetByEmail busca usuário por email
func (r *userRepo) GetByEmail(ctx context.Context, email string) (*models.User, error) {
	query := `
		SELECT id, email, name, active, created_at, updated_at
		FROM users
		WHERE email = $1
	`

	var user models.User
	err := r.db.QueryRowContext(ctx, query, email).Scan(
		&user.ID,
		&user.Email,
		&user.Name,
		&user.Active,
		&user.CreatedAt,
		&user.UpdatedAt,
	)

	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, ErrUserNotFound
		}
		return nil, fmt.Errorf("falha ao buscar usuário: %w", err)
	}

	return &user, nil
}

// List retorna lista paginada de usuários
func (r *userRepo) List(ctx context.Context, limit, offset int) ([]*models.User, error) {
	query := `
		SELECT id, email, name, active, created_at, updated_at
		FROM users
		ORDER BY created_at DESC
		LIMIT $1 OFFSET $2
	`

	if limit <= 0 {
		limit = 10
	}
	if limit > 100 {
		limit = 100 // Máximo por página
	}

	rows, err := r.db.QueryContext(ctx, query, limit, offset)
	if err != nil {
		return nil, fmt.Errorf("falha ao listar usuários: %w", err)
	}
	defer rows.Close()

	var users []*models.User
	for rows.Next() {
		var user models.User
		err := rows.Scan(
			&user.ID,
			&user.Email,
			&user.Name,
			&user.Active,
			&user.CreatedAt,
			&user.UpdatedAt,
		)
		if err != nil {
			return nil, fmt.Errorf("falha ao scan usuário: %w", err)
		}
		users = append(users, &user)
	}

	if err = rows.Err(); err != nil {
		return nil, fmt.Errorf("erro ao iterar rows: %w", err)
	}

	return users, nil
}

// Count retorna total de usuários
func (r *userRepo) Count(ctx context.Context) (int64, error) {
	query := `SELECT COUNT(*) FROM users`

	var count int64
	err := r.db.QueryRowContext(ctx, query).Scan(&count)
	if err != nil {
		return 0, fmt.Errorf("falha ao contar usuários: %w", err)
	}

	return count, nil
}

UPDATE

// Update atualiza usuário
func (r *userRepo) Update(ctx context.Context, id int64, update *models.UserUpdate) (*models.User, error) {
	// Construir query dinamicamente
	var setParts []string
	var args []interface{}
	argIndex := 1

	if update.Email != nil {
		setParts = append(setParts, fmt.Sprintf("email = $%d", argIndex))
		args = append(args, *update.Email)
		argIndex++
	}
	if update.Name != nil {
		setParts = append(setParts, fmt.Sprintf("name = $%d", argIndex))
		args = append(args, *update.Name)
		argIndex++
	}
	if update.Active != nil {
		setParts = append(setParts, fmt.Sprintf("active = $%d", argIndex))
		args = append(args, *update.Active)
		argIndex++
	}

	if len(setParts) == 0 {
		return nil, errors.New("nenhum campo para atualizar")
	}

	// Sempre atualizar updated_at
	setParts = append(setParts, fmt.Sprintf("updated_at = $%d", argIndex))
	args = append(args, time.Now())
	argIndex++

	// Adicionar ID
	args = append(args, id)

	query := fmt.Sprintf(`
		UPDATE users
		SET %s
		WHERE id = $%d
		RETURNING id, email, name, active, created_at, updated_at
	`, strings.Join(setParts, ", "), argIndex)

	var user models.User
	err := r.db.QueryRowContext(ctx, query, args...).Scan(
		&user.ID,
		&user.Email,
		&user.Name,
		&user.Active,
		&user.CreatedAt,
		&user.UpdatedAt,
	)

	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, ErrUserNotFound
		}
		if isDuplicateKeyError(err) {
			return nil, ErrEmailExists
		}
		return nil, fmt.Errorf("falha ao atualizar usuário: %w", err)
	}

	return &user, nil
}

DELETE

// Delete remove usuário
func (r *userRepo) Delete(ctx context.Context, id int64) error {
	query := `DELETE FROM users WHERE id = $1`

	result, err := r.db.ExecContext(ctx, query, id)
	if err != nil {
		return fmt.Errorf("falha ao deletar usuário: %w", err)
	}

	rowsAffected, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("falha ao obter rows afetadas: %w", err)
	}

	if rowsAffected == 0 {
		return ErrUserNotFound
	}

	return nil
}

// SoftDelete (alternativa ao delete físico)
func (r *userRepo) SoftDelete(ctx context.Context, id int64) error {
	query := `
		UPDATE users 
		SET active = false, deleted_at = NOW() 
		WHERE id = $1
	`

	result, err := r.db.ExecContext(ctx, query, id)
	if err != nil {
		return fmt.Errorf("falha ao deletar usuário: %w", err)
	}

	rowsAffected, _ := result.RowsAffected()
	if rowsAffected == 0 {
		return ErrUserNotFound
	}

	return nil
}

Transações

// WithTransaction executa função dentro de transação
func (r *userRepo) WithTransaction(ctx context.Context, fn func(*sql.Tx) error) error {
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return fmt.Errorf("falha ao iniciar transação: %w", err)
	}

	defer func() {
		if p := recover(); p != nil {
			tx.Rollback()
			panic(p)
		}
	}()

	if err := fn(tx); err != nil {
		tx.Rollback()
		return err
	}

	if err := tx.Commit(); err != nil {
		return fmt.Errorf("falha ao commit: %w", err)
	}

	return nil
}

// TransferBalance exemplo de transação
func TransferBalance(ctx context.Context, db *sql.DB, fromID, toID int64, amount float64) error {
	tx, err := db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer tx.Rollback()

	// Verificar saldo
	var balance float64
	err = tx.QueryRowContext(ctx, 
		"SELECT balance FROM accounts WHERE id = $1 FOR UPDATE", 
		fromID,
	).Scan(&balance)
	if err != nil {
		return err
	}

	if balance < amount {
		return errors.New("saldo insuficiente")
	}

	// Debitar
	_, err = tx.ExecContext(ctx,
		"UPDATE accounts SET balance = balance - $1 WHERE id = $2",
		amount, fromID,
	)
	if err != nil {
		return err
	}

	// Creditar
	_, err = tx.ExecContext(ctx,
		"UPDATE accounts SET balance = balance + $1 WHERE id = $2",
		amount, toID,
	)
	if err != nil {
		return err
	}

	// Registrar transação
	_, err = tx.ExecContext(ctx,
		`INSERT INTO transactions (from_id, to_id, amount, created_at) 
		 VALUES ($1, $2, $3, NOW())`,
		fromID, toID, amount,
	)
	if err != nil {
		return err
	}

	return tx.Commit()
}

sqlc: Type-Safe SQL

Configuração

# sqlc.yaml
version: "2"
sql:
  - schema: "migrations/"
    queries: "queries/"
    engine: "postgresql"
    gen:
      go:
        package: "db"
        out: "internal/db"
        sql_package: "pgx/v5"
        emit_json_tags: true
        emit_prepared_queries: true
        emit_interface: true
        emit_empty_slices: true

Queries SQL

-- queries/user.sql
-- name: CreateUser :one
INSERT INTO users (email, name, password, active)
VALUES ($1, $2, $3, $4)
RETURNING *;

-- name: GetUser :one
SELECT * FROM users
WHERE id = $1;

-- name: GetUserByEmail :one
SELECT * FROM users
WHERE email = $1;

-- name: ListUsers :many
SELECT * FROM users
ORDER BY created_at DESC
LIMIT $1 OFFSET $2;

-- name: UpdateUser :one
UPDATE users
SET email = COALESCE($2, email),
    name = COALESCE($3, name),
    active = COALESCE($4, active),
    updated_at = NOW()
WHERE id = $1
RETURNING *;

-- name: DeleteUser :exec
DELETE FROM users WHERE id = $1;

-- name: CountUsers :one
SELECT COUNT(*) FROM users;

Gerar Código

# Gerar código Go a partir das queries
sqlc generate

# Estrutura gerada:
# internal/db/
# ├── db.go
# ├── models.go
# ├── querier.go (interface)
# └── user.sql.go

Uso do sqlc

// internal/repository/user_sqlc.go
package repository

import (
	"context"

	"github.com/jackc/pgx/v5/pgtype"
	"project/internal/db"
)

type UserSQLCRepository struct {
	queries *db.Queries
}

func NewUserSQLCRepository(queries *db.Queries) *UserSQLCRepository {
	return &UserSQLCRepository{queries: queries}
}

func (r *UserSQLCRepository) Create(ctx context.Context, email, name, password string) (*db.User, error) {
	user, err := r.queries.CreateUser(ctx, db.CreateUserParams{
		Email:    email,
		Name:     name,
		Password: password,
		Active:   true,
	})
	if err != nil {
		return nil, err
	}
	return &user, nil
}

func (r *UserSQLCRepository) List(ctx context.Context, limit, offset int32) ([]db.User, error) {
	return r.queries.ListUsers(ctx, db.ListUsersParams{
		Limit:  limit,
		Offset: offset,
	})
}

func (r *UserSQLCRepository) Update(ctx context.Context, id int64, email, name *string, active *bool) (*db.User, error) {
	var emailParam, nameParam pgtype.Text
	var activeParam pgtype.Bool

	if email != nil {
		emailParam = pgtype.Text{String: *email, Valid: true}
	}
	if name != nil {
		nameParam = pgtype.Text{String: *name, Valid: true}
	}
	if active != nil {
		activeParam = pgtype.Bool{Bool: *active, Valid: true}
	}

	user, err := r.queries.UpdateUser(ctx, db.UpdateUserParams{
		ID:     id,
		Email:  emailParam,
		Name:   nameParam,
		Active: activeParam,
	})
	if err != nil {
		return nil, err
	}
	return &user, nil
}

Migrations

Usando golang-migrate

# Criar nova migration
migrate create -ext sql -dir migrations -seq create_users_table

# Aplicar migrations
migrate -path migrations -database "postgres://user:pass@localhost/db?sslmode=disable" up

# Reverter última migration
migrate -path migrations -database "postgres://user:pass@localhost/db?sslmode=disable" down 1

Migration Files

-- migrations/001_create_users_table.up.sql
CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    name VARCHAR(100) NOT NULL,
    password VARCHAR(255) NOT NULL,
    active BOOLEAN DEFAULT true,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    deleted_at TIMESTAMP WITH TIME ZONE
);

CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_active ON users(active);

-- Trigger para atualizar updated_at
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ language 'plpgsql';

CREATE TRIGGER update_users_updated_at 
    BEFORE UPDATE ON users 
    FOR EACH ROW 
    EXECUTE FUNCTION update_updated_at_column();
-- migrations/001_create_users_table.down.sql
DROP TRIGGER IF EXISTS update_users_updated_at ON users;
DROP FUNCTION IF EXISTS update_updated_at_column();
DROP INDEX IF EXISTS idx_users_active;
DROP INDEX IF EXISTS idx_users_email;
DROP TABLE IF EXISTS users;

Boas Práticas

1. Context para Timeout

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

user, err := repo.GetByID(ctx, id)

2. Prepared Statements

// database/sql já usa prepared statements internamente
// Para queries repetidas, use Prepare

stmt, err := db.PrepareContext(ctx, "SELECT * FROM users WHERE id = $1")
if err != nil {
    return err
}
defer stmt.Close()

for _, id := range ids {
    row := stmt.QueryRowContext(ctx, id)
    // ...
}

3. Connection Pool

db.SetMaxOpenConns(25)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(time.Hour)

4. Error Handling

var (
    ErrNotFound = errors.New("recurso não encontrado")
    ErrDuplicate = errors.New("recurso já existe")
    ErrConstraint = errors.New("violação de constraint")
)

func mapError(err error) error {
    if err == nil {
        return nil
    }
    
    if errors.Is(err, sql.ErrNoRows) {
        return ErrNotFound
    }
    
    pqErr, ok := err.(*pq.Error)
    if ok {
        switch pqErr.Code {
        case "23505": // unique_violation
            return ErrDuplicate
        case "23503": // foreign_key_violation
            return ErrConstraint
        }
    }
    
    return err
}

5. Logging

// Log queries em desenvolvimento
func logQuery(query string, args ...interface{}) {
    if os.Getenv("ENV") == "development" {
        log.Printf("[SQL] %s | Args: %v", query, args)
    }
}

Checklist para Produção

  • Connection pool configurado corretamente
  • Timeouts em todas as queries (context)
  • Migrations automatizadas no deploy
  • Prepared statements para queries frequentes
  • Índices otimizados
  • Hash de senhas (bcrypt/argon2)
  • Soft delete para dados importantes
  • Auditoria (created_at, updated_at)
  • SSL para conexões (sslmode=require)
  • Backup automatizado

Próximos Passos

Aprofunde seus conhecimentos:

Ler mais →

Go Observability: Logs, Métricas e Traces - Guia Completo

Aprenda observabilidade em Go com logging estruturado, métricas Prometheus, distributed tracing e OpenTelemetry. Tutorial completo para monitorar aplicações Go em produção.

Observabilidade é essencial para aplicações em produção. Este guia completo mostra como implementar logging estruturado, métricas, traces e alerting em Go usando as melhores práticas e ferramentas modernas.

Pilares da Observabilidade

┌─────────────────────────────────────────────────────────┐
│                    OBSERVABILITY                        │
├─────────────┬─────────────┬─────────────────────────────┤
│    LOGS     │  METRICS    │          TRACES             │
│             │             │                             │
│ O que       │ Quanto/     │ Onde/O                      │
│ aconteceu   │ Quando      │ quando                      │
│             │             │                             │
│ Eventos     │ Números     │ Request flow                │
│ detalhados  │ agregados   │ distribuído                 │
└─────────────┴─────────────┴─────────────────────────────┘

1. Structured Logging

Logging estruturado é fundamental para debug e análise de incidentes.

Ler mais →

Go Performance: Profiling e Otimização de Alto Desempenho

Domine profiling e otimização em Go. Aprenda a usar pprof para CPU e memory profiling, escrever benchmarks com testing.B, identificar gargalos e aplicar estratégias avançadas de otimização. Guia completo com casos reais.

Performance é diferencial competitivo. Enquanto concorrentes lutam com latência, aplicações Go otimizadas processam milhões de requisições por segundo. Neste guia completo, você vai dominar as ferramentas e técnicas usadas por engenheiros da Google, Netflix e Uber para criar sistemas de alto desempenho.

Por Que Performance Importa em Go?

O Custo da Lentidão

MétricaImpacto no Negócio
+100ms de latência-1% de conversão (Amazon)
+500ms de latência-20% de tráfego (Google)
+1s de latência-11% pageviews (Bing)
3s de carga53% abandonam mobile

Em Go, cada microssegundo conta. Um serviço que processa 10M requests/dia economiza 2.7 horas de CPU por dia com apenas 1ms de otimização por request.

Ler mais →

Go Testing: TDD e Integração Contínua - Guia Completo

Aprenda Test-Driven Development (TDD) e CI/CD para Go. Tutorial completo com GitHub Actions, testes automatizados, pipelines e deploy contínuo para projetos Go profissionais.

Test-Driven Development (TDD) e Integração Contínua (CI/CD) são práticas essenciais para desenvolvimento de software profissional. Este guia completo mostra como implementar TDD e pipelines de CI/CD em projetos Go, desde testes unitários até deploy automatizado.

Por Que TDD + CI/CD em Go?

Go é ideal para TDD e CI/CD porque:

  • Testes rápidos — Compilação e execução em segundos
  • Binário único — Deploy simplificado
  • Biblioteca padrão robusta — Sem dependências complexas
  • Cross-compilation nativa — Build para múltiplas plataformas

Fundamentos de TDD

O Ciclo Red-Green-Refactor

┌─────────┐    ┌─────────┐    ┌─────────┐
│   RED   │ →  │  GREEN  │ →  │ REFACTOR│
│  (Falha)│    │ (Passa) │    │(Melhora)│
└─────────┘    └─────────┘    └─────────┘
     ↑                              │
     └──────────────────────────────┘

Exemplo Prático: Calculadora com TDD

Passo 1: Escreva o teste (RED)

Ler mais →

Go e Redis: Cache e Session Store Completo

Aprenda a usar Redis com Go para caching de alta performance, gerenciamento de sessões e arquiteturas escaláveis. Guia completo com exemplos práticos.

Go e Redis: Cache e Session Store Completo

O Redis é um dos bancos de dados em memória mais populares do mundo, e quando combinado com Go, cria aplicações extremamente rápidas e escaláveis. Neste guia completo, você vai aprender a implementar caching de alta performance e gerenciamento de sessões usando Go e Redis.

Por Que Usar Redis com Go?

Antes de mergulhar no código, entenda por que essa combinação é tão poderosa:

Ler mais →

Go Clean Architecture: Organizando Projetos Escaláveis

Domine a Clean Architecture em Go. Aprenda a organizar projetos com ports e adapters, dependency injection, repository pattern e testes eficientes. Estrutura profissional para aplicações Go.

Clean Architecture (Arquitetura Limpa) é o padrão usado por empresas como Netflix, Uber e Google para construir sistemas escaláveis e testáveis. Em Go, ela brilha pela simplicidade que combina com a filosofia da linguagem. Neste guia, você vai aprender a organizar projetos Go profissionais.

Por Que Clean Architecture em Go?

Os Problemas Sem Arquitetura

📁 projeto-caotico/
├── main.go              # 2000 linhas
├── handlers.go          # Tudo misturado
├── database.go          # SQL espalhado
├── models.go            # Regras de negócio aqui
└── utils.go             # Deus sabe o que tem aqui

Problemas:

Ler mais →

Go Concurrency Patterns: Goroutines e Channels Avançados

Aprenda patterns avançados de concorrência em Go: worker pools, fan-out/fan-in, rate limiting com channels, e context package. Tutorial completo com exemplos práticos de código.

A concorrência é um dos recursos mais poderosos de Go. Enquanto muitas linguagens tratam concorrência como um recurso avançado complexo, Go a torna acessível através de goroutines e channels. Este tutorial explora patterns avançados que você encontrará em aplicações Go de produção.

Pré-requisitos: Conhecimento básico de Go (variáveis, funções, structs) e familiaridade com goroutines e channels. Se você é novo em Go, confira nosso guia para iniciantes.

Sumário dos Patterns

PatternUse CaseComplexidade
Worker PoolProcessar tarefas em paralelo com controle⭐⭐
Fan-Out/Fan-InDistribuir trabalho e agregar resultados⭐⭐⭐
PipelineProcessar dados em estágios sequenciais⭐⭐
Rate LimitingControlar throughput de requisições⭐⭐⭐
Context CancellationCancelar operações de forma segura⭐⭐
Select StatementMultiplexar channels⭐⭐⭐

1. Worker Pool Pattern

O pattern Worker Pool é essencial quando você precisa processar um grande número de tarefas de forma concorrente, mas quer limitar o número de goroutines simultâneas.

Ler mais →

Go para APIs REST: Guia Completo de Desenvolvimento Web

Aprenda a criar APIs REST robustas em Go. Tutorial completo cobrindo rotas, JSON, middleware, autenticação, banco de dados e deploy. Exemplos práticos incluídos.

Go é uma das melhores linguagens para desenvolvimento de APIs REST. Com sua performance nativa, concorrência eficiente via goroutines e biblioteca padrão robusta, Go permite criar serviços web escaláveis e de alta performance. Neste guia completo, vamos construir uma API REST do zero, cobrindo desde conceitos básicos até padrões avançados usados em produção.

Por Que Go para APIs REST?

Vantagens de Go para Backend

┌─────────────────────────────────────────────────────────┐
│  PERFORMANCE          │  Goroutines leves (2KB stack)   │
├─────────────────────────────────────────────────────────┤
│  CONFIABILIDADE       │  Tipagem forte, erro explícito  │
├─────────────────────────────────────────────────────────┤
│  PRODUTIVIDADE        │  Compilação rápida, stdlib rica │
├─────────────────────────────────────────────────────────┤
│  DEPLOY               │  Binário único, cross-compile   │
├─────────────────────────────────────────────────────────┤
│  ECOSISTEMA           │  Gin, Echo, Chi, Fiber          │
└─────────────────────────────────────────────────────────┘

Empresas usando Go em produção:

Ler mais →

Go para Microserviços: Arquitetura e Práticas de Produção

Domine a arquitetura de microserviços em Go. Aprenda padrões de comunicação, service discovery, circuit breakers, distributed tracing e estratégias de deploy com exemplos práticos.

Microserviços transformaram a forma como desenvolvemos sistemas escaláveis. Go é a linguagem preferida para microserviços em empresas como Netflix, Uber, e Kubernetes. Neste guia completo, você aprenderá a arquitetar, construir e operar microserviços robustos em Go.

Por Que Go é Perfeito para Microserviços

O Match Perfeito

┌─────────────────────────────────────────────────────────────────┐
│  MICROSERVIÇO          │  GO                                    │
├─────────────────────────────────────────────────────────────────┤
│  Leve e rápido         │  Binário único, startup < 100ms        │
├─────────────────────────────────────────────────────────────────┤
│  Escalável             │  Goroutines (milhões simultâneas)      │
├─────────────────────────────────────────────────────────────────┤
│  Confiável             │  Tipagem forte, sem runtime pesado     │
├─────────────────────────────────────────────────────────────────┤
│  Eficiente             │  Memória mínima (10-50MB por serviço)  │
├─────────────────────────────────────────────────────────────────┤
│  Portátil              │  Cross-compile, Docker nativo          │
├─────────────────────────────────────────────────────────────────┤
│  Simples               │  Código explícito, fácil manutenção    │
└─────────────────────────────────────────────────────────────────┘

Comparação com Outras Linguagens

AspectoGoJavaNode.jsPython
Startup~50ms~5s~2s~1s
Memória10-50MB200-500MB100-300MB150-400MB
ConcorrênciaNativaThreads pesadasCallbacksGIL limita
BinárioÚnicoJVM necessáriaNode runtimeInterpreter
PerformanceNativaBoa (JIT)MédiaLenta

Arquitetura de um Microserviço Go

Estrutura de Projeto Recomendada

order-service/
├── cmd/
│   └── api/
│       └── main.go              # Entry point
├── internal/
│   ├── domain/
│   │   ├── order.go             # Entidades
│   │   └── errors.go            # Erros de domínio
│   ├── application/
│   │   ├── service.go           # Lógica de negócio
│   │   └── dto.go               # Data Transfer Objects
│   ├── infrastructure/
│   │   ├── http/
│   │   │   ├── handler.go       # HTTP handlers
│   │   │   ├── router.go        # Configuração de rotas
│   │   │   └── middleware.go    # Middlewares
│   │   ├── persistence/
│   │   │   ├── postgres.go      # Implementação do repo
│   │   │   └── redis.go         # Cache
│   │   └── messaging/
│   │       └── kafka.go         # Event publisher
│   └── config/
│       └── config.go            # Configurações
├── pkg/
│   └── logger/                  # Shared packages
├── api/
│   └── proto/                   # Protocol Buffers
├── deployments/
│   ├── docker/
│   └── k8s/
├── go.mod
└── Makefile

Implementação Completa

// internal/domain/order.go
package domain

import (
	"errors"
	"time"
)

var (
	ErrInvalidAmount  = errors.New("valor do pedido inválido")
	ErrOrderNotFound  = errors.New("pedido não encontrado")
)

type Order struct {
	ID        string    `json:"id"`
	UserID    string    `json:"user_id"`
	Items     []Item    `json:"items"`
	Total     float64   `json:"total"`
	Status    Status    `json:"status"`
	CreatedAt time.Time `json:"created_at"`
}

type Item struct {
	ProductID string  `json:"product_id"`
	Quantity  int     `json:"quantity"`
	Price     float64 `json:"price"`
}

type Status string

const (
	StatusPending   Status = "PENDING"
	StatusPaid      Status = "PAID"
	StatusShipped   Status = "SHIPPED"
	StatusDelivered Status = "DELIVERED"
)

func (o *Order) CalculateTotal() {
	var total float64
	for _, item := range o.Items {
		total += item.Price * float64(item.Quantity)
	}
	o.Total = total
}

func (o *Order) Validate() error {
	if o.Total <= 0 {
		return ErrInvalidAmount
	}
	return nil
}
// internal/application/service.go
package application

import (
	"context"
	"fmt"
	"time"

	"order-service/internal/domain"
)

// Portas (interfaces) para dependências
type OrderRepository interface {
	Save(ctx context.Context, order *domain.Order) error
	GetByID(ctx context.Context, id string) (*domain.Order, error)
	Update(ctx context.Context, order *domain.Order) error
}

type PaymentService interface {
	Process(ctx context.Context, orderID string, amount float64) error
}

type EventPublisher interface {
	PublishOrderCreated(ctx context.Context, order *domain.Order) error
}

type OrderService struct {
	repo      OrderRepository
	payment   PaymentService
	publisher EventPublisher
}

func NewOrderService(
	repo OrderRepository,
	payment PaymentService,
	publisher EventPublisher,
) *OrderService {
	return &OrderService{
		repo:      repo,
		payment:   payment,
		publisher: publisher,
	}
}

func (s *OrderService) CreateOrder(ctx context.Context, userID string, items []domain.Item) (*domain.Order, error) {
	order := &domain.Order{
		ID:        generateID(),
		UserID:    userID,
		Items:     items,
		Status:    domain.StatusPending,
		CreatedAt: time.Now(),
	}

	order.CalculateTotal()
	if err := order.Validate(); err != nil {
		return nil, err
	}

	// Salvar no banco
	if err := s.repo.Save(ctx, order); err != nil {
		return nil, fmt.Errorf("falha ao salvar pedido: %w", err)
	}

	// Publicar evento assíncrono
	go func() {
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		s.publisher.PublishOrderCreated(ctx, order)
	}()

	return order, nil
}

func (s *OrderService) ProcessPayment(ctx context.Context, orderID string) error {
	order, err := s.repo.GetByID(ctx, orderID)
	if err != nil {
		return err
	}

	if err := s.payment.Process(ctx, orderID, order.Total); err != nil {
		return fmt.Errorf("pagamento falhou: %w", err)
	}

	order.Status = domain.StatusPaid
	return s.repo.Update(ctx, order)
}

func generateID() string {
	return fmt.Sprintf("ORD-%d", time.Now().UnixNano())
}

Padrões de Comunicação

1. Síncrono: HTTP REST

Já coberto no guia de APIs REST. Para microserviços, adicione:

Ler mais →

Go Testing: TDD e Benchmarks - Guia Completo

Aprenda testes em Go com TDD, testes unitários, mock, benchmarks e coverage. Tutorial completo com exemplos práticos para testar aplicações Go profissionalmente.

Testar código é essencial para garantir qualidade, confiança e manutenibilidade de software. Go possui uma biblioteca de testes robusta e bem integrada na linguagem. Neste guia completo, vamos explorar desde testes unitários básicos até TDD, mocks, benchmarks e testes de integração.

Por Que Testar em Go?

Go torna testes simples com:

  • Package testing embutido - sem dependências externas
  • Testes compilados - rápidos e eficientes
  • Coverage nativo - go test -cover
  • Benchmarks - testes de performance integrados
  • Paralelismo - t.Parallel() para testes concorrentes

Testes Unitários Básicos

Arquivos de teste terminam com _test.go:

Ler mais →

Concorrência em Go: Goroutines e Channels - Guia Completo

Domine concorrência em Go. Tutorial completo sobre goroutines, channels, select, sync e padrões de concorrência. Exemplos práticos para iniciantes.

A concorrência é uma das features mais poderosas e distintivas de Go. Enquanto outras linguagens tornam a programação paralela complexa e cheia de boilerplate, Go a torna simples e elegante com goroutines e channels.

Neste quarto artigo da série “Golang para Iniciantes”, vamos desvendar os segredos da concorrência em Go. Você aprenderá a executar múltiplas tarefas simultaneamente de forma segura e eficiente.

Se você está chegando agora:

📖 ← Artigo Anterior: Controle de Fluxo em Go

Ler mais →

Controle de Fluxo em Go: if, switch, loops - Tutorial Completo

Aprenda controle de fluxo em Go. Tutorial completo sobre if/else, switch, for loops, range e controle de execução. Exemplos práticos para iniciantes.

Neste terceiro artigo da série “Golang para Iniciantes”, vamos explorar as estruturas de controle de fluxo em Go. Você aprenderá a tomar decisões com if/else, selecionar entre múltiplas opções com switch, e repetir operações com loops for.

Se você está chegando agora, recomendamos começar pela introdução:

📖 ← Artigo Anterior: Sintaxe Básica de Go

If e Else

Go tem uma sintaxe de condicionais limpa e direta.

If Básico

package main

import "fmt"

func main() {
    idade := 18

    if idade >= 18 {
        fmt.Println("Maior de idade")
    }
}

If-Else

package main

import "fmt"

func main() {
    nota := 7.5

    if nota >= 7.0 {
        fmt.Println("Aprovado!")
    } else if nota >= 5.0 {
        fmt.Println("Recuperação")
    } else {
        fmt.Println("Reprovado")
    }
}

If com Inicialização

Uma característica única de Go: você pode inicializar variáveis no if:

Ler mais →

Criando sua Primeira API REST em Go: Tutorial Completo Passo a Passo

Construa uma API REST completa em Go. Tutorial prático com HTTP handlers, JSON, routing, middleware e banco de dados. Do zero à produção.

Neste quinto e último artigo da série “Golang para Iniciantes”, vamos aplicar tudo o que aprendemos para construir uma API REST completa do zero. Você criará um servidor web funcional com rotas, handlers, JSON, persistência em memória e testes.

Se você está chegando agora, recomendamos revisar os artigos anteriores:

📖 ← Série Completa: Golang para Iniciantes

O Que Vamos Construir

Vamos criar uma API de Gerenciamento de Tarefas (Todo API) com as seguintes funcionalidades:

Ler mais →

Golang para Iniciantes: Primeiros Passos - Guia Completo 2026

Aprenda Go do zero. Tutorial completo de instalação, configuração do ambiente e primeiro programa. Guia passo a passo para iniciantes em Golang.

Go (ou Golang) é uma das linguagens de programação mais populares para desenvolvimento backend, microserviços e ferramentas de infraestrutura. Criada pelo Google em 2009, ela combina simplicidade, performance e produtividade de uma forma única.

Se você está começando sua jornada em Go, este é o lugar certo. Neste primeiro artigo da série “Golang para Iniciantes”, vamos cobrir tudo o que você precisa para dar os primeiros passos: desde a instalação até escrever seu primeiro programa funcional.

Ler mais →

Sintaxe Básica de Go: Variáveis, Tipos e Funções - Tutorial Completo

Aprenda a sintaxe de Go na prática: variáveis com :=, 15 tipos de dados, funções com múltiplos retornos, structs e métodos. Exemplos que você pode copiar e rodar.

Neste segundo artigo da série “Golang para Iniciantes”, vamos mergulhar nos fundamentos da linguagem. Você aprenderá tudo sobre variáveis, tipos de dados, funções, structs e métodos — os blocos de construção essenciais de qualquer programa Go.

Se você ainda não leu o primeiro artigo, recomendamos começar por lá:

📖 ← Artigo Anterior: Golang para Iniciantes: Primeiros Passos

Variáveis em Go

Go oferece várias formas de declarar variáveis, desde a forma explícita até a inferência de tipos.

Ler mais →