Conceitos-chave de arquitetura
Acoplamento, coesão, separação de responsabilidades, princípios SOLID aplicados à arquitetura e bounded contexts.
Construir uma linguagem comum
Antes de nos aprofundarmos na arquitetura distribuída, é bom estabelecer um vocabulário comum. Estes termos vão aparecer repetidamente ao longo de toda a plataforma.
Muitas decisões técnicas parecem complexas apenas porque faltam definições compartilhadas. Nesta seção vamos construir essa linguagem comum e os princípios que orientam as decisões arquiteturais.
Domínio
É a área do negócio que o software tenta resolver. Por exemplo: usuários, produtos, pedidos, pagamentos, estoque.
O domínio não é um conceito técnico, e sim de negócio. Entender o domínio é o primeiro passo para projetar uma boa arquitetura.
Subdomínio
Uma parte específica dentro do domínio geral do negócio. Ajuda a decompor o problema em áreas mais gerenciáveis.
Bounded Context
Um limite explícito dentro do qual certos termos, modelos e regras têm um significado concreto.
Por que importam?
Em um sistema grande, a mesma palavra pode significar coisas diferentes dependendo do contexto:
- “Usuário” na autenticação: e-mail, senha, papéis.
- “Usuário” no faturamento: nome fiscal, endereço, forma de pagamento.
- “Usuário” no suporte: histórico de tickets, nível de satisfação.
Cada bounded context tem seu próprio modelo com os atributos de que precisa.
Mapeamento para a arquitetura
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Autenticación │ │ Facturación │ │ Soporte │
│ │ │ │ │ │
│ Usuario: │ │ Cliente: │ │ ContactoSoporte:│
│ - email │ │ - nombre_fiscal │ │ - historial │
│ - password_hash │ │ - dirección │ │ - satisfacción │
│ - roles │ │ - método_pago │ │ - prioridad │
└──────────────────┘ └──────────────────┘ └──────────────────┘
Comunicação entre contextos
- APIs bem definidas: Contratos explícitos entre contextos.
- Eventos de domínio: Um contexto publica eventos que outros consomem.
- Anti-Corruption Layer (ACL): Camada de tradução que protege um contexto de mudanças em outro.
DTO (Data Transfer Object)
Objeto usado para transferir dados entre camadas ou sistemas. Não representa necessariamente o modelo interno do domínio.
Contrato
Definição explícita de como duas partes se comunicam. Pode definir:
- Estrutura de request e response
- Campos obrigatórios e opcionais
- Tipos de dados
- Erros possíveis
- Regras de versionamento
Um contrato reduz a ambiguidade e ajuda a evoluir sistemas sem quebrar consumidores.
Evento
Fato relevante que já ocorreu no sistema. Por exemplo: OrderPlaced, UserCreated, PaymentConfirmed.
Os eventos são imutáveis e fundamentais em arquiteturas event-driven.
Integração
Conexão entre sistemas ou componentes distintos, sejam internos ou externos.
Consistência
Grau em que os dados refletem corretamente o estado do sistema.
Consistência forte
Todos veem o mesmo estado imediatamente.
Consistência eventual
Pode haver defasagens temporárias até que o sistema convirja.
Idempotência
Capacidade de executar uma operação várias vezes e obter o mesmo resultado sem efeitos duplicados indesejados.
Acoplamento
O acoplamento mede o quanto um componente depende de outro.
Tipos de acoplamento (do maior ao menor)
- Acoplamento de conteúdo: Um módulo modifica diretamente os dados internos de outro. É o pior tipo.
- Acoplamento comum: Dois módulos compartilham variáveis globais ou estado mutável.
- Acoplamento de controle: Um módulo diz a outro como fazer seu trabalho (passando flags de controle).
- Acoplamento de dados: Os módulos se comunicam apenas por meio de parâmetros bem definidos. É o tipo desejável.
Exemplo prático
// Alto acoplamiento: el servicio de pedidos conoce
// la estructura interna del servicio de usuarios
function crearPedido(usuarioId) {
const usuario = db.query('SELECT * FROM usuarios WHERE id = ?', usuarioId);
if (usuario.saldo_cuenta > 0) {
// Accede directamente a la tabla de otro dominio
}
}
// Bajo acoplamiento: comunicación a través de una interfaz
function crearPedido(usuarioId) {
const puedeComprar = await servicioUsuarios.verificarCapacidadCompra(usuarioId);
if (puedeComprar) {
// No conoce los detalles internos del servicio de usuarios
}
}
Busque baixo acoplamento entre módulos. Cada componente deve poder evoluir de forma independente.
Coesão
A coesão mede o quanto as responsabilidades dentro de um mesmo componente estão relacionadas. Alta coesão significa que tudo o que um módulo faz está estreitamente relacionado.
Tipos de coesão (do menor ao maior)
- Coincidental: As funções estão juntas por acaso, sem relação lógica.
- Temporal: As funções são agrupadas porque são executadas ao mesmo tempo.
- Funcional: Todas as funções contribuem para uma única responsabilidade bem definida. É o tipo ideal.
Exemplo prático
// Baja cohesión: este módulo hace demasiadas cosas no relacionadas
const utils = {
formatearFecha(fecha) { /* ... */ },
calcularImpuesto(monto) { /* ... */ },
enviarEmail(destinatario, asunto) { /* ... */ },
validarRUT(rut) { /* ... */ },
};
// Alta cohesión: cada módulo tiene una responsabilidad clara
const formatoFechas = {
formatear(fecha, formato) { /* ... */ },
parsear(texto, formato) { /* ... */ },
diferencia(fechaA, fechaB) { /* ... */ },
};
A relação acoplamento-coesão
Estes dois conceitos estão inversamente relacionados: ao aumentar a coesão dentro de um módulo, você naturalmente reduz o acoplamento entre módulos.
Separação de responsabilidades (Separation of Concerns)
Cada parte do sistema deve cuidar de uma única preocupação. Na arquitetura, isso se manifesta em camadas e módulos com responsabilidades claras.
Exemplo em camadas
┌─────────────────────────┐
│ Presentación (UI) │ → Cómo se muestra la información
├─────────────────────────┤
│ Lógica de negocio │ → Qué reglas aplican
├─────────────────────────┤
│ Acceso a datos │ → Dónde y cómo se almacenan los datos
└─────────────────────────┘
Princípios SOLID na arquitetura
Os princípios SOLID, originalmente formulados para o design orientado a objetos, também se aplicam no nível arquitetural:
S — Single Responsibility (Responsabilidade Única)
Cada serviço ou módulo deve ter um único motivo para mudar. Se uma mudança nas regras de faturamento exige modificar o serviço de usuários, há um problema de design.
O — Open/Closed (Aberto/Fechado)
O sistema deve ser extensível sem modificar o que já existe. Plugins, middlewares e event-driven architectures facilitam isso.
L — Liskov Substitution (Substituição de Liskov)
Se um serviço implementa um contrato (API), qualquer implementação alternativa deve ser intercambiável sem quebrar os consumidores.
I — Interface Segregation (Segregação de Interfaces)
Os consumidores não devem depender de interfaces que não usam. O padrão BFF (Backend for Frontend) é um exemplo direto desse princípio.
D — Dependency Inversion (Inversão de Dependências)
Os módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações.
// Violación: la lógica de negocio depende de PostgreSQL
class ServicioPedidos {
constructor() {
this.db = new PostgresClient();
}
}
// Correcto: depende de una abstracción
class ServicioPedidos {
constructor(repositorio) {
this.repositorio = repositorio; // Puede ser Postgres, Mongo, en memoria...
}
}
Resumo prático
| Conceito | Pergunta-chave | Objetivo |
|---|---|---|
| Domínio | Qual problema de negócio resolvemos? | Entender o contexto |
| Bounded Context | Onde estão os limites do domínio? | Modelos claros e autônomos |
| Contrato | Como as partes se comunicam? | Comunicação explícita |
| Acoplamento | O quanto A depende de B? | Minimizar dependências |
| Coesão | O quão relacionado está o que A faz? | Maximizar a relação interna |
| Consistência | Os dados refletem o estado real? | Gerenciar expectativas |
| Idempotência | Posso repetir sem efeitos duplicados? | Operações seguras |
| SOLID | O design é extensível e sustentável? | Flexibilidade a longo prazo |
Estes conceitos não são regras absolutas, e sim guias para tomar melhores decisões. Quanto mais claro for este vocabulário, mais fácil será entender decisões de design mais avançadas.