Projetos Práticos para aprender System Design
Guia técnico com exercícios claros, padrões de arquitetura, desenho de dados e estratégias de operação para sistemas em produção. Escrito para você que busca decisões de alto impacto com foco em escalabilidade e qualidade.
Neste artigo, apresento 4 projetos práticos, com foco em decisões de arquitetura, dimensionamento de componentes e desenho de dados. Eu explico o racional, proponho exercícios e trago exemplos concretos para você aplicar já.
1. Definindo objetivos, requisitos não funcionais e métricas
Antes de desenhar qualquer solução, defino o objetivo central e as métricas de sucesso. Com System Design, aquilo que não está quantificado tende a receber menos atenção do time. Abaixo estão diretrizes que eu sigo.
- Latência alvo por operação: leituras rápidas (< 100 ms) e escritas estáveis (< 200–500 ms) sob carga típica.
- Throughput esperado (operações por segundo) e picos sazonais; planejo dimensionamento com buffers para picos.
- Disponibilidade desejada (por exemplo, 99.9% ou 99.99%) e RTO/RPO aceitáveis.
- Consistência de dados: decidir entre forte imediato ou eventual, com mecanismos de reconciliação quando necessário.
- Custos operacionais, limites de banda, uso de cache e estratégias de retenção de dados.
- Observabilidade: métricas, logs estruturados e tracing para diagnóstico rápido de incidentes.
Exercício rápido: descreva, em 1 página, os requisitos para um sistema de reservas de ingressos, considerando pico de demanda, controle de disponibilidade e recuperação de falhas.
2. Arquitetura de alto nível: componentes e fluxos
Eu normalmente começo definindo um conjunto básico de componentes que sustenta o domínio do sistema, mantendo limites claros entre responsabilidades:
- API Gateway que gerencia autenticação, autorização e roteamento.
- Serviços de domínio (ex.: Catálogo, Pedido, Usuário) com interfaces estáveis.
- Banco de dados principal por serviço (isolamento de transações) com leituras otimizadas via camadas de leitura.
- Cache distribuído (por exemplo, Redis) para dados quentes e padrões de acesso previsíveis.
- Event Bus ou fila assíncrona para integração entre serviços e atualização de visões de leitura.
- Visões de leitura separatadas (read models) para melhorar o desempenho de consultas complexas.
- Observabilidade: métricas, logs estruturados e tracing para rastrear a jornada de uma solicitação.
Fluxo típico de uma operação de criação:
- Cliente => API Gateway => Serviço de Autenticação (token) => Serviço de Domínio (cria pedido) => Evento no Event Bus => Serviço de Visão de Leitura atualiza read model => Cache invalidado/refrescado.
3. Dados, particionamento e consistência
Para suportar multi-tenancy e escalabilidade horizontal, o particionamento (sharding) é escolhido com base em padrões de acesso. A ideia é manter cada particionamento com carga balanceada, facilitar recuperação e reduzir contenção entre tenants.
- Escolha de chave de particionamento: tenant_id ou user_id, dependendo do padrão de leitura/escrita.
- Particionamento horizontal para escalar writes; leitura pode ser distribuída entre read models ou caches.
- Consistência: use confirmação assíncrona via eventos para atualizações entre serviços, mantendo leitura rápida com visões atualizadas periodicamente.
- Read models substituem queries complexas nos dados transacionais, melhorando a escalabilidade de leitura.
- Estratégias de retenção, TTL e arquivamento para controle de custo e conformidade.
-- Exemplo de particionamento em PostgreSQL
CREATE TABLE events (
tenant_id UUID NOT NULL,
event_id UUID NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
type TEXT,
payload JSONB,
PRIMARY KEY (tenant_id, created_at, event_id)
) PARTITION BY HASH (tenant_id);
CREATE TABLE events_p0 PARTITION OF events FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE events_p1 PARTITION OF events FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE events_p2 PARTITION OF events FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE events_p3 PARTITION OF events FOR VALUES WITH (MODULUS 4, REMAINDER 3);
Observação: este é um esquema básico para demonstrar o conceito de particionamento. Ajustes são necessários conforme o padrão de acesso real, a frequência de eventos e o volume de dados.
4. Observabilidade, resiliência e trade-offs
Operar sistemas em produção exige visibilidade e controles para manter a confiabilidade. Abaixo estão práticas que eu considero essenciais.
- Logs estruturados e consistentes, com correlação de traces entre serviços.
- Métricas significativas: latência, throughput, taxa de erros, saturation de recursos.
- Tracing distribuído para entender a jornada de uma requisição entre serviços.
- Estratégias de resiliência: retries com backoff, circuit breakers, timeout adequado e bulkheads.
- Planejamento de capacidade e DR: avaliações periódicas, failover testado e replicação entre regiões quando aplicável.
- Escolha de trade-offs: equilíbrio entre consistência de dados, disponibilidade e custo (CAP), alinhado aos SLOs.
Exercício rápido: desenhe um conjunto de dashboards para monitorar um sistema de reservas com foco em disponibilidade e latência. Liste as métricas mais relevantes para cada domínio.
Curtiu o conteúdo técnico? Continue explorando mais posts para aprofundar sua compreensão de arquitetura de software.