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
- O que é Dapr?
- Arquitetura e Sidecar
- Configuração
- Service Invocation
- State Management
- Pub/Sub Messaging
- Bindings
- Observability
O que é Dapr?
Building Blocks
Dapr fornece 8 building blocks que resolvem problemas comuns:
| Building Block | Descrição |
|---|---|
| Service Invocation | Chamadas resilientes entre serviços |
| State Management | Armazenamento de estado chave-valor |
| Pub/Sub | Mensageria publish/subscribe |
| Bindings | Integração com sistemas externos |
| Actors | Modelo de atores virtual |
| Observability | Rastreamento e métricas |
| Secrets | Gerenciamento de segredos |
| Configuration | Configurações dinâmicas |
Benefícios
1. Portabilidade Mesmo código funciona com Redis, Kafka, RabbitMQ, etc. Troque componentes sem mudar código.
2. Vendor Lock-in Zero Abstração sobre serviços cloud. Migre de AWS para Azure sem reescrever código.
3. Sidecar Architecture Dapr roda como processo separado (sidecar). Sua aplicação permanece simples.
┌─────────────────────────────────────────────────────────────┐
│ Kubernetes Pod │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ │ │ │ │
│ │ App Container │ │ Dapr Sidecar │ │
│ │ (Go Application) │◀─│ (daprd) │ │
│ │ │ │ │ │
│ │ HTTP/gRPC │ │ HTTP: 3500 │ │
│ │ localhost:5000 │ │ gRPC: 50001 │ │
│ │ │ │ │ │
│ └─────────────────────┘ └─────────┬───────────┘ │
│ │ │
└─────────────────────────────────────┼──────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Componentes (Redis, Kafka, etc) │
└─────────────────────────────────────────────────────────────┘
Arquitetura e Sidecar
Comunicação Sidecar
Sua aplicação Go comunica com Dapr via HTTP ou gRPC na porta local:
// HTTP API
// http://localhost:3500/v1.0/invoke/{app-id}/method/{method}
// http://localhost:3500/v1.0/state/{store-name}/{key}
// gRPC (mais performático)
// localhost:50001
SDK Go do Dapr
go get github.com/dapr/go-sdk/client
go get github.com/dapr/go-sdk/service/http
Cliente Dapr
package dapr
import (
"context"
"fmt"
dapr "github.com/dapr/go-sdk/client"
)
type Client struct {
dapr.Client
}
func NewClient() (*Client, error) {
client, err := dapr.NewClient()
if err != nil {
return nil, fmt.Errorf("falha ao criar cliente Dapr: %w", err)
}
return &Client{client}, nil
}
func (c *Client) Close() {
c.Client.Close()
}
Configuração
Componentes
Arquivos YAML definem componentes (state store, pub/sub, etc):
# components/statestore.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: redis:6379
- name: redisPassword
value: ""
- name: actorStateStore
value: "true"
# components/pubsub.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: pubsub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: redis:6379
Docker Compose (Desenvolvimento)
# docker-compose.yml
version: '3.8'
services:
redis:
image: redis:alpine
ports:
- "6379:6379"
service-a:
build: ./service-a
ports:
- "8080:8080"
environment:
- APP_PORT=8080
- DAPR_HTTP_PORT=3500
networks:
- dapr-network
service-a-dapr:
image: daprio/daprd:latest
command: [
"./daprd",
"--app-id", "service-a",
"--app-port", "8080",
"--dapr-http-port", "3500",
"--components-path", "/components"
]
volumes:
- ./components:/components
depends_on:
- redis
- service-a
network_mode: "service:service-a"
service-b:
build: ./service-b
ports:
- "8081:8081"
environment:
- APP_PORT=8081
- DAPR_HTTP_PORT=3501
service-b-dapr:
image: daprio/daprd:latest
command: [
"./daprd",
"--app-id", "service-b",
"--app-port", "8081",
"--dapr-http-port", "3501",
"--components-path", "/components"
]
volumes:
- ./components:/components
depends_on:
- redis
- service-b
network_mode: "service:service-b"
networks:
dapr-network:
driver: bridge
Service Invocation
Chamando Outros Serviços
package service
import (
"context"
"encoding/json"
"fmt"
dapr "github.com/dapr/go-sdk/client"
)
type UserService struct {
client dapr.Client
}
func NewUserService(client dapr.Client) *UserService {
return &UserService{client: client}
}
// GetUser chama outro serviço via Dapr
func (s *UserService) GetUser(ctx context.Context, userID string) (*User, error) {
// Invoca service-b método /users/{id}
resp, err := s.client.InvokeMethodWithContent(
ctx,
"service-b", // app-id do serviço destino
fmt.Sprintf("users/%s", userID), // método
"get", // HTTP method
&dapr.DataContent{
ContentType: "application/json",
},
)
if err != nil {
return nil, fmt.Errorf("falha na invocação: %w", err)
}
var user User
if err := json.Unmarshal(resp, &user); err != nil {
return nil, err
}
return &user, nil
}
// CreateUser envia POST para outro serviço
func (s *UserService) CreateUser(ctx context.Context, user *User) error {
data, err := json.Marshal(user)
if err != nil {
return err
}
_, err = s.client.InvokeMethodWithContent(
ctx,
"service-b",
"users",
"post",
&dapr.DataContent{
ContentType: "application/json",
Data: data,
},
)
return err
}
Recebendo Chamadas
package main
import (
"encoding/json"
"net/http"
"github.com/dapr/go-sdk/service/common"
daprHttp "github.com/dapr/go-sdk/service/http"
)
type User struct {
ID string `json:"id"`
Name string `json:"name"`
}
func main() {
// Cria serviço Dapr na porta da aplicação
s := daprHttp.NewService(":8080")
// Handler para invocação
s.AddServiceInvocationHandler("users", userHandler)
s.AddServiceInvocationHandler("health", healthHandler)
// Inicia serviço
if err := s.Start(); err != nil {
panic(err)
}
}
func userHandler(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) {
var user User
if err := json.Unmarshal(in.Data, &user); err != nil {
return nil, err
}
// Processa usuário
response, _ := json.Marshal(map[string]string{
"status": "created",
"id": user.ID,
})
return &common.Content{
Data: response,
ContentType: "application/json",
}, nil
}
func healthHandler(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) {
return &common.Content{
Data: []byte(`{"status":"healthy"}`),
ContentType: "application/json",
}, nil
}
State Management
Operações Básicas
package state
import (
"context"
"encoding/json"
"fmt"
"time"
dapr "github.com/dapr/go-sdk/client"
)
const stateStoreName = "statestore"
type StateManager struct {
client dapr.Client
}
func NewStateManager(client dapr.Client) *StateManager {
return &StateManager{client: client}
}
// SaveState salva estado com TTL
func (sm *StateManager) SaveState(ctx context.Context, key string, value interface{}, ttl time.Duration) error {
data, err := json.Marshal(value)
if err != nil {
return err
}
return sm.client.SaveState(ctx, stateStoreName, key, data, map[string]string{
"ttlInSeconds": fmt.Sprintf("%d", int(ttl.Seconds())),
})
}
// GetState recupera estado
func (sm *StateManager) GetState(ctx context.Context, key string, target interface{}) error {
item, err := sm.client.GetState(ctx, stateStoreName, key, nil)
if err != nil {
return err
}
if item.Value == nil {
return fmt.Errorf("key not found: %s", key)
}
return json.Unmarshal(item.Value, target)
}
// DeleteState remove estado
func (sm *StateManager) DeleteState(ctx context.Context, key string) error {
return sm.client.DeleteState(ctx, stateStoreName, key, nil)
}
// SaveBulkState salva múltiplos itens
func (sm *StateManager) SaveBulkState(ctx context.Context, items map[string]interface{}) error {
var states []*dapr.SetStateItem
for key, value := range items {
data, err := json.Marshal(value)
if err != nil {
return err
}
states = append(states, &dapr.SetStateItem{
Key: key,
Value: data,
})
}
return sm.client.SaveBulkState(ctx, stateStoreName, states)
}
Uso em Workflow
package order
type OrderWorkflow struct {
stateManager *state.StateManager
}
func (w *OrderWorkflow) ProcessOrder(ctx context.Context, order Order) error {
orderID := order.ID
// Salva estado inicial
if err := w.stateManager.SaveState(ctx, orderID, order, 24*time.Hour); err != nil {
return err
}
// Processa pagamento
payment, err := w.processPayment(ctx, order)
if err != nil {
// Atualiza estado com erro
order.Status = "payment_failed"
w.stateManager.SaveState(ctx, orderID, order, 24*time.Hour)
return err
}
// Atualiza estado
order.PaymentID = payment.ID
order.Status = "paid"
if err := w.stateManager.SaveState(ctx, orderID, order, 24*time.Hour); err != nil {
return err
}
// Envia para fulfillment
if err := w.sendToFulfillment(ctx, order); err != nil {
return err
}
order.Status = "fulfillment"
return w.stateManager.SaveState(ctx, orderID, order, 24*time.Hour)
}
Pub/Sub Messaging
Publicando Mensagens
package messaging
import (
"context"
"encoding/json"
dapr "github.com/dapr/go-sdk/client"
)
const pubsubName = "pubsub"
type Publisher struct {
client dapr.Client
}
func NewPublisher(client dapr.Client) *Publisher {
return &Publisher{client: client}
}
// PublishEvent publica evento em um tópico
func (p *Publisher) PublishEvent(ctx context.Context, topic string, data interface{}) error {
payload, err := json.Marshal(data)
if err != nil {
return err
}
return p.client.PublishEvent(ctx, pubsubName, topic, payload, map[string]string{
"content-type": "application/json",
})
}
// Exemplo: OrderCreated event
type OrderCreatedEvent struct {
OrderID string `json:"order_id"`
UserID string `json:"user_id"`
Total float64 `json:"total"`
Timestamp int64 `json:"timestamp"`
}
func (p *Publisher) PublishOrderCreated(ctx context.Context, event OrderCreatedEvent) error {
return p.PublishEvent(ctx, "orders", event)
}
// Exemplo: PaymentProcessed event
type PaymentProcessedEvent struct {
OrderID string `json:"order_id"`
PaymentID string `json:"payment_id"`
Status string `json:"status"`
}
func (p *Publisher) PublishPaymentProcessed(ctx context.Context, event PaymentProcessedEvent) error {
return p.PublishEvent(ctx, "payments", event)
}
Subscrevendo em Eventos
package main
import (
"context"
"encoding/json"
"log"
"github.com/dapr/go-sdk/service/common"
daprHttp "github.com/dapr/go-sdk/service/http"
)
func main() {
s := daprHttp.NewService(":8080")
// Subscrição em tópico "orders"
sub := &common.Subscription{
PubsubName: "pubsub",
Topic: "orders",
Route: "/orders",
}
s.AddTopicEventHandler(sub, orderEventHandler)
// Subscrição em tópico "payments"
paymentSub := &common.Subscription{
PubsubName: "pubsub",
Topic: "payments",
Route: "/payments",
}
s.AddTopicEventHandler(paymentSub, paymentEventHandler)
if err := s.Start(); err != nil {
panic(err)
}
}
func orderEventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) {
var event OrderCreatedEvent
if err := json.Unmarshal(e.RawData, &event); err != nil {
log.Printf("Erro ao parsear evento: %v", err)
return false, err // Não retry
}
log.Printf("Order criada: %s, Total: %.2f", event.OrderID, event.Total)
// Processa evento
// ...
return false, nil // Sucesso
}
func paymentEventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) {
var event PaymentProcessedEvent
if err := json.Unmarshal(e.RawData, &event); err != nil {
return false, err
}
if event.Status == "success" {
log.Printf("Pagamento aprovado: %s", event.OrderID)
// Atualiza ordem, envia email, etc
} else {
log.Printf("Pagamento falhou: %s", event.OrderID)
// Compensação
}
return false, nil
}
Bindings
Input Bindings (Triggers)
# components/cron-binding.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: daily-report
spec:
type: bindings.cron
version: v1
metadata:
- name: schedule
value: "0 9 * * *" # 9h todo dia
// Handler para cron trigger
func main() {
s := daprHttp.NewService(":8080")
s.AddBindingInvocationHandler("daily-report", func(ctx context.Context, in *common.BindingEvent) ([]byte, error) {
log.Println("Executando relatório diário...")
// Gera relatório
report := generateDailyReport()
// Envia email
sendEmail(report)
return nil, nil
})
s.Start()
}
Output Bindings
# components/email-binding.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: sendgrid
spec:
type: bindings.twilio.sendgrid
version: v1
metadata:
- name: apiKey
secretKeyRef:
name: sendgrid-key
key: apiKey
func (s *Service) SendEmail(ctx context.Context, to, subject, body string) error {
req := map[string]string{
"emailTo": to,
"subject": subject,
"emailBody": body,
}
data, _ := json.Marshal(req)
_, err := s.daprClient.InvokeBinding(ctx, &dapr.InvokeBindingRequest{
Name: "sendgrid",
Operation: "create",
Data: data,
})
return err
}
Observability
Tracing
Dapr automaticamente gera traces distribuídos:
# configuration/tracing.yaml
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: app-config
spec:
tracing:
samplingRate: "1"
zipkin:
endpointAddress: http://zipkin:9411/api/v2/spans
// Extrai trace context para propagar
func (s *Service) CallWithTrace(ctx context.Context) {
// Dapr injeta trace context automaticamente
// Acesse via OpenTelemetry ou similar
}
Métricas
// Métricas expostas pelo Dapr sidecar
// http://localhost:3500/v1.0/metrics
import "github.com/prometheus/client_golang/prometheus"
var (
requestsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "app_requests_total",
Help: "Total requests",
},
[]string{"method", "status"},
)
)
Conclusão
Neste guia, você aprendeu:
✅ Arquitetura: Sidecar pattern e building blocks ✅ Service Invocation: Chamadas resilientes entre serviços ✅ State Management: Armazenamento de estado distribuído ✅ Pub/Sub: Mensageria publish-subscribe ✅ Bindings: Integração com sistemas externos ✅ Observability: Tracing e métricas
Próximos Passos
- Go e Temporal - Workflows resilientes
- Go Microservices - Arquitetura completa
- Go e Kubernetes - Orquestração de containers
FAQ
Q: Dapr adiciona overhead? R: Mínimo. Latência adicional é tipicamente <5ms para chamadas locais.
Q: Posso usar Dapr sem Kubernetes? R: Sim! Funciona com Docker Compose, VMs ou bare metal.
Q: Preciso mudar meu código para trocar componentes? R: Não. Troque o YAML do componente, o código permanece igual.
Q: Dapr é production-ready? R: Sim. Usado em produção por empresas como Microsoft, Alibaba, Ignition Group.