A ideia aqui é partilhar meus estudos com vocês. Hoje começaremos por um tema básico, que todo dev deve ter na ponta da língua. O que que é esse tal de microserviço?
O que é um Microsserviço?
Microserviços (ou Microservices Architecture) é um estilo arquitetural no qual uma aplicação é estruturada como uma coleção de serviços pequenos, independentes e fracamente acoplados. Isto é, cada microserviço tem baixa dependência em relação ao outro e alta autonomia. Pensando nisso, cada serviço deve focar em uma única responsabilidade, executar seu próprio processo separadamente, comunicar-se através de APIs (com conexões HTTP/REST ou serviços de mensageria, por exemplo), ser independência para ser deployado, ter seu próprio banco de dado e ser desenvolvido por times autonômos.
O dilema entre Monolito e Microserviço
Vamos desmistificar?
Enquanto o monolito é uma aplicação única, na qual os processos de deploy e de escabalidade funcionam "numa tacada só" (tudo "deploya" e escala junto), um microserviço é independente, assim como seu deploy e escala serviços específicos. Sobre complexidade, um ponto interessante é que o monolito tende a ser inicialmente mais simples, enquanto o microserviço já é inicializado com uma complexidade maior, devido a sua natureza infraestrutural e de comunicação.
Isso vai de encontro com o que tanto ouvimos dos desenvolvedores, sobre qual é a melhor escolha: depende. Se o seu projeto não for escalar, for algo pontual, às vezes, um monolito, por sua simplicidade já basta. Agora, para muitos casos, o investimento inicial compensa.
| Característica | Monolito | Microserviços |
|---|---|---|
| Estrutura | Aplicação única | Múltiplos serviços independentes |
| Deploy | Tudo junto | Cada serviço tem sua vez ao sol |
| Escalabilidade | Aplicação inteira | Serviços específicos |
| Tecnologia | Limitação de stack | Flexibilidade pra várias tecnologias |
| Complexidade | Menor inicialmente | Maior inicialmente (infra, comunicação...) |
| Times (tende a...) | Time grande e centralizado | Times pequenos e autônomos |
Beleza, entendi. Temos mais benefícios?
Benefícios do Microserviço:
1. Escalabilidade Independente
- Escale apenas os serviços que precisam
- Otimize recursos e custos
2. Resiliência e Isolamento de Falhas
- Se um serviço cai, os outros continuam funcionando
- Fault isolation: problemas ficam contidos
3. Flexibilidade Tecnológica
- Cada serviço pode usar a tecnologia mais adequada
- Facilita adoção de novas tecnologias gradualmente
4. Deploy Independente
- Atualize um serviço sem afetar os outros
- Faça releases mais rápidos e frequentes
- Menor risco em cada deploy
5. Organização de Times
- Times menores e focados em domínios específicos
- Maior autonomia e ownership
- Desenvolvimento paralelo mais eficiente
6. Manutenibilidade
- Código menor e mais focado
- Mais fácil de entender e modificar
- Tende a reduzir débito técnico
Então vou usar sempre! Tem alguma desvantagem?
Quando NÃO usar Microserviços:
Aplicações pequenas e simples
Sua configuração inicial tende a ser mais complexa e, pra aplicações pequenininhas, pode ser super desnecessário seu uso.
Falta de expertise em DevOps/infraestrutura
Essa não é EXATAMENTE uma destanvagem, mas, antes de construir seu microserviço, é essencial que sua infra seja bem pensada (e, para isso, precisamos de conhecimento sólido).
Startup em fase inicial
Essa aqui foi uma sugestão que encontrei na internet. E a justificativa é, no mínimo, interessante. Antes de escalar para microserviços, valide o produto. Valide, valide, valide! E, depois, se aventure.
Show de bola! Como posso construir um Microsserviço?
Lance mão dos princípios de Design
Domain-Driven Design (DDD)
- Identifique os Bounded Contexts (contextos delimitados)
- Cada microserviço representa um domínio de negócio
- Exemplo:
UserService,PaymentService,NotificationService
Single Responsibility
- Cada serviço tem apenas uma função
- Se precisa do "E" para descrever, provavelmente já foi além
Design pensado para Falhas
- Assuma que tudo pode falhar (e é quase certo que algo irá!)
- Implemente Circuit Breakers, Retries, Timeouts...
Tem mais!
Conheça um pouco sobre componentes essenciais
API Gateway
Cliente → API Gateway → [Microserviço A]
→ [Microserviço B]
→ [Microserviço C]
- Ponto de entrada único
- Roteamento de requisições
- Autenticação/Autorização centralizada
- Rate limiting
2. Service Discovery
- Serviços se registram automaticamente
- Localização dinâmica de serviços
3. Load Balancer
- Distribui requisições entre instâncias
- Health checks
4. Message Broker (opcional)
- Comunicação assíncrona
- Ferramentas: RabbitMQ, Kafka, NATS
5. Banco de Dados por Serviço
- Cada microserviço tem seu próprio DB
- Database por Service Pattern
Vamos ao passo a passo?
Planejamento e Design
1. Identifique os domínios de negócio
2. Defina as responsabilidades de cada serviço
3. Desenhe as APIs e contratos
4. Defina a estratégia de comunicação (síncrona/assíncrona)
5. Planeje a estratégia de dados
Configuração da Infraestrutura
1. Container runtime (Docker)
2. Orquestração (Kubernetes, Docker Swarm)
3. Service mesh (Istio, Linkerd) - opcional
4. Observabilidade (logs, métricas, tracing)
5. CI/CD pipeline
Desenvolvimento do Serviço
exemplo de estrutura
microservice/
├── cmd/
│ └── api/
│ └── main.go // Entry point
├── internal/
│ ├── handler/ // HTTP handlers
│ ├── service/ // Business logic
│ ├── repository/ // Data access
│ └── model/ // Domain models
├── pkg/
│ └── middleware/ // Middlewares reutilizáveis
├── config/
│ └── config.go // Configs
├── Dockerfile
├── docker-compose.yml
└── go.mod
main.go
package main
import (
"log"
"net/http"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
// Health check
r.HandleFunc("/health", healthHandler).Methods("GET")
// Business endpoints
r.HandleFunc("/api/v1/users", getUsersHandler).Methods("GET")
r.HandleFunc("/api/v1/users/{id}", getUserHandler).Methods("GET")
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", r))
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}
Alguns dos padrões essenciais:
Circuit Breaker:
import "github.com/sony/gobreaker"
var cb *gobreaker.CircuitBreaker
func init() {
cb = gobreaker.NewCircuitBreaker(gobreaker.Settings{
Name: "external-service",
MaxRequests: 3,
Interval: time.Minute,
Timeout: time.Second * 10,
})
}
func callExternalService() error {
_, err := cb.Execute(func() (interface{}, error) {
return http.Get("http://external-service/api")
})
return err
}
Health Check:
type HealthResponse struct {
Status string `json:"status"`
Services map[string]string `json:"services"`
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
health := HealthResponse{
Status: "UP",
Services: map[string]string{
"database": checkDatabase(),
"cache": checkCache(),
},
}
json.NewEncoder(w).Encode(health)
}
Observabilidade
Logging estruturado:
import "go.uber.org/zap"
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("user created",
zap.String("user_id", userID),
zap.String("email", email),
)
Métricas com Prometheus:
import "github.com/prometheus/client_golang/prometheus"
var httpRequestsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "endpoint", "status"},
)
func init() {
prometheus.MustRegister(httpRequestsTotal)
}
Tracing distribuído:
import "go.opentelemetry.io/otel"
// Adiciona tracing aos handlers
tracer := otel.Tracer("my-service")
ctx, span := tracer.Start(r.Context(), "getUserHandler")
defer span.End()
Containerização
Exemplo de Dockerfile:
# Multi-stage build
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o main cmd/api/main.go
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/main .
EXPOSE 8080
CMD ["./main"]
docker-compose.yml:
version: '3.8'
services:
api:
build: .
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/mydb
depends_on:
- db
db:
image: postgres:15-alpine
environment:
- POSTGRES_DB=mydb
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Segurança
Autenticação e Autorização
- JWT tokens
- OAuth 2.0 / OpenID Connect
- API Keys
Comunicação Segura
- TLS/HTTPS
- mTLS (mutual TLS) entre serviços
Secrets Management
- Não hardcode secrets!
- Use: HashiCorp Vault, AWS Secrets Manager, Kubernetes Secrets
Algumas Aplicações Práticas (hipotéticas)
Cenário: E-commerce
Problema: Monolito com 500k usuários, Black Friday derruba o site.
Solução com Microserviços:
┌─────────────────┐
│ API Gateway │
└────────┬────────┘
│
┌────┴────┬────────┬──────────┬─────────┐
│ │ │ │ │
┌───▼───┐ ┌──▼──┐ ┌───▼────┐ ┌───▼───┐ ┌──▼────┐
│ Users │ │Catalog│ │Payment│ │Orders│ │Shipping│
└───────┘ └─────┘ └────────┘ └───────┘ └───────┘
Benefícios:
- Escale apenas
PaymenteOrdersdurante picos - Se
Shippingcai, usuários ainda podem comprar - Deploy de promoções sem afetar pagamento
Cenário: Plataforma de Streaming
Problema: Diferentes funcionalidades com cargas muito diferentes.
Microserviços:
- Video Transcoding Service - Processa uploads (CPU intensivo)
- Recommendation Service - ML/AI para recomendações
- User Service - Gerencia contas e preferências
- Streaming Service - Entrega de conteúdo (alto throughput)
- Billing Service - Cobranças e assinaturas
- Analytics Service - Coleta dados de visualização
Benefícios:
- Transcoding usa GPU, Streaming usa CDN
- Recomendações podem usar Python/TensorFlow, por exemplo
- Streaming escala horizontalmente em eventos
Cenário: Banking/Fintech
Problema: Alta regulação, necessidade de auditoria, segurança crítica.
Microserviços:
Authentication Service (SSO)
↓
Account Service → Transaction Service → Notification Service
↓ ↓
Audit Service ← Fraud Detection Service
Benefícios:
- Audit Service registra TUDO independentemente
- Fraud Detection pode usar modelos ML complexos
- Isolamento de falhas crítico (fraude não derruba transações)
- Compliance mais fácil (isole serviços regulados)
Cenário: App de Delivery
Problema: Operações em tempo real, geo-localização, múltiplos atores.
Microserviços:
- Customer Service - Pedidos dos clientes
- Restaurant Service - Gestão de restaurantes/cardápios
- Driver Service - Entregadores disponíveis
- Location Service - Tracking em tempo real
- Matching Service - Match pedido → entregador
- Payment Service - Processamento de pagamentos
- Notification Service - Push notifications
Comunicação:
- REST para operações CRUD
- WebSockets para tracking em tempo real
- Kafka para eventos (pedido criado, entregador atribuído)
Nem tudo são flores, né?
Problemas Comuns de Microserviços em Go
Gerenciamento de Erros Distribuídos
Problema:
// Erro sem contexto propagado
func GetUser(id string) (*User, error) {
user, err := repo.FindByID(id)
if err != nil {
return nil, err // Perdeu o contexto!
}
return user, nil
}
Solução:
//Com contexto e wrapping
import "fmt"
func GetUser(id string) (*User, error) {
user, err := repo.FindByID(id)
if err != nil {
return nil, fmt.Errorf("failed to get user %s: %w", id, err)
}
return user, nil
}
// Plus: com pkg/errors
import "github.com/pkg/errors"
func GetUser(id string) (*User, error) {
user, err := repo.FindByID(id)
if err != nil {
return nil, errors.Wrap(err, "repository.FindByID failed")
}
return user, nil
}
Context Propagation (Tracing)
Problema:
// Sem propagação de contexto
func ProcessOrder(orderID string) error {
user := getUserFromDB(orderID) // Perdeu trace
payment := processPayment(user.ID) // Perdeu trace
return nil
}
Solução:
func ProcessOrder(ctx context.Context, orderID string) error {
user, err := getUserFromDB(ctx, orderID)
if err != nil {
return err
}
payment, err := processPayment(ctx, user.ID)
if err != nil {
return err
}
return nil
}
Timeout e Cancelamento
Problema:
// Chamada HTTP sem timeout
resp, err := http.Get("http://other-service/api")
Solução:
client := &http.Client{
Timeout: 5 * time.Second,
}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", "http://other-service/api", nil)
resp, err := client.Do(req)
Goroutine Leaks em Services
Problema:
// Goroutine sem controle
func HandleRequest(w http.ResponseWriter, r *http.Request) {
go func() {
// Se a requisição cancelar, isso continua rodando!
result := doHeavyWork()
// Pode causar leak
}()
}
Solução:
// Com context e, também, controle
func HandleRequest(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
done := make(chan Result)
go func() {
result := doHeavyWork()
select {
case done <- result:
case <-ctx.Done():
return // Cancela se request cancelar
}
}()
select {
case result := <-done:
json.NewEncoder(w).Encode(result)
case <-ctx.Done():
http.Error(w, "request cancelled", http.StatusRequestTimeout)
}
}
Database Connection Pooling
Problema:
// Abrir conexão a cada request
func GetUser(id string) (*User, error) {
db, _ := sql.Open("postgres", dsn) // Abre nova conexão!
defer db.Close()
// ...
}
Solução:
// Connection pool global
var db *sql.DB
func init() {
var err error
db, err = sql.Open("postgres", dsn)
if err != nil {
log.Fatal(err)
}
// Configurar pool
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(5 * time.Minute)
}
func GetUser(ctx context.Context, id string) (*User, error) {
var user User
err := db.QueryRowContext(ctx, "SELECT * FROM users WHERE id = $1", id).Scan(&user)
return &user, err
}
Graceful Shutdown
Problema:
// Server sem graceful shutdown
func main() {
http.ListenAndServe(":8080", handler)
}
// Se matar o processo, conexões serão perdidas
Solução:
// Graceful shutdown completo
func main() {
server := &http.Server{
Addr: ":8080",
Handler: handler,
}
// Inicia server em goroutine
go func() {
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server error: %v", err)
}
}()
// Espera sinal de shutdown
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
// Timeout para shutdown
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
log.Println("Server exited")
}
Circuit Breaker não implementado
Problema:
- Serviço A chama Serviço B
- Serviço B está lento
- Serviço A entra em looping de retry
- Cascading failure!
Solução:
import (
"github.com/sony/gobreaker"
"time"
)
var cb *gobreaker.CircuitBreaker
func init() {
var st gobreaker.Settings
st.Name = "HTTP Request"
st.MaxRequests = 3
st.Interval = time.Minute
st.Timeout = 10 * time.Second
st.ReadyToTrip = func(counts gobreaker.Counts) bool {
failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
return counts.Requests >= 3 && failureRatio >= 0.6
}
cb = gobreaker.NewCircuitBreaker(st)
}
func CallExternalService() (interface{}, error) {
return cb.Execute(func() (interface{}, error) {
resp, err := http.Get("http://external-service/api")
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
return resp, nil
})
}
Falta de Structured Logging
Problema:
// Logs não estruturados
log.Println("User created: " + userID)
log.Println("Error: " + err.Error())
Solução:
// Structured logging com zap
import "go.uber.org/zap"
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("user created",
zap.String("user_id", userID),
zap.String("email", email),
zap.Time("created_at", time.Now()),
)
logger.Error("failed to process payment",
zap.Error(err),
zap.String("payment_id", paymentID),
zap.String("user_id", userID),
)
Configuração Hardcoded
Problema:
// Configuração hardcoded
const DatabaseURL = "postgres://localhost:5432/mydb"
const RedisURL = "localhost:6379"
Solução:
// Configuração via environment
import "github.com/kelseyhightower/envconfig"
type Config struct {
DatabaseURL string `envconfig:"DATABASE_URL" required:"true"`
RedisURL string `envconfig:"REDIS_URL" default:"localhost:6379"`
Port int `envconfig:"PORT" default:"8080"`
LogLevel string `envconfig:"LOG_LEVEL" default:"info"`
}
func LoadConfig() (*Config, error) {
var cfg Config
err := envconfig.Process("", &cfg)
if err != nil {
return nil, err
}
return &cfg, nil
}
JSON Marshal/Unmarshal em Hot Path
Problema:
// Parsing JSON repetidas vezes
func HandleRequest(w http.ResponseWriter, r *http.Request) {
var req Request
json.NewDecoder(r.Body).Decode(&req) // Aloca memória
json.NewEncoder(w).Encode(response) // Aloca memória
}
Solução:
// Buffers com sync.Pool
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func HandleRequest(w http.ResponseWriter, r *http.Request) {
buf := bufferPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
bufferPool.Put(buf)
}()
// Ou use jsoniter para performance
import jsoniter "github.com/json-iterator/go"
var json = jsoniter.ConfigCompatibleWithStandardLibrary
}
E esse foi o estudo de hoje.
Espero que tu, que chegou até aqui, tenha aprendido ou, se já sabia, relembrado sobre esse tal de microserviço, viu?
Ah, pra fechar, deixo uns dois links interessantes, que se destacaram pra mim nos estudos:
4 Microservices Examples: Amazon, Netflix, Uber, and Etsy (https://blog.dreamfactory.com/microservices-examples)
Microservices: Is It Worth the Trouble? (https://dev.to/mygames/microservices-is-it-worth-the-trouble-3a69)
Bons estudos! E bora praticar!
Top comments (0)