Introdução
Microsserviços em produção exigem uma combinação rara de qualidades: alta performance para lidar com milhares de requisições por segundo, baixo consumo de memória para reduzir custos de infraestrutura, latência previsível para cumprir SLAs, e confiabilidade para operar 24/7 sem memory leaks ou crashes inesperados. Rust oferece todas essas qualidades de forma nativa, tornando-se uma escolha cada vez mais popular para serviços que precisam ser robustos em produção.
Enquanto Go dominou a primeira onda de microsserviços cloud-native, Rust está se estabelecendo em cenários onde Go não é suficiente: serviços com requisitos de latência p99 agressivos, orçamento de memória limitado (serverless, edge computing), processamento de dados em tempo real e componentes de infraestrutura crítica. Neste artigo, vamos construir um microsserviço completo com gRPC, health checks, graceful shutdown e deploy otimizado.
Ecossistema para Microsserviços
Comunicação
| Biblioteca | Protocolo | Descrição |
|---|---|---|
| Tonic | gRPC | Implementação gRPC completa e de alta performance |
| Axum | HTTP/REST | Framework web com middleware Tower |
| reqwest | HTTP Client | Cliente HTTP assíncrono |
| lapin | AMQP | Cliente RabbitMQ |
| rdkafka | Kafka | Cliente Apache Kafka (bindings librdkafka) |
| redis | Redis | Cliente Redis com suporte a pub/sub e streams |
Observabilidade
| Biblioteca | Função |
|---|---|
| tracing | Logging estruturado e distributed tracing |
| metrics | Métricas (counters, gauges, histograms) |
| opentelemetry | OpenTelemetry (traces, metrics, logs) |
| tower-http | Middleware para HTTP (logging, compression, CORS) |
Resiliência
| Biblioteca | Função |
|---|---|
| tower | Middleware composicional (retry, timeout, rate limit) |
| backoff | Retry com backoff exponencial |
| circuit-breaker | Padrão circuit breaker |
Exemplo Prático: Microsserviço de Pedidos com gRPC
Vamos construir um serviço de gerenciamento de pedidos que se comunica via gRPC, inclui health checks, métricas e graceful shutdown.
Definição do Protocolo (proto/pedidos.proto)
syntax = "proto3";
package pedidos;
service ServicoPedidos {
// Criar um novo pedido
rpc CriarPedido(NovoPedidoRequest) returns (PedidoResponse);
// Buscar pedido por ID
rpc BuscarPedido(BuscarPedidoRequest) returns (PedidoResponse);
// Listar pedidos de um cliente
rpc ListarPedidos(ListarPedidosRequest) returns (ListarPedidosResponse);
// Atualizar status do pedido
rpc AtualizarStatus(AtualizarStatusRequest) returns (PedidoResponse);
// Stream de atualizações de status
rpc ObservarPedido(BuscarPedidoRequest) returns (stream StatusUpdate);
}
message NovoPedidoRequest {
string cliente_id = 1;
repeated ItemPedido itens = 2;
string endereco_entrega = 3;
}
message ItemPedido {
string produto_id = 1;
string nome = 2;
uint32 quantidade = 3;
double preco_unitario = 4;
}
message BuscarPedidoRequest {
string pedido_id = 1;
}
message ListarPedidosRequest {
string cliente_id = 1;
uint32 limite = 2;
uint32 offset = 3;
}
message PedidoResponse {
string id = 1;
string cliente_id = 2;
repeated ItemPedido itens = 3;
double total = 4;
string status = 5;
string endereco_entrega = 6;
string criado_em = 7;
string atualizado_em = 8;
}
message ListarPedidosResponse {
repeated PedidoResponse pedidos = 1;
uint32 total = 2;
}
message AtualizarStatusRequest {
string pedido_id = 1;
string novo_status = 2;
}
message StatusUpdate {
string pedido_id = 1;
string status_anterior = 2;
string status_novo = 3;
string timestamp = 4;
}
Cargo.toml
[package]
name = "servico-pedidos"
version = "0.1.0"
edition = "2021"
[dependencies]
tonic = "0.12"
prost = "0.13"
tokio = { version = "1", features = ["full"] }
tokio-stream = "0.1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
uuid = { version = "1", features = ["v4"] }
chrono = "0.4"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
anyhow = "1"
dashmap = "6"
[build-dependencies]
tonic-build = "0.12"
Build Script (build.rs)
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_build::compile_protos("proto/pedidos.proto")?;
Ok(())
}
Implementação do Serviço
use chrono::Utc;
use dashmap::DashMap;
use std::pin::Pin;
use std::sync::Arc;
use tokio::sync::broadcast;
use tokio_stream::{wrappers::BroadcastStream, Stream, StreamExt};
use tonic::{Request, Response, Status};
use uuid::Uuid;
// Módulo gerado pelo tonic-build
pub mod pedidos {
tonic::include_proto!("pedidos");
}
use pedidos::servico_pedidos_server::ServicoPedidos;
use pedidos::*;
// === Modelo Interno ===
#[derive(Debug, Clone)]
struct Pedido {
id: String,
cliente_id: String,
itens: Vec<ItemPedido>,
total: f64,
status: String,
endereco_entrega: String,
criado_em: String,
atualizado_em: String,
}
impl From<&Pedido> for PedidoResponse {
fn from(p: &Pedido) -> Self {
PedidoResponse {
id: p.id.clone(),
cliente_id: p.cliente_id.clone(),
itens: p.itens.clone(),
total: p.total,
status: p.status.clone(),
endereco_entrega: p.endereco_entrega.clone(),
criado_em: p.criado_em.clone(),
atualizado_em: p.atualizado_em.clone(),
}
}
}
// === Implementação do Serviço ===
pub struct ServicoPedidosImpl {
pedidos: Arc<DashMap<String, Pedido>>,
notificacoes: broadcast::Sender<StatusUpdate>,
}
impl ServicoPedidosImpl {
pub fn new() -> Self {
let (tx, _) = broadcast::channel(100);
Self {
pedidos: Arc::new(DashMap::new()),
notificacoes: tx,
}
}
}
#[tonic::async_trait]
impl ServicoPedidos for ServicoPedidosImpl {
async fn criar_pedido(
&self,
request: Request<NovoPedidoRequest>,
) -> Result<Response<PedidoResponse>, Status> {
let req = request.into_inner();
// Validações
if req.itens.is_empty() {
return Err(Status::invalid_argument(
"Pedido deve ter pelo menos um item",
));
}
if req.cliente_id.is_empty() {
return Err(Status::invalid_argument(
"ID do cliente é obrigatório",
));
}
let total: f64 = req
.itens
.iter()
.map(|i| i.preco_unitario * i.quantidade as f64)
.sum();
let agora = Utc::now().to_rfc3339();
let pedido = Pedido {
id: Uuid::new_v4().to_string(),
cliente_id: req.cliente_id,
itens: req.itens,
total,
status: "criado".to_string(),
endereco_entrega: req.endereco_entrega,
criado_em: agora.clone(),
atualizado_em: agora,
};
tracing::info!(
pedido_id = %pedido.id,
cliente_id = %pedido.cliente_id,
total = %pedido.total,
"Novo pedido criado"
);
let resposta = PedidoResponse::from(&pedido);
self.pedidos.insert(pedido.id.clone(), pedido);
Ok(Response::new(resposta))
}
async fn buscar_pedido(
&self,
request: Request<BuscarPedidoRequest>,
) -> Result<Response<PedidoResponse>, Status> {
let pedido_id = &request.into_inner().pedido_id;
let pedido = self
.pedidos
.get(pedido_id)
.ok_or_else(|| Status::not_found("Pedido não encontrado"))?;
Ok(Response::new(PedidoResponse::from(pedido.value())))
}
async fn listar_pedidos(
&self,
request: Request<ListarPedidosRequest>,
) -> Result<Response<ListarPedidosResponse>, Status> {
let req = request.into_inner();
let limite = if req.limite == 0 { 20 } else { req.limite } as usize;
let offset = req.offset as usize;
let todos: Vec<PedidoResponse> = self
.pedidos
.iter()
.filter(|entry| entry.value().cliente_id == req.cliente_id)
.map(|entry| PedidoResponse::from(entry.value()))
.collect();
let total = todos.len() as u32;
let pedidos: Vec<PedidoResponse> = todos
.into_iter()
.skip(offset)
.take(limite)
.collect();
Ok(Response::new(ListarPedidosResponse { pedidos, total }))
}
async fn atualizar_status(
&self,
request: Request<AtualizarStatusRequest>,
) -> Result<Response<PedidoResponse>, Status> {
let req = request.into_inner();
// Validar transição de status
let status_validos = [
"criado", "confirmado", "preparando",
"enviado", "entregue", "cancelado",
];
if !status_validos.contains(&req.novo_status.as_str()) {
return Err(Status::invalid_argument(format!(
"Status inválido: {}. Válidos: {:?}",
req.novo_status, status_validos
)));
}
let mut pedido = self
.pedidos
.get_mut(&req.pedido_id)
.ok_or_else(|| Status::not_found("Pedido não encontrado"))?;
let status_anterior = pedido.status.clone();
pedido.status = req.novo_status.clone();
pedido.atualizado_em = Utc::now().to_rfc3339();
tracing::info!(
pedido_id = %req.pedido_id,
de = %status_anterior,
para = %req.novo_status,
"Status atualizado"
);
// Notificar observadores
let _ = self.notificacoes.send(StatusUpdate {
pedido_id: req.pedido_id,
status_anterior,
status_novo: req.novo_status,
timestamp: Utc::now().to_rfc3339(),
});
Ok(Response::new(PedidoResponse::from(pedido.value())))
}
type ObservarPedidoStream =
Pin<Box<dyn Stream<Item = Result<StatusUpdate, Status>> + Send>>;
async fn observar_pedido(
&self,
request: Request<BuscarPedidoRequest>,
) -> Result<Response<Self::ObservarPedidoStream>, Status> {
let pedido_id = request.into_inner().pedido_id;
// Verificar se pedido existe
if !self.pedidos.contains_key(&pedido_id) {
return Err(Status::not_found("Pedido não encontrado"));
}
let rx = self.notificacoes.subscribe();
let stream = BroadcastStream::new(rx)
.filter_map(move |result| {
match result {
Ok(update) if update.pedido_id == pedido_id => {
Some(Ok(update))
}
Err(_) => Some(Err(Status::internal("Erro no stream"))),
_ => None,
}
});
Ok(Response::new(Box::pin(stream)))
}
}
Main com Health Check e Graceful Shutdown
use pedidos::servico_pedidos_server::ServicoPedidosServer;
use tokio::signal;
use tonic::transport::Server;
mod servico; // módulo com a implementação acima
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Logging estruturado em JSON para produção
tracing_subscriber::fmt()
.json()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "info".into()),
)
.init();
let addr = "0.0.0.0:50051".parse()?;
let servico = servico::ServicoPedidosImpl::new();
tracing::info!(%addr, "Iniciando serviço de pedidos");
// Health check para Kubernetes/Docker
let (mut health_reporter, health_service) =
tonic_health::server::health_reporter();
health_reporter
.set_serving::<ServicoPedidosServer<servico::ServicoPedidosImpl>>()
.await;
// Reflection para ferramentas como grpcurl
let reflection_service = tonic_reflection::server::Builder::configure()
.register_encoded_file_descriptor_set(
tonic::include_file_descriptor_set!("pedidos_descriptor")
)
.build_v1()?;
Server::builder()
.add_service(health_service)
.add_service(reflection_service)
.add_service(ServicoPedidosServer::new(servico))
.serve_with_shutdown(addr, async {
shutdown_signal().await;
tracing::info!("Sinal de shutdown recebido, finalizando...");
})
.await?;
tracing::info!("Serviço encerrado com sucesso");
Ok(())
}
/// Aguarda sinais de shutdown (SIGTERM, SIGINT).
async fn shutdown_signal() {
let ctrl_c = async {
signal::ctrl_c()
.await
.expect("Falha ao registrar handler Ctrl+C");
};
#[cfg(unix)]
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("Falha ao registrar handler SIGTERM")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}
}
Dockerfile Multi-Stage para Produção
# === Build ===
FROM rust:1.85-bookworm AS builder
# Instalar protoc para compilar .proto
RUN apt-get update && apt-get install -y protobuf-compiler && rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Cache de dependências
COPY Cargo.toml Cargo.lock ./
COPY build.rs ./
COPY proto ./proto
RUN mkdir src && echo 'fn main() {}' > src/main.rs
RUN cargo build --release && rm -rf src
# Build real
COPY src ./src
RUN touch src/main.rs && cargo build --release
# === Runtime ===
FROM debian:bookworm-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl \
&& rm -rf /var/lib/apt/lists/*
RUN useradd --create-home --shell /bin/bash app
USER app
COPY --from=builder /app/target/release/servico-pedidos /usr/local/bin/
EXPOSE 50051
# Health check via grpc-health-probe
HEALTHCHECK --interval=15s --timeout=5s --retries=3 \
CMD grpc_health_probe -addr=:50051 || exit 1
ENV RUST_LOG=info
ENTRYPOINT ["servico-pedidos"]
Docker Compose com Serviços Dependentes
version: "3.8"
services:
pedidos:
build: .
ports:
- "50051:50051"
environment:
RUST_LOG: "info,servico_pedidos=debug"
DATABASE_URL: "postgres://app:senha@postgres:5432/pedidos"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
postgres:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: senha
POSTGRES_DB: pedidos
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
pgdata:
Padrões Essenciais para Produção
Retry com Backoff Exponencial
use std::time::Duration;
use tokio::time::sleep;
async fn com_retry<F, Fut, T, E>(
operacao: F,
max_tentativas: u32,
) -> Result<T, E>
where
F: Fn() -> Fut,
Fut: std::future::Future<Output = Result<T, E>>,
E: std::fmt::Display,
{
let mut tentativa = 0;
loop {
match operacao().await {
Ok(resultado) => return Ok(resultado),
Err(e) => {
tentativa += 1;
if tentativa >= max_tentativas {
tracing::error!(%e, "Máximo de tentativas excedido");
return Err(e);
}
let delay = Duration::from_millis(100 * 2u64.pow(tentativa));
tracing::warn!(
%e, tentativa, delay_ms = %delay.as_millis(),
"Tentativa falhou, retrying..."
);
sleep(delay).await;
}
}
}
}
Middleware de Métricas com Tower
use std::time::Instant;
use tower::Layer;
#[derive(Clone)]
pub struct MetricasLayer;
impl<S> Layer<S> for MetricasLayer {
type Service = MetricasMiddleware<S>;
fn layer(&self, inner: S) -> Self::Service {
MetricasMiddleware { inner }
}
}
#[derive(Clone)]
pub struct MetricasMiddleware<S> {
inner: S,
}
// A implementação completa registraria métricas em Prometheus/OpenTelemetry
// usando a crate `metrics` para counters e histograms.
Empresas Usando Rust para Microsserviços
- Discord: Migrou serviços críticos de Go para Rust, reduzindo latência p99 e uso de memória
- Cloudflare: Centenas de microsserviços Rust no edge processando trilhões de requests
- AWS: Serviços internos, incluindo componentes do S3, Lambda e IAM
- Dropbox: Componentes de sincronização e storage
- Figma: Servidor multiplayer para colaboração em tempo real
- 1Password: Backend de criptografia e sincronização
- Fly.io: Runtime de aplicações distribuídas
- Linkerd: Data plane proxy (microsserviço de infraestrutura)
Como Começar
- Fundamentos de Rust: Tutorial de primeiros passos e concorrência
- API REST: Comece com uma API REST simples com Axum antes de ir para gRPC
- Servidor HTTP: Pratique com a receita de servidor HTTP
- Docker: Configure seu ambiente Docker para Rust
- Banco de dados: Integre com PostgreSQL
- Tratamento de erros: Essencial para produção — tutorial completo
Conclusão
Rust é uma escolha excelente para microsserviços que precisam entregar alta performance com baixo consumo de recursos. A combinação de gRPC com Tonic, observabilidade com tracing/OpenTelemetry e deploy otimizado com Docker multi-stage cria um stack de produção robusto. Se seus microsserviços precisam processar milhares de requisições por segundo com latência previsível e consumo mínimo de memória, Rust oferece o melhor custo-benefício entre performance e produtividade.
Veja Também
- Guia: Docker para Rust — Imagens otimizadas para produção
- Receita: Criar Servidor HTTP — Fundamentos de servidores em Rust
- Tutorial: API REST com Axum — REST como alternativa ao gRPC
- Rust para Desenvolvimento Web — Ecossistema web completo
- Rust para DevOps — CI/CD e infraestrutura
- Rust para APIs GraphQL — Alternativa ao REST e gRPC
- Receita: Variáveis de Ambiente — Configuração de serviços
- Vagas Rust no Brasil — Oportunidades em empresas que usam microsserviços