29/06/2026

Volume I — Parte 4 — Arquitetura de Software, Integração entre Sistemas, Padrões de Projeto, Microsserviços, Mensageria e APIs Corporativas

Voltar
Sumário do artigo

← Parte anterior: Volume I — Parte 3 — Representação da Informação, Arquitetura de Computadores, Sistemas de Arquivos, Virtualização, Cloud Computing e Segurança da Informação

Foco: transformar necessidades de negócio em estruturas de software que possam evoluir, integrar-se e operar com segurança.
Abordagem: decisões justificadas, comparação entre alternativas, contratos explícitos, falhas tratadas como parte do desenho, exemplos em Python/Flask e aplicação ao ecossistema KnowledgeBasePro, BookWave, GLPI, n8n, Redis/RQ e Ollama.


Sumário


Prefácio — Arquitetura começa onde a solução simples deixa de ser suficiente

Um formulário recebe um PDF, grava o arquivo, cadastra um registro no banco e devolve “upload concluído”. Em uma demonstração, tudo funciona. Em produção, o proxy limita o corpo da requisição; dois cliques criam documentos duplicados; o container reinicia antes de persistir o arquivo; o worker processa a mesma tarefa duas vezes; o modelo de IA demora; a API do GLPI fica indisponível; o usuário visualiza um documento de outra entidade; e não existe um identificador comum entre os logs para reconstruir a falha.

Nenhum desses problemas é resolvido por um diagrama bonito. Eles exigem decisões sobre fronteiras, estado, contratos, identidade, concorrência, segurança, recuperação e operação. Esse conjunto de decisões — mais as razões e consequências associadas — constitui a arquitetura real.

A arquitetura também não nasce apenas quando aparecem microsserviços. Um monólito possui arquitetura; um script de integração possui arquitetura; até uma planilha crítica conectada a um ERP possui decisões estruturais e riscos. A diferença é que, quando essas decisões permanecem implícitas, a organização paga por elas na forma de retrabalho, indisponibilidade e dependência de pessoas específicas.

Esta parte encerra o Volume I ligando os fundamentos físicos e lógicos das partes anteriores à construção de sistemas corporativos. O objetivo não é ensinar a escolher a tecnologia “mais moderna”. É desenvolver uma capacidade mais valiosa: formular o problema, tornar os trade-offs visíveis, escolher a menor arquitetura que satisfaça os riscos e verificar a decisão por evidências.


Como estudar esta parte

Use quatro perguntas em cada capítulo:

  1. Qual problema existe antes da tecnologia? Uma fila não é requisito; absorver picos sem bloquear o usuário pode ser.
  2. Que propriedade precisa ser preservada? Consistência, disponibilidade, confidencialidade, ordem, auditabilidade ou tempo de resposta?
  3. Que falha a decisão introduz? Todo mecanismo adiciona modos de falha, custo e trabalho operacional.
  4. Como saberemos se funcionou? Uma decisão verificável possui teste, métrica, alerta, experimento ou critério de aceite.

Ao estudar um padrão, escreva dois exemplos: um caso em que ele reduz complexidade e outro em que apenas desloca complexidade. Ao analisar uma integração, percorra o caminho completo do dado: origem, transformação, transporte, destino, confirmação, repetição, auditoria e reconciliação.

Regra de leitura: nomes como REST, Kafka, SOLID e Clean Architecture são atalhos. O aprendizado começa quando você consegue explicar as garantias, limites e custos escondidos atrás do nome.


Mapa sistêmico

flowchart TB
    N["Necessidade de negócio"] --> D["Drivers e restrições"]
    D --> A["Decisões arquiteturais"]
    A --> C["Contratos e componentes"]
    C --> O["Operação e evidências"]
    O --> R["Revisão da decisão"]
    R --> A

A arquitetura percorre um ciclo, não uma linha reta. O negócio muda, a carga cresce, uma dependência externa altera o contrato e a equipe aprende. A arquitetura sustentável deixa rastros para que uma decisão possa ser reavaliada sem depender da memória de quem a tomou.


Capítulo 16 — Arquitetura de Software como sistema de decisões

Objetivos de aprendizagem

Ao concluir este capítulo, você deverá ser capaz de:

  • diferenciar arquitetura, descrição arquitetural, design e implementação;
  • identificar stakeholders, preocupações, drivers e restrições;
  • converter atributos de qualidade abstratos em cenários mensuráveis;
  • registrar decisões e consequências por meio de ADRs;
  • escolher visões e diagramas adequados ao público;
  • realizar uma avaliação arquitetural leve baseada em risco.

16.1 O que é — e o que não é — arquitetura

A ISO/IEC/IEEE 42010:2022 distingue a arquitetura de uma entidade da descrição arquitetural que expressa essa arquitetura [1]. Essa separação é essencial: o sistema possui estruturas e comportamentos mesmo que ninguém os tenha documentado. O desenho é uma representação; não é a própria arquitetura.

Arquitetura de software compreende as estruturas significativas do sistema, as relações entre seus elementos e as decisões que condicionam qualidades relevantes. “Significativa” não significa apenas grande. Uma regra aparentemente pequena — por exemplo, salvar uploads somente em volume persistente — pode determinar a durabilidade de todo o produto.

Não existe uma lista universal de decisões arquiteturais. Uma escolha se torna arquitetural quando é cara de mudar, afeta múltiplas partes, condiciona atributos de qualidade ou cria compromisso organizacional. Exemplos:

  • adotar sessão no servidor ou tokens autocontidos;
  • permitir que módulos compartilhem tabelas ou exigir acesso por interfaces;
  • processar PDF na requisição ou em trabalho assíncrono;
  • usar um banco transacional único ou bancos por serviço;
  • definir quem é a fonte oficial de usuários, documentos ou chamados;
  • publicar uma API que outros times passarão a consumir;
  • tratar uma integração como melhor esforço ou como processo reconciliável.

Cor de botão, nome de variável e estrutura de uma função normalmente são decisões locais. Entretanto, um design system corporativo, uma convenção pública de erros ou uma biblioteca obrigatória de autenticação podem ganhar alcance arquitetural. O contexto determina a escala da decisão.

16.2 Stakeholders, preocupações, visões e viewpoints

Um único diagrama raramente responde às perguntas de todos. A direção quer compreender risco, custo e capacidade de evolução. Desenvolvimento precisa de dependências e contratos. Operações precisa de processos, portas, armazenamento, telemetria e procedimentos de recuperação. Segurança busca fronteiras de confiança, identidades e fluxos de dados. Usuários se preocupam com disponibilidade, privacidade e experiência.

Stakeholder Pergunta típica Visão útil
Negócio Qual capacidade será entregue e qual risco permanece? contexto, capacidades e trade-offs
Desenvolvimento Onde fica cada responsabilidade? containers, componentes e dependências
Operações O que é implantado, monitorado e recuperado? deployment, runtime e fluxo de falha
Segurança Onde há dados sensíveis e mudança de confiança? fluxo de dados e threat model
Dados Quem é dono, qual schema e qual retenção? modelo de informação e linhagem
Suporte Como correlacionar sintoma, requisição e componente? sequência, logs, métricas e runbook

Um viewpoint define as convenções para construir uma visão: público, preocupações, elementos, relações e notação. A visão é a aplicação desse viewpoint a um sistema concreto. Isso evita diagramas universais, densos e sem propósito.

16.3 Arquitetura, design, implementação e operação

Nível Pergunta Exemplo na KnowledgeBasePro
Arquitetura Como responsabilidades e estado são distribuídos? aplicação Web e worker separados por fila
Design Como um módulo realiza sua responsabilidade? DocumentService coordena repositório e publicador
Implementação Como a regra é codificada? validação de extensão e hash em Python
Operação Como a solução vive em produção? limites do Nginx, volume, métricas da fila e backup

As fronteiras não são rígidas. Escolher RQ em vez de RabbitMQ pode ser detalhe de implementação em um protótipo; torna-se decisão arquitetural quando contratos operacionais, disponibilidade e escala dependem disso. Do mesmo modo, declarar “usaremos microsserviços” sem definir operação, propriedade dos dados e modelo de falha é apenas um rótulo.

16.4 Drivers: negócio, qualidade, restrições e riscos

Uma decisão sólida nasce de drivers explícitos:

  • objetivos de negócio: reduzir tempo de atendimento, monetizar conteúdo, integrar suporte e conhecimento;
  • requisitos funcionais: enviar documento, abrir chamado, pesquisar artigo, gerar resumo;
  • atributos de qualidade: resposta em até 300 ms, recuperação em até 30 minutos, isolamento por tenant;
  • restrições: equipe pequena, GLPI 10.0.15, implantação Docker, orçamento limitado, dados locais;
  • riscos: perda de arquivos, vazamento entre entidades, dependência de IA, duplicidade em integrações;
  • princípios: segurança por padrão, automação repetível, fonte única de verdade e observabilidade.

Confundir desejo com restrição reduz alternativas cedo demais. “Precisa ser Kafka” não é requisito de negócio. “Precisamos reter eventos por sete dias, permitir releitura independente por três consumidores e processar 20 mil eventos por segundo” descreve capacidades avaliáveis.

16.5 Qualidade não é adjetivo: é cenário

A ISO/IEC 25010:2023 atualizou o modelo de qualidade de produto para nove características [2]. O modelo é uma referência para organizar preocupações, não uma receita de arquitetura. Palavras como rápido, seguro e escalável permanecem ambíguas até receberem contexto e medida.

Um cenário de atributo de qualidade pode ser escrito com seis elementos:

  1. fonte do estímulo;
  2. estímulo;
  3. ambiente;
  4. artefato afetado;
  5. resposta esperada;
  6. medida da resposta.

Exemplo vago: “a pesquisa deve ser rápida”.

Exemplo verificável: “com 100 usuários concorrentes e índice contendo um milhão de documentos, 95% das pesquisas autenticadas devem responder em até 500 ms, medidos na borda da API, sem incluir o carregamento de imagens”.

Qualidade Cenário verificável Evidência
Desempenho p95 da busca inferior a 500 ms sob carga definida teste de carga e histograma
Disponibilidade falha de um worker não impede novos uploads teste de caos controlado
Recuperabilidade restaurar banco e arquivos em até 60 minutos exercício de restore
Segurança usuário de uma entidade não lê objeto de outra testes de autorização por objeto
Modificabilidade adicionar provedor de IA sem alterar o domínio teste de contrato da porta de IA
Observabilidade localizar todos os passos de um upload por trace_id consulta correlacionada

Disponibilidade composta

Quando uma requisição depende serialmente de vários serviços e todos precisam responder, a disponibilidade teórica — assumindo falhas independentes — pode ser aproximada por:

\[A_{total} = \prod_{i=1}^{n} A_i\]

Nessa expressão, A_i é a disponibilidade de cada dependência e n é a quantidade de dependências obrigatórias. Em leitura direta: multiplicam-se as disponibilidades dos componentes do caminho crítico.

Quatro dependências com 99,9% cada não produzem 99,9% ponta a ponta; produzem aproximadamente 99,6%. A hipótese de independência frequentemente não se sustenta — serviços podem compartilhar rede, cluster ou identidade —, mas a fórmula expõe um ponto importante: acrescentar chamadas obrigatórias pode reduzir a disponibilidade do fluxo.

16.6 Trade-off: melhorar uma qualidade pode pressionar outra

Replicar dados aumenta disponibilidade de leitura, mas cria custo e desafios de consistência. Criptografar e auditar fortalece segurança, mas consome recursos e adiciona operação. Cache reduz latência, mas introduz invalidação e possível desatualização. Microsserviços permitem deploy independente, mas trocam chamadas locais por rede e transações locais por coordenação distribuída.

Arquitetura madura não promete maximizar tudo. Ela prioriza qualidades conforme o negócio, registra o que foi sacrificado e define limites aceitáveis.

Uma avaliação leve pode seguir este roteiro:

  1. listar os três objetivos de negócio prioritários;
  2. escrever cenários para as cinco qualidades mais importantes;
  3. desenhar os fluxos críticos e suas dependências;
  4. identificar pontos únicos de falha, dados sensíveis e decisões irreversíveis;
  5. propor alternativas;
  6. registrar riscos, sensibilidades e trade-offs;
  7. selecionar experimentos para reduzir incerteza.

Se a dúvida é se o OCR atende ao volume, faça um benchmark com documentos reais. Se a dúvida é se o GLPI suporta a taxa necessária, teste em ambiente controlado. Um protótipo orientado à incerteza é mais valioso que uma prova de conceito feita apenas para confirmar a escolha preferida.

16.7 ADR: memória técnica executável

Um Architecture Decision Record registra uma decisão significativa e seu contexto. O ADR não deve ser um relatório retrospectivo que esconde incertezas. Deve informar o que motivou a escolha, quais opções foram consideradas e quais consequências precisarão ser administradas.

# ADR-004 — Processamento assíncrono de documentos

- Status: aceito
- Data: 2026-09-02
- Responsáveis: Plataforma e Operações

## Contexto
Extração, OCR e geração de embeddings variam de segundos a minutos.
A requisição HTTP não deve permanecer aberta durante o processamento.

## Drivers
- upload confirmado em até 2 segundos;
- retomada após reinício de worker;
- rastreabilidade por documento;
- equipe pequena e Redis já operado.

## Opções
1. processamento dentro da requisição;
2. Redis + RQ;
3. RabbitMQ + consumidor dedicado.

## Decisão
Adotar Redis + RQ nesta fase, com job idempotente e estado persistido no banco.

## Consequências
- exige monitorar fila, workers e jobs falhos;
- Redis não será a fonte oficial do estado do documento;
- migração para outro broker exigirá adaptar a porta de despacho;
- experiência do usuário passa a consultar um estado assíncrono.

## Verificação
- teste de reinício durante OCR;
- alerta para idade do job mais antigo;
- reconciliação diária entre documentos pendentes e jobs.

Status comuns: proposto, aceito, rejeitado, substituído e obsoleto. Não reescreva silenciosamente um ADR aceito quando a decisão mudar; crie outro registro e ligue os dois. Assim, o histórico explica por que o desenho atual existe.

16.8 Diagramas com propósito: C4 e visões complementares

O C4 organiza a comunicação em níveis de zoom: contexto do sistema, containers, componentes e código [3]. “Container”, nesse modelo, significa uma unidade executável ou armazenadora de dados, não necessariamente um container Docker. Para a maioria dos times, contexto e containers já respondem às perguntas mais importantes.

flowchart TB
    U["Leitor ou analista"] --> K["KnowledgeBasePro"]
    K --> G["GLPI"]
    K --> I["Provedor de IA"]
    K --> N["n8n e Slack"]

Esse diagrama de contexto não deve mostrar Redis, tabelas ou classes. Ele explica pessoas, sistema em foco e sistemas externos. Um diagrama de containers aproxima o zoom e mostra aplicações, workers, armazenamentos e protocolos. Um diagrama dinâmico explica uma sequência crítica. Um diagrama de deployment responde onde processos executam e quais limites de infraestrutura compartilham.

Boas regras de documentação visual:

  • título que declare propósito e escopo;
  • legenda ou notação compreensível;
  • responsabilidade dentro de cada elemento;
  • relação rotulada com protocolo ou intenção;
  • data ou versão quando o desenho muda;
  • nível de detalhe compatível com o público;
  • diagramas mantidos próximos do código quando possível.

16.9 Arquitetura sociotécnica

Sistemas refletem estruturas de comunicação. Se três equipes precisam coordenar cada alteração de um “microsserviço”, o serviço não é operacionalmente independente. Se ninguém é proprietário de uma API, compatibilidade e incidentes se tornam responsabilidade difusa. Se uma única pessoa conhece a integração com o GLPI, o principal risco não está no protocolo, mas na concentração de conhecimento.

Fronteiras técnicas e organizacionais precisam ser analisadas em conjunto:

  • quem decide o contrato;
  • quem atende incidentes;
  • quem paga o custo;
  • quem aprova mudanças;
  • quem responde pela qualidade dos dados;
  • como consumidores são avisados;
  • quais competências a equipe realmente possui.

Arquitetura que ignora a equipe desenha um sistema que existe apenas no papel.

16.10 Exemplo comparado: três respostas para o mesmo problema

Problema: após um artigo ser publicado, a plataforma precisa atualizar a busca e avisar o Slack.

Opção Vantagem Risco Quando faz sentido
chamadas diretas na mesma transação simples e consistente localmente Slack lento pode bloquear publicação protótipo e dependências não críticas
serviços chamados sincronamente componentes separados falha em cascata e latência acumulada resposta imediata realmente necessária
evento ArtigoPublicado desacopla tempo e consumidores consistência eventual e duplicidade tarefas posteriores independentes

A solução adequada pode combinar abordagens: confirmar a publicação e o registro do evento na transação local, atualizar a busca de forma assíncrona e tratar o Slack como efeito não crítico. A arquitetura não precisa escolher um estilo único para o sistema inteiro.

Notas Zettelkasten — Capítulo 16

ZK-060 — Arquitetura é decisão contextual

Uma tecnologia só se torna decisão arquitetural quando condiciona qualidades, fronteiras, custo de mudança ou operação. Diagramas registram uma visão da arquitetura, mas não a substituem.

Conecta-se a: ZK-061, ZK-062 e ZK-075.

ZK-061 — Qualidade precisa de cenário

“Rápido” e “seguro” não orientam engenharia. Um cenário de qualidade define estímulo, ambiente, resposta e medida, permitindo testar a decisão.

Conecta-se a: ZK-060, ZK-073 e ZK-080.

ZK-062 — ADR preserva contexto, não apenas escolha

Sem contexto e alternativas, um ADR vira ata. Seu valor está em tornar explícitas as forças que justificaram a decisão e as consequências que precisam ser verificadas.

Conecta-se a: ZK-060, ZK-065 e ZK-077.

Exercícios — Capítulo 16

  1. Transforme “o sistema deve ser escalável” em dois cenários mensuráveis: leitura e processamento de PDF.
  2. Liste três stakeholders da integração GLPI–n8n e uma preocupação diferente de cada um.
  3. Escreva um ADR comparando storage local persistente e storage compatível com S3.
  4. Explique por que inserir mais uma dependência síncrona pode reduzir a disponibilidade ponta a ponta.
  5. Desenhe uma visão de contexto sem incluir componentes internos.
  6. Identifique uma decisão tecnicamente correta que seria inadequada para uma equipe de duas pessoas.

Capítulo 17 — Camadas, módulos e fronteiras

Objetivos de aprendizagem

Ao concluir este capítulo, você deverá ser capaz de:

  • distinguir separação lógica de separação física;
  • definir responsabilidades e dependências entre camadas;
  • proteger regras de negócio de frameworks e integrações;
  • comparar arquitetura em camadas, hexagonal, Clean Architecture e vertical slices;
  • estruturar um monólito modular em Flask;
  • reconhecer transações e modelos que atravessam fronteiras indevidamente.

17.1 Separar responsabilidade não significa distribuir processos

Camada, módulo, pacote, componente, serviço e container descrevem coisas diferentes. Uma aplicação pode possuir ótima separação lógica e executar em um único processo. Também pode ter vinte serviços implantados separadamente e continuar fortemente acoplada por banco, deploy e conhecimento.

Conceito Natureza Exemplo
Camada agrupamento por responsabilidade técnica apresentação, aplicação, domínio, persistência
Módulo fronteira de capacidade ou domínio documentos, usuários, chamados
Pacote unidade de organização do código app/documents/
Processo unidade em execução Gunicorn, worker RQ
Container de implantação ambiente isolado de processo container Docker da aplicação
Serviço capacidade acessível por contrato API de tickets

Separar processos aumenta isolamento, mas introduz rede, autenticação entre serviços, telemetria distribuída, versionamento e consistência parcial. Começar pela modularidade dentro de um processo permite aprender as fronteiras antes de pagar esse custo.

17.2 A arquitetura em camadas

Uma decomposição comum inclui:

  • apresentação: HTTP, formulários, serialização e resposta;
  • aplicação: coordenação de casos de uso;
  • domínio: regras, invariantes e linguagem do negócio;
  • persistência: mapeamento e consultas;
  • infraestrutura: banco, fila, e-mail, storage e APIs externas.

O valor não está nos nomes, mas na direção das dependências. Um controller pode depender de um caso de uso; o caso de uso pode depender de uma interface de repositório; a implementação PostgreSQL satisfaz essa interface. O domínio não precisa importar Flask, Redis ou a SDK do GLPI.

flowchart TB
    P["Apresentação"] --> A["Aplicação"]
    A --> D["Domínio"]
    I["Infraestrutura"] --> A
    I --> D

A seta representa dependência de código, não fluxo temporal. Uma requisição entra pela apresentação e termina no banco, mas a regra central não deve depender do detalhe externo.

17.3 Responsabilidades por camada

Apresentação

A borda HTTP deve:

  • autenticar ou receber a identidade validada;
  • extrair parâmetros e corpo;
  • realizar validações sintáticas;
  • chamar um caso de uso;
  • mapear resultado ou erro para HTTP;
  • não decidir regras como “somente gestores aprovam publicação”.
@documents_bp.post("/documents")
def create_document():
    command = CreateDocumentCommand(
        actor_id=current_user.id,
        tenant_id=current_user.tenant_id,
        title=request.form["title"],
        upload=request.files["file"],
    )
    result = create_document_handler.handle(command)
    return {"id": result.id, "status": result.status}, 202

Aplicação

A camada de aplicação coordena o caso de uso: carrega entidades, verifica política, chama portas, controla a transação e produz o resultado. Ela descreve o que acontece, não como PostgreSQL ou Redis executam.

class CreateDocumentHandler:
    def __init__(self, documents, storage, jobs, unit_of_work):
        self.documents = documents
        self.storage = storage
        self.jobs = jobs
        self.uow = unit_of_work

    def handle(self, command):
        document = Document.receive(
            actor_id=command.actor_id,
            tenant_id=command.tenant_id,
            title=command.title,
        )
        stored = self.storage.save(command.upload, document.id)
        document.attach(stored.key, stored.sha256)
        self.documents.add(document)
        self.uow.commit()
        self.jobs.enqueue(document.id)
        return document

Esse exemplo ainda possui uma janela de falha entre commit() e enqueue(). O capítulo 22 mostrará como o Outbox Pattern fecha essa lacuna sem fingir que banco e broker participam de uma transação simples.

Domínio

O domínio protege invariantes: transições válidas de estado, propriedade, classificação, limites e decisões que possuem significado de negócio.

class Document:
    ALLOWED = {
        "received": {"stored", "rejected"},
        "stored": {"queued"},
        "queued": {"processing", "failed"},
        "processing": {"indexed", "failed"},
    }

    def transition_to(self, target):
        if target not in self.ALLOWED.get(self.status, set()):
            raise InvalidDocumentTransition(self.status, target)
        self.status = target

Colocar essa regra apenas em botões da interface permite que API, job ou script violem o processo.

Infraestrutura e persistência

Infraestrutura implementa detalhes: SQLAlchemy, filesystem, S3, Redis, GLPI e SMTP. Persistência traduz entre o modelo de domínio e armazenamento. Essa separação não significa criar uma classe para cada linha de código; significa impedir que detalhes instáveis contaminem todas as regras.

17.4 Modelos diferentes para responsabilidades diferentes

Reutilizar o mesmo objeto como formulário, entidade de domínio, registro ORM, resposta pública e mensagem de evento parece econômico, mas conecta ciclos de mudança diferentes.

Modelo Finalidade Pode conter
Request DTO validar entrada campos recebidos e regras sintáticas
Entidade de domínio preservar invariantes comportamento e estado do negócio
Modelo de persistência armazenar colunas, chaves e detalhes ORM
Response DTO contrato público campos autorizados e estáveis
Evento de integração fato publicado schema versionado e metadados

Nem toda aplicação precisa de cinco classes para cada conceito. O custo da tradução se justifica quando as responsabilidades realmente divergem. Em CRUD simples e interno, o mapeamento pode ser mais direto. Em API pública, multi-tenant ou integração crítica, a separação reduz vazamento de campos e mudanças acidentais de contrato.

17.5 Transações pertencem ao caso de uso

Se cada repositório realiza commit sozinho, um caso de uso que altera documento, permissão e auditoria pode persistir apenas parte do estado. A unidade de trabalho delimita a transação:

with unit_of_work as uow:
    document = uow.documents.get(document_id)
    document.publish(actor)
    uow.audit.add(AuditEntry.from_document(document, actor))
    uow.outbox.add(DocumentPublished.from_document(document))
    uow.commit()

A transação local oferece atomicidade dentro do mesmo banco. Ela não torna atômica uma chamada ao Slack ou ao GLPI. Manter transação aberta enquanto se chama uma API externa também é perigoso: locks duram mais, a disponibilidade externa passa a controlar o banco local e o resultado remoto pode ser incerto após timeout.

17.6 Monólito modular: uma arquitetura, não uma desculpa

Um monólito modular possui uma unidade de implantação, mas fronteiras internas explícitas. Cada módulo controla regras e acesso aos seus dados. Integrações internas ocorrem por interfaces ou eventos internos, não por importações arbitrárias.

app/
├── documents/
│   ├── domain/
│   ├── application/
│   ├── adapters/
│   └── web/
├── identity/
├── knowledge/
├── tickets/
├── shared/
└── bootstrap.py

Regras práticas:

  • um módulo não consulta diretamente tabelas privadas de outro;
  • dependências permitidas são testadas;
  • eventos internos usam nomes de negócio;
  • código compartilhado contém mecanismos, não um depósito de regras;
  • cada módulo possui responsável e linguagem clara;
  • extração futura é possível, mas não obrigatória.

O anti-padrão “monólito distribuído” ocorre quando módulos foram separados em processos, mas continuam exigindo deploy coordenado, banco compartilhado e chamadas síncronas em cadeia. Ele combina custo distribuído com dependência monolítica.

17.7 Layered, Hexagonal, Clean e Vertical Slice

Estilo Eixo principal Força Risco de uso mecânico
Em camadas responsabilidade técnica familiaridade e organização camada de serviço genérica e domínio anêmico
Hexagonal portas e adaptadores isola domínio de tecnologia interfaces artificiais sem alternativa real
Clean Architecture dependências apontam ao núcleo testabilidade das regras excesso de mapeamentos e pastas
Vertical Slice caso de uso ponta a ponta mudança localizada por funcionalidade duplicação não controlada
Modular monolith fronteira de domínio simplicidade operacional disciplina interna insuficiente

Arquitetura Hexagonal e Clean Architecture compartilham a ideia de proteger políticas centrais de mecanismos externos. Vertical Slice organiza o código por caso de uso em vez de atravessar pastas genéricas para cada alteração. Essas abordagens podem coexistir: módulos por domínio, casos de uso verticais e adaptadores nas bordas.

17.8 Quando camadas atrapalham

Sintomas comuns:

  • controller chama service, que apenas chama repository, sem decisão;
  • uma alteração simples exige editar cinco objetos de passagem;
  • domínio é um conjunto de getters e setters;
  • pasta utils concentra comportamento sem dono;
  • toda exceção vira 500 ou booleano;
  • módulos importam modelos ORM uns dos outros;
  • a regra existe duplicada em controller, worker e script;
  • “Clean Architecture” significa apenas nomes de diretórios.

Arquitetura deve reduzir o custo total de mudança. Se a separação não protege uma variação, contrato ou regra, ela pode ser cerimônia. A pergunta correta não é “quantas camadas usar?”, mas “quais mudanças queremos manter independentes?”.

17.9 Caso aplicado: integração com GLPI sem contaminar o domínio

O módulo de tickets da plataforma pode definir uma porta própria:

from typing import Protocol

class TicketGateway(Protocol):
    def open_ticket(self, command: "OpenTicket") -> "ExternalTicket": ...
    def attach_document(self, ticket_id: str, document: "Attachment") -> None: ...

O adapter do GLPI conhece App-Token, sessão, payload, categoria ITIL, entidade, criação de Document e vínculo por Document_Item. O caso de uso conhece apenas a intenção de abrir chamado e anexar evidência. Um adapter falso permite testar o domínio sem GLPI disponível.

Isso não elimina falhas externas. O módulo ainda precisa representar estados como pending, sent, partially_attached e failed, além de guardar o identificador remoto. A fronteira reduz acoplamento de código; confiabilidade exige desenho de processo.

Notas Zettelkasten — Capítulo 17

ZK-063 — Separação lógica precede distribuição

Processos separados não garantem módulos independentes. Fronteiras de responsabilidade, dados e contrato devem existir antes que a rede seja introduzida.

Conecta-se a: ZK-064, ZK-075 e ZK-076.

ZK-064 — Modelo compartilhado cria acoplamento oculto

Request, domínio, persistência, resposta e evento mudam por razões diferentes. Compartilhar o mesmo modelo economiza tradução hoje e pode transformar qualquer mudança em quebra sistêmica amanhã.

Conecta-se a: ZK-067, ZK-070 e ZK-073.

ZK-065 — Transação local não atravessa a rede

Banco, broker e API externa não formam automaticamente uma unidade atômica. Processos confiáveis registram intenção, repetem com segurança e reconciliam divergências.

Conecta-se a: ZK-062, ZK-076 e ZK-079.

Exercícios — Capítulo 17

  1. Separe um caso de uso de abertura de chamado em apresentação, aplicação, domínio e infraestrutura.
  2. Explique por que um container Docker não equivale a um container do C4.
  3. Mostre uma janela de falha ao realizar commit e depois publicar uma mensagem.
  4. Defina três regras para impedir dependências indevidas em um monólito modular.
  5. Compare DTO público e modelo ORM usando um campo sensível como exemplo.
  6. Escolha entre camadas e vertical slice para um CRUD pequeno e justifique sem usar “boa prática” como argumento.

Capítulo 18 — Acoplamento, coesão, SOLID e desenho modular

Objetivos de aprendizagem

Ao concluir este capítulo, você deverá ser capaz de:

  • reconhecer acoplamento estrutural, temporal, semântico e de dados;
  • avaliar coesão pela razão de mudança de um módulo;
  • aplicar os cinco princípios SOLID sem transformá-los em dogmas;
  • usar inversão de dependência e injeção de dependência corretamente;
  • proteger limites modulares com testes e regras automatizadas;
  • analisar um ecossistema GLPI, Slack e n8n como dependências evolutivas.

18.1 Acoplamento é o custo de mudar em conjunto

Dois elementos estão acoplados quando uma mudança em um exige conhecimento ou mudança no outro. Todo sistema útil possui acoplamento: uma aplicação precisa conversar com banco, bibliotecas e serviços. A meta não é zerá-lo, mas tornar dependências intencionais, estáveis, observáveis e proporcionais ao valor.

Acoplamento aparece de formas diferentes:

Tipo Exemplo Consequência
Estrutural módulo importa classe interna de outro refatoração propaga
De dados dois sistemas escrevem as mesmas tabelas schema vira contrato oculto
Temporal consumidor precisa estar online junto do produtor falha se propaga no tempo
Semântico campo status=5 possui significado não documentado interpretação diverge
De implantação serviços sempre precisam subir juntos deploy independente é ilusório
Operacional todos disputam o mesmo pool, fila ou volume falha e saturação se propagam
Organizacional mudança depende de aprovações de vários times lead time aumenta

Uma API reduz acoplamento estrutural, mas pode manter acoplamento temporal e semântico. Uma fila remove a exigência de simultaneidade, mas cria dependência do schema da mensagem. “Desacoplar” nunca é uma ação genérica; é preciso dizer qual dimensão foi reduzida e qual nova dependência surgiu.

18.2 Coesão é foco de responsabilidade

Um módulo coeso reúne comportamentos que mudam pela mesma razão e colaboram para uma capacidade clara. O módulo documents pode receber, validar, classificar e controlar o ciclo de vida de documentos. Colocar envio de e-mail, cálculo de folha e integração fiscal ali reduziria coesão, mesmo que tecnicamente seja possível.

Uma heurística melhor que contar classes é perguntar:

  • o módulo pode ser descrito por uma frase de negócio sem usar “e também” muitas vezes?
  • seus dados e regras possuem o mesmo proprietário?
  • uma mudança regulatória localizada afeta apenas esse módulo?
  • o módulo pode ser testado por comportamentos próprios?
  • o nome explica a capacidade ou é genérico como common, manager e utils?

Alta coesão e baixo acoplamento trabalham juntos. Dividir cada função em um serviço separado pode reduzir coesão, porque partes de uma única regra passam a conversar pela rede.

18.3 Acoplamento aferente, eferente e instabilidade

No nível de módulos ou pacotes, C_a representa dependências que chegam ao módulo; C_e, dependências que saem dele. Uma medida didática de instabilidade é:

\[I = \frac{C_e}{C_a + C_e}\]

Nessa expressão, I varia de 0 a 1. Próximo de 0, muitos dependem do módulo e ele depende de poucos; próximo de 1, o módulo depende de muitos e poucos dependem dele. A métrica não mede qualidade sozinha. Um adapter de borda pode ser deliberadamente instável; um núcleo amplamente reutilizado deve mudar com cuidado.

O erro é transformar a fórmula em meta. Dependências têm pesos diferentes, importações podem ser apenas de tipos e a semântica não aparece no grafo. Use a medida como pergunta de investigação, não como nota automática.

18.4 SOLID: cinco pressões de design

SOLID reúne princípios para tornar mudanças mais localizadas em design orientado a objetos. Eles não exigem uma interface para cada classe nem uma hierarquia extensa.

S — Single Responsibility Principle

Uma unidade deve possuir uma razão coerente para mudar. A formulação está ligada a atores e políticas, não ao número de métodos.

class TicketReportService:
    def calculate(self, tickets): ...
    def render_pdf(self, report): ...
    def send_email(self, pdf): ...
    def renew_glpi_session(self): ...

A classe mistura análise, apresentação, entrega e integração. Uma divisão útil não precisa criar dezenas de classes; pode separar o cálculo puro de adapters de PDF, e-mail e GLPI.

O — Open/Closed Principle

Uma unidade deve permitir extensão sem exigir modificação constante do núcleo. Strategy funciona quando a variação é real:

class PriorityPolicy(Protocol):
    def calculate(self, ticket: Ticket) -> Priority: ...

class ImpactUrgencyPolicy:
    def calculate(self, ticket):
        return MATRIX[ticket.impact][ticket.urgency]

class VipCustomerPolicy:
    def calculate(self, ticket):
        return Priority.CRITICAL if ticket.customer.is_vip else ticket.priority

Se há somente uma regra estável, criar plugin system antecipa complexidade. O princípio protege pontos de variação conhecidos; não exige prever todos os futuros.

L — Liskov Substitution Principle

Uma implementação deve poder substituir a abstração sem surpreender o consumidor. Não basta ter os mesmos métodos. Pré-condições, pós-condições, erros e efeitos precisam respeitar o contrato.

Se Storage.save() promete persistência durável e uma implementação grava apenas em diretório temporário, ela compila, mas viola substituibilidade. Se TicketGateway.open() às vezes devolve None sem contrato, consumidores passam a conhecer a implementação.

I — Interface Segregation Principle

Consumidores não devem depender de operações que não usam. Uma interface CorporateSystem com 40 métodos força um adapter somente de leitura a implementar operações vazias. Portas orientadas a capacidades — TicketReader, TicketWriter, AttachmentPublisher — tornam dependências explícitas.

Fragmentar demais também prejudica. Interfaces devem refletir papéis coesos do consumidor, não um método por arquivo.

D — Dependency Inversion Principle

Políticas de alto nível não devem depender diretamente de detalhes voláteis; ambos dependem de abstrações adequadas ao núcleo. A interface pertence conceitualmente a quem a consome.

class SummarizeDocument:
    def __init__(self, models: LanguageModelPort):
        self.models = models

    def execute(self, document):
        prompt = build_summary_prompt(document)
        return self.models.complete(prompt, timeout_seconds=45)

O caso de uso não conhece Ollama, OpenAI ou Gemini. Cada adapter traduz autenticação, timeout, formato e erro. A abstração não elimina diferenças de capacidade: contexto máximo, preço e política de dados ainda precisam estar no desenho.

18.5 Inversão de dependência não é injeção de dependência

Inversão de dependência é um princípio sobre direção de código. Injeção de dependência é uma técnica para fornecer colaboradores de fora. Um container de DI pode automatizar a montagem, mas não corrige abstrações ruins.

# composition root
ticket_gateway = GlpiTicketAdapter(http_client, glpi_settings)
document_repo = SqlAlchemyDocumentRepository(session)
handler = OpenSupportTicket(document_repo, ticket_gateway)

O composition root é o lugar em que detalhes concretos são conectados. Espalhar GlpiTicketAdapter() por casos de uso reintroduz dependência. Usar um service locator global oculta requisitos e dificulta testes.

18.6 Modularidade verificável

Convenções de pastas não impedem importações indevidas. Fronteiras podem ser protegidas por:

  • testes de arquitetura que validam dependências permitidas;
  • APIs internas explícitas por módulo;
  • revisão de código com mapa de responsabilidades;
  • proprietários de módulos;
  • linters para ciclos de importação;
  • testes de contrato em adapters;
  • esquema de banco com propriedade documentada;
  • métricas de mudança conjunta no repositório.

Um teste simples pode falhar se documents.domain importar Flask, SQLAlchemy ou um adapter:

FORBIDDEN = {"flask", "sqlalchemy", "redis", "requests"}

def test_domain_has_no_framework_dependencies(import_graph):
    imported = import_graph.dependencies_of("app.documents.domain")
    assert not (imported & FORBIDDEN)

A regra precisa servir ao desenho. Proibir qualquer dependência externa sem motivo apenas cria wrappers vazios.

18.7 Caso aplicado: GLPI, n8n e Slack

Considere o fluxo: uma mensagem do Slack inicia um workflow no n8n; o workflow abre um ticket no GLPI; um anexo é criado como Document e depois vinculado ao ticket; a resposta retorna ao canal.

Acoplamentos inevitáveis:

  • identidade do usuário entre Slack e GLPI;
  • categoria ITIL e entidade de destino;
  • contrato REST e ciclo de sessão do GLPI;
  • limites e política de arquivos;
  • correlação entre mensagem, execução e ticket.

Acoplamentos que podem ser reduzidos:

  • mapear categorias em configuração, não em vários nodes;
  • centralizar sessão e erros no adapter GLPI;
  • definir um comando interno OpenTicketRequest independente do payload do Slack;
  • persistir o estado da solicitação para retomada;
  • usar uma chave idempotente derivada do evento original;
  • registrar ticket remoto e vínculo do documento separadamente.

O resultado não é “desacoplado” em sentido absoluto. Ele possui dependências reconhecidas, com tradução, propriedade e recuperação definidas.

18.8 Sinais de abstração prematura

  • interface com uma única implementação e nenhuma fronteira relevante;
  • factory que apenas chama um construtor;
  • repository genérico que esconde consultas importantes;
  • BaseService herdado por módulos sem comportamento comum;
  • evento publicado para chamar exatamente um método no mesmo processo;
  • DTO idêntico ao ORM copiado mecanicamente;
  • uso de design pattern para demonstrar conhecimento, não resolver variação.

Abstração tem custo de descoberta. O leitor precisa navegar entre arquivos e traduzir conceitos. A abstração é boa quando esse custo é menor que o custo de acoplamento que ela remove.

Notas Zettelkasten — Capítulo 18

ZK-066 — Acoplamento possui dimensões

Trocar chamada direta por API reduz dependência de código, mas pode manter dependência temporal, semântica e operacional. Toda alegação de desacoplamento deve indicar a dimensão afetada.

Conecta-se a: ZK-063, ZK-069 e ZK-078.

ZK-067 — Coesão acompanha a razão de mudança

Elementos pertencem ao mesmo módulo quando colaboram para uma capacidade e mudam por forças semelhantes. Tamanho pequeno não garante coesão.

Conecta-se a: ZK-064, ZK-068 e ZK-075.

ZK-068 — SOLID protege variações concretas

Princípios orientam decisões; não prescrevem quantidade de classes. Aplicação mecânica cria abstrações que aumentam o custo sem proteger uma mudança real.

Conecta-se a: ZK-067, ZK-081 e ZK-083.

Exercícios — Capítulo 18

  1. Classifique cinco acoplamentos do fluxo Slack–n8n–GLPI.
  2. Dê um exemplo em que fila reduz acoplamento temporal, mas aumenta acoplamento semântico.
  3. Explique uma violação de Liskov em duas implementações de storage.
  4. Reescreva uma interface genérica de GLPI em três capacidades coesas.
  5. Calcule I para um módulo com C_e=6 e C_a=2 e explique por que o valor não é um veredito.
  6. Identifique uma abstração prematura em um projeto seu e descreva a simplificação.

Capítulo 19 — Integração entre sistemas

Objetivos de aprendizagem

Ao concluir este capítulo, você deverá ser capaz de:

  • tratar integração como processo de negócio e não apenas transporte;
  • comparar APIs, arquivos, webhooks, mensageria, CDC e banco compartilhado;
  • projetar contratos, idempotência, retry, reconciliação e observabilidade;
  • integrar sistemas legados por uma Anti-Corruption Layer;
  • documentar ownership, segurança e tratamento de falhas;
  • diagnosticar integrações parcialmente concluídas.

19.1 Integração é acordo entre sistemas autônomos

Uma integração conecta sistemas que possuem modelos, ciclos de mudança, proprietários e restrições diferentes. Enviar JSON com sucesso não prova que o processo terminou. O destino pode aceitar a requisição e falhar depois; o timeout pode ocultar uma operação concluída; o dado pode ser sintaticamente válido e semanticamente incorreto.

Uma integração completa define:

  • intenção de negócio;
  • fonte oficial de cada dado;
  • identidade e autorização;
  • contrato e versionamento;
  • transporte e formato;
  • garantias de entrega e ordem;
  • timeout e repetição;
  • tratamento de duplicidade;
  • confirmação de conclusão;
  • auditoria e correlação;
  • reconciliação e responsabilidade operacional.

Princípio: transporte move bytes; integração preserva significado e resultado através de fronteiras.

19.2 Fonte de verdade e propriedade

Antes do endpoint, responda: quem pode criar, corrigir e excluir o dado? O Active Directory pode ser fonte de identidade, o GLPI de tickets e a plataforma de documentos. Replicar dados para consulta não transfere automaticamente a propriedade.

Dado Sistema de registro Cópias permitidas Regra de conflito
usuário corporativo AD/IdP GLPI e plataforma identidade vem do IdP
ticket GLPI portal e analytics estado operacional vem do GLPI
documento original plataforma storage e backup hash identifica conteúdo
métrica agregada data warehouse Metabase recomputável a partir da origem

Sem essa definição, sincronização bidirecional pode criar ciclos: A corrige B, B interpreta como mudança e corrige A novamente.

19.3 Estilos de integração comparados

Estilo Latência Acoplamento temporal Volume Ponto forte Risco principal
REST/HTTP baixa alto médio interoperabilidade falha em cascata
SOAP baixa alto médio contrato e padrões corporativos maduros complexidade WS-*
gRPC muito baixa alto alto IDL, streaming e eficiência interna browser e depuração humana
GraphQL baixa alto variável consulta dirigida pelo cliente custo, autorização e N+1
arquivo em lote alta baixo alto legado e grandes volumes atraso e reprocessamento
webhook baixa parcial médio notificação por evento autenticidade e repetição
broker de mensagens variável baixo alto buffer e fan-out operação e consistência eventual
CDC baixa baixo alto captura de mudanças sem alterar origem schema físico vaza
banco compartilhado baixa alto alto implementação inicial rápida acoplamento máximo de dados

REST não é automaticamente melhor que arquivo. Uma conciliação financeira noturna de milhões de linhas pode usar lote com checksum, manifesto e reconciliação. Uma consulta interativa de estoque precisa de resposta síncrona. A decisão nasce do tempo, volume, garantia e autonomia.

19.4 Síncrono e assíncrono

Na comunicação síncrona, o chamador aguarda uma resposta. O modelo é simples e apropriado quando o resultado é necessário para continuar. Entretanto, timeout, pool de conexões e dependências em cadeia tornam a falha visível ao usuário.

Na comunicação assíncrona, o produtor registra trabalho ou publica um fato e continua. Isso absorve picos e permite que consumidores operem em ritmos diferentes, mas exige estado de processo, idempotência, monitoramento de backlog e experiência de usuário para “ainda processando”.

Um fluxo pode combinar ambos:

  1. POST /documents valida e persiste o recebimento;
  2. retorna 202 Accepted com URL de status;
  3. worker extrai conteúdo;
  4. evento atualiza busca;
  5. cliente consulta ou recebe notificação de conclusão.

202 não significa sucesso final. Significa que o pedido foi aceito para processamento. A API deve fornecer como acompanhar falha, conclusão ou expiração.

19.5 Contrato: sintaxe, semântica e política

Um contrato de integração contém mais que campos:

  • nomes, tipos, formatos e obrigatoriedade;
  • significado e unidade de medida;
  • identidade única e regras de deduplicação;
  • estados e transições válidas;
  • timezone e precisão temporal;
  • compatibilidade e depreciação;
  • erros e possibilidade de repetição;
  • limites, tamanho e paginação;
  • autenticação, escopos e classificação de dados;
  • exemplos válidos e inválidos.

JSON Schema Draft 2020-12 fornece uma linguagem para estrutura e validação de documentos JSON [4]. OpenAPI descreve contratos HTTP; AsyncAPI descreve APIs orientadas a mensagens. O schema não substitui a semântica: status: 5 pode ser válido e ainda ser incompreensível fora do GLPI.

19.6 Modelo canônico ou tradução ponto a ponto

Com n sistemas integrados diretamente entre si, o número máximo de conexões bilaterais cresce aproximadamente como:

\[L = \frac{n(n-1)}{2}\]

Nessa expressão, L é a quantidade de ligações e n é a quantidade de sistemas. Em leitura direta: cada novo sistema pode precisar conhecer vários formatos existentes.

Um modelo canônico reduz traduções ao definir uma linguagem comum. Porém, um modelo corporativo universal pode se tornar grande, lento para evoluir e controlado por um comitê distante dos domínios. Uma alternativa equilibrada é usar contratos canônicos por contexto ou por evento, mantendo tradução nas fronteiras.

19.7 Anti-Corruption Layer

Uma Anti-Corruption Layer traduz o modelo externo para o modelo interno e impede que conceitos legados se espalhem. Ela pode incluir facade, adapter, mapeadores e política de resiliência [5].

Exemplo: o GLPI representa status, categoria, entidade e anexos conforme seu modelo. A plataforma pode trabalhar com SupportRequest, KnowledgeDocument e Attachment. O adapter converte intenções internas em chamadas GLPI e converte respostas externas em resultados estáveis.

Cuidados:

  • a camada pode virar gargalo ou ponto único de falha;
  • traduções precisam de testes com contratos reais;
  • valores desconhecidos não devem ser silenciosamente descartados;
  • logs não podem expor tokens ou anexos;
  • mudanças do legado precisam de inventário e alerta.

19.8 Idempotência: repetir sem duplicar efeito

Redes falham de modo ambíguo. O cliente envia a solicitação, o servidor conclui, mas a resposta se perde. Repetir pode criar dois tickets ou cobrar duas vezes.

Uma operação idempotente produz o mesmo efeito observável após repetições equivalentes. Estratégias:

  • chave idempotente única por intenção;
  • restrição UNIQUE no banco;
  • registro de requisição e resultado;
  • upsert quando a semântica permite;
  • hash de conteúdo com escopo e janela definidos;
  • consumidor com tabela de mensagens processadas.
CREATE TABLE integration_request (
    idempotency_key varchar(120) PRIMARY KEY,
    operation varchar(80) NOT NULL,
    request_hash char(64) NOT NULL,
    status varchar(30) NOT NULL,
    response_json jsonb,
    created_at timestamptz NOT NULL DEFAULT now()
);

A mesma chave com payload diferente deve ser rejeitada. Deduplicar apenas pelo título do chamado pode unir solicitações legítimas. Idempotência precisa de identidade, escopo, retenção e política de conflito.

19.9 Timeout, retry, backoff e jitter

Sem timeout, threads e conexões podem permanecer presas indefinidamente. Com retry indiscriminado, uma dependência degradada recebe ainda mais carga. Repita somente falhas transitórias e somente operações seguras ou protegidas por idempotência.

Uma política de backoff exponencial pode ser expressa como:

\[d_k = \min(d_{max}, d_0 \times 2^k) + J\]

Nessa expressão, d_k é o atraso da tentativa k, d_0 é o atraso inicial, d_max é o limite e J é uma variação aleatória. O jitter reduz a chance de muitos clientes repetirem simultaneamente. Defina também máximo de tentativas e orçamento total de tempo.

Falha Repetir? Observação
timeout de leitura talvez resultado remoto pode ter ocorrido
429 Too Many Requests sim, controlado respeitar Retry-After
503 Service Unavailable sim, limitado combinar com circuit breaker
400 Bad Request não corrigir contrato ou dado
401 Unauthorized após renovar credencial impedir loop de autenticação
403 Forbidden não automaticamente permissão é problema permanente
404 Not Found depende pode haver consistência eventual

19.10 Webhooks seguros e recuperáveis

Webhook é uma requisição iniciada pelo produtor após um evento. O receptor deve assumir repetição, atraso e ordem diferente.

Checklist:

  • HTTPS;
  • assinatura do corpo com segredo rotacionável ou autenticação equivalente;
  • timestamp e janela contra replay;
  • leitura dos bytes brutos antes de validar assinatura;
  • identificador único do evento;
  • resposta rápida após persistir recebimento;
  • processamento assíncrono;
  • idempotência;
  • política de retry do emissor conhecida;
  • endpoint de replay ou reconciliação;
  • segredo fora do código e dos logs.

Permitir IP não substitui assinatura: proxies e mudanças de infraestrutura tornam a origem difícil de representar somente por endereço.

19.11 Arquivo em lote feito profissionalmente

Integração por arquivo pode ser robusta quando possui protocolo operacional:

  • nome imutável e identificador de lote;
  • escrita em arquivo temporário e rename atômico;
  • manifesto com quantidade, tamanho e checksum;
  • codificação e delimitador explícitos;
  • timezone e formato de datas;
  • pasta de entrada, processados, rejeitados e quarentena;
  • reprocessamento idempotente;
  • relatório de linhas rejeitadas;
  • retenção e criptografia;
  • reconciliação de totais.

Um CSV solto em pasta compartilhada é frágil. Um lote com contrato, confirmação e reconciliação é um protocolo de integração.

19.12 CDC, ETL e acesso direto ao banco

Change Data Capture observa alterações no log ou mecanismo do banco e publica mudanças. É útil para analytics, cache, migração e integração de legado, mas tende a expor schema físico e eventos de baixa semântica: row_updated não explica por que um chamado mudou.

ETL extrai, transforma e carrega; ELT carrega dados brutos e transforma no destino. Ambos atendem integração analítica, não substituem automaticamente transações operacionais.

Consultar banco do GLPI em réplica somente leitura para Metabase pode ser apropriado. Escrever diretamente nas tabelas contorna validações, plugins, auditoria e compatibilidade da aplicação. Para operações, prefira API ou mecanismo suportado; para analytics, documente schema, carga, privacidade e impacto.

19.13 Reconciliação: a defesa contra falhas silenciosas

Mesmo com retry, mensagens e APIs podem divergir. Reconciliação compara fontes:

  • documentos queued sem job ativo;
  • tickets locais sem external_ticket_id;
  • anexos criados no GLPI sem Document_Item;
  • eventos outbox não publicados;
  • mensagens na DLQ;
  • contagens por lote entre origem e destino.

O objetivo não é apenas alertar; é permitir reparar com segurança. Sistemas corporativos confiáveis tratam reconciliação como funcionalidade, não como script emergencial.

19.14 Estudo aplicado: Slack → n8n → GLPI

sequenceDiagram
    actor U as Usuário
    participant S as Slack
    participant N as n8n
    participant G as GLPI
    participant A as Auditoria
    U->>S: Solicita atendimento
    S->>N: Evento assinado
    N->>A: Registra intenção
    N->>G: Cria ticket com chave única
    G-->>N: Ticket e status
    N->>G: Cria e vincula documento
    N->>A: Registra resultado parcial ou final
    N-->>S: Confirma ticket

Pontos que precisam de estado:

  1. evento do Slack validado;
  2. ticket solicitado;
  3. ticket criado;
  4. documento criado;
  5. documento vinculado;
  6. resposta publicada.

Se a etapa 4 funciona e a 5 falha, repetir o fluxo inteiro não deve criar outro ticket. A retomada começa do último marco confirmado. Um correlation_id comum permite ligar evento, execução, ticket, documento e mensagem de retorno.

Notas Zettelkasten — Capítulo 19

ZK-069 — Integração preserva intenção

Entrega de bytes é condição necessária, não suficiente. O processo precisa manter significado, propriedade, confirmação e possibilidade de reparo.

Conecta-se a: ZK-066, ZK-070 e ZK-080.

ZK-070 — Contrato possui semântica e política

Schema valida forma. O contrato completo define significado, identidade, erros, compatibilidade, segurança e limites operacionais.

Conecta-se a: ZK-064, ZK-069 e ZK-073.

ZK-071 — Reconciliação fecha o ciclo

Retry trata falhas conhecidas; reconciliação encontra divergências que escaparam do caminho normal. Sem ela, a integração pode falhar silenciosamente.

Conecta-se a: ZK-065, ZK-079 e ZK-080.

Exercícios — Capítulo 19

  1. Defina a fonte de verdade de usuários, tickets, documentos e métricas em um ecossistema corporativo.
  2. Compare webhook e polling para acompanhar alteração de ticket.
  3. Projete uma chave idempotente para abertura de chamado iniciada pelo Slack.
  4. Explique por que timeout não prova que a operação remota falhou.
  5. Crie uma matriz de retry para cinco códigos HTTP.
  6. Desenhe uma reconciliação que encontre anexos criados, mas não vinculados ao ticket.

Capítulo 20 — APIs corporativas como produtos e contratos

Objetivos de aprendizagem

Ao concluir este capítulo, você deverá ser capaz de:

  • desenhar uma API a partir de recursos, capacidades e jornadas;
  • aplicar corretamente a semântica de métodos e códigos HTTP;
  • especificar contratos com OpenAPI e schemas;
  • planejar compatibilidade, versionamento e depreciação;
  • comparar REST, GraphQL, gRPC e SOAP;
  • incorporar autorização por objeto, limites e observabilidade desde o desenho.

20.1 Uma API corporativa possui consumidores e ciclo de vida

API não é apenas um conjunto de rotas. É uma interface consumida por software, com expectativa de estabilidade, suporte, segurança e comportamento previsível. Quando outros times automatizam processos sobre ela, mudanças deixam de ser locais.

Tratar API como produto significa definir:

  • consumidores e jornadas;
  • proposta de valor;
  • contrato e exemplos;
  • proprietário técnico e de negócio;
  • política de acesso;
  • objetivos de nível de serviço;
  • onboarding e ambiente de teste;
  • canal de suporte;
  • versionamento e depreciação;
  • métricas de uso e qualidade;
  • catálogo e inventário.

Uma API tecnicamente elegante que ninguém consegue descobrir, autenticar ou diagnosticar não é uma boa API corporativa.

20.2 REST como estilo arquitetural

REST foi descrito por Roy Fielding como um estilo arquitetural para sistemas hipermídia distribuídos, não como sinônimo de “JSON sobre HTTP” [6]. Suas restrições incluem cliente-servidor, stateless, cache, interface uniforme, sistema em camadas e código sob demanda opcional.

Na prática corporativa, muitas APIs são HTTP orientadas a recursos sem implementar hipermídia plenamente. Essa descrição é mais precisa que declarar automaticamente “RESTful”. O valor está em usar a semântica do protocolo de forma consistente.

20.3 Recursos, URLs e comandos

URLs devem identificar recursos, enquanto métodos expressam ações padronizadas:

GET    /v1/documents/8f3a
POST   /v1/documents
PATCH  /v1/documents/8f3a
DELETE /v1/documents/8f3a
GET    /v1/documents/8f3a/processing-jobs

Evite transformar todo verbo em URL:

POST /getDocument
POST /deleteDocument
POST /updateDocumentStatus

Nem toda ação de domínio mapeia naturalmente para CRUD. Publicar um artigo pode ser modelado como criação de uma transição ou comando:

POST /v1/articles/42/publication
POST /v1/tickets/20379/closure

O objetivo não é vencer uma disputa gramatical, mas construir um contrato estável, autorizado e observável. Comandos explícitos podem ser melhores que um PATCH genérico capaz de violar transições.

20.4 Semântica dos métodos HTTP

O RFC 9110 define a semântica atual do HTTP [7]. Dois conceitos são fundamentais:

  • safe: o cliente não solicita mudança de estado; GET, HEAD, OPTIONS e TRACE são definidos como seguros;
  • idempotent: múltiplas solicitações idênticas têm o mesmo efeito pretendido que uma; PUT, DELETE e métodos seguros são idempotentes por semântica.
Método Uso típico Seguro Idempotente Corpo comum
GET obter representação sim sim não
POST criar ou executar processamento não não por padrão sim
PUT substituir estado no URI conhecido não sim sim
PATCH alteração parcial não depende do formato/operação sim
DELETE remover associação/recurso não sim opcional

Idempotente não significa resposta idêntica. O primeiro DELETE pode responder 204 e o segundo 404; o efeito pretendido — recurso removido — permanece. Também não impede efeitos laterais como logs.

Não use GET para excluir, aprovar ou disparar job. Caches, crawlers e prefetch podem executar GET sem intenção humana.

20.5 Status codes e erros acionáveis

Código Significado prático
200 operação concluída com representação
201 recurso criado; preferir Location
202 aceito para processamento ainda não concluído
204 sucesso sem conteúdo de resposta
400 requisição malformada ou inválida em nível geral
401 autenticação ausente ou inválida
403 identidade conhecida, operação não autorizada
404 recurso não localizado ou ocultado por política
409 conflito com estado atual
412 precondição falhou, útil em concorrência otimista
422 conteúdo entendido, mas semanticamente inválido
429 limite de solicitações excedido
500 falha inesperada do servidor
502/503/504 gateway inválido, indisponibilidade e timeout de gateway

O RFC 9457 padroniza Problem Details for HTTP APIs, substituindo o RFC 7807 [8]:

{
  "type": "https://api.projetoplataforma.com.br/problems/invalid-transition",
  "title": "Transição de estado inválida",
  "status": 409,
  "detail": "O documento em processamento não pode ser publicado.",
  "instance": "/problems/occurrences/01JXYZ",
  "current_status": "processing",
  "requested_status": "published",
  "trace_id": "7f45b8d4d9a14c2a"
}

detail ajuda o consumidor a corrigir o problema; não deve expor stack trace, SQL, segredo ou caminho interno. Códigos de erro estáveis e campos estruturados são melhores que obrigar o cliente a interpretar texto.

20.6 OpenAPI 3.2.0 e contrato executável

A OpenAPI Specification 3.2.0, publicada em setembro de 2025, descreve interfaces HTTP de forma independente de linguagem [9]. Um documento pode alimentar portal, mocks, validação, geração de clientes e testes; não garante sozinho que a implementação cumpre o contrato.

openapi: 3.2.0
info:
  title: Knowledge Documents API
  version: 1.0.0
paths:
  /v1/documents:
    post:
      operationId: createDocument
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [title, file]
              properties:
                title:
                  type: string
                  minLength: 3
                file:
                  type: string
                  format: binary
      responses:
        "202":
          description: Documento aceito para processamento
          headers:
            Location:
              schema: { type: string, format: uri-reference }
        "422":
          description: Documento rejeitado por validação

Contrato executável exige disciplina:

  • revisão de compatibilidade no CI;
  • exemplos testados;
  • validação de request e response nas bordas;
  • testes do provedor contra a especificação;
  • consumer-driven contracts quando consumidores conhecidos possuem expectativas críticas;
  • documentação de autenticação, limites e erros;
  • inventário das versões em produção.

“Gerar Swagger depois” documenta o código atual, mas pode institucionalizar um contrato acidental. Design-first ajuda a discutir a interface antes da implementação; code-first reduz duplicação. O time pode combinar ambos, desde que exista uma fonte autoritativa e verificação de divergência.

20.7 Evolução compatível e versionamento

Mudanças geralmente compatíveis:

  • adicionar campo opcional à resposta, se clientes ignoram desconhecidos;
  • adicionar endpoint;
  • aceitar novo valor opcional sem mudar o padrão;
  • ampliar limites sem alterar semântica.

Mudanças potencialmente incompatíveis:

  • remover ou renomear campo;
  • tornar campo opcional obrigatório;
  • mudar tipo, unidade ou significado;
  • restringir enumeração;
  • alterar autorização ou ordenação padrão;
  • trocar 200 por fluxo assíncrono 202 sem preparar cliente.

Versionamento por caminho (/v1) é explícito e operacionalmente simples. Header ou media type preserva URLs, mas pode dificultar ferramentas e observação. Versionar cada mudança cria fragmentação; nunca versionar transfere quebra ao consumidor.

Uma política profissional define janela de suporte, aviso, telemetria de uso e encerramento. O RFC 9745 registra depreciação por header HTTP, enquanto o RFC 8594 define Sunset para indicar quando um recurso deverá deixar de responder [18][19]. Comunicação fora da API continua necessária.

20.8 Paginação, filtros e ordenação

Offset é simples:

GET /v1/tickets?limit=50&offset=100

Com dados que mudam, inserções podem causar repetição ou salto; offsets altos também custam caro em alguns bancos. Cursor representa posição estável:

GET /v1/tickets?limit=50&after=eyJjcmVhdGVkX2F0IjoiLi4uIn0
Estratégia Vantagem Limitação
offset/limit navegação e implementação simples inconsistência sob mudança e custo alto
página/número amigável para interface herda problemas de offset
cursor/keyset desempenho e estabilidade não salta facilmente para página arbitrária

Defina ordenação determinística, incluindo critério de desempate. ORDER BY created_at não é suficiente se vários registros compartilham timestamp; acrescente identificador.

20.9 Concorrência otimista

Dois usuários podem editar a mesma entidade. O último write silencioso perde a alteração anterior. HTTP permite precondições com ETag e If-Match:

GET /v1/articles/42
ETag: "v17"

PATCH /v1/articles/42
If-Match: "v17"
Content-Type: application/merge-patch+json

{"title":"Novo título"}

Se a versão já mudou, a API responde 412 Precondition Failed. O cliente recarrega, reconcilia e tenta conscientemente.

20.10 Autenticação não substitui autorização

Autenticação confirma uma identidade. Autorização decide se ela pode executar a ação sobre aquele objeto. O OWASP API Security Top 10 2023 coloca Broken Object Level Authorization como risco principal [10].

Este endpoint é insuficiente:

@app.get("/v1/documents/<document_id>")
@login_required
def get_document(document_id):
    return repository.get(document_id).to_dict()

A consulta deve restringir tenant, entidade e política:

document = repository.get_authorized(
    document_id=document_id,
    tenant_id=current_user.tenant_id,
    permissions=current_user.permissions,
)
if document is None:
    raise NotFound()

Use IDs não previsíveis como defesa complementar, não como controle de acesso. UUIDs evitam enumeração trivial, mas um identificador vazado continua exigindo autorização.

20.11 OAuth, OpenID Connect, API keys e mTLS

Mecanismo Resolve Uso adequado Atenção
cookie de sessão sessão Web no mesmo domínio aplicação server-rendered CSRF, SameSite e armazenamento
API key identifica aplicação/projeto automação controlada não representa usuário por si só
OAuth 2.0 autorização delegada APIs e integrações fluxos, escopos e tokens
OpenID Connect autenticação sobre OAuth SSO e identidade validar issuer, audience e nonce
mTLS autenticação de cliente/canal B2B e serviço a serviço certificados e rotação

O RFC 9700 consolida práticas atuais de segurança para OAuth 2.0 e desaconselha padrões históricos inseguros [11]. OpenID Connect Core define a camada de identidade [12]. Para cenários de maior risco, tokens vinculados ao cliente podem usar mTLS ou DPoP conforme RFC 8705 e RFC 9449 [13][14].

Não coloque token em query string. Valide assinatura, emissor, audiência, tempo e escopos. Token JWT não deve ser aceito apenas porque “decodifica”. Tokens de serviço devem ter privilégio mínimo e rotação; segredo compartilhado entre todos os workloads elimina identidade granular.

20.12 Rate limiting, quotas e proteção de recursos

Rate limit controla taxa em janela; quota controla consumo acumulado; limite de concorrência controla trabalho simultâneo; tamanho máximo protege memória e storage. Uma API de IA precisa considerar solicitações, tokens, GPU e tempo, não apenas requests por minuto.

O código 429 Too Many Requests foi definido pelo RFC 6585 e pode usar Retry-After [15]. A política deve indicar chave de controle: IP, usuário, tenant, token, endpoint ou custo ponderado.

Limitar somente no Nginx protege a borda, mas não entende custo por usuário. Limitar somente na aplicação pode permitir exaustão antes da regra. Controles em camadas são complementares.

20.13 Gateway, BFF e service mesh

API Gateway concentra entrada, roteamento, autenticação, limites e observação. Não deve virar o lugar de todas as regras de negócio. Backend for Frontend cria contratos adequados a um cliente específico, como portal e mobile. Service mesh atua na comunicação leste-oeste entre serviços, com identidade e telemetria de rede; não substitui autorização de domínio.

Componente Fronteira Conhecimento ideal
Gateway cliente → plataforma rota, identidade, quota e política de borda
BFF experiência específica composição para Web/mobile
Serviço domínio regra e autorização do objeto
Mesh serviço → serviço transporte, identidade workload e telemetria

20.14 Observabilidade de APIs

Cada resposta deve permitir investigação sem revelar detalhes internos. Registre método, rota normalizada, status, duração, tenant, identidade técnica, tamanho e identificadores de correlação. Não use a URL bruta como label de métrica se contém IDs, pois gera alta cardinalidade.

O W3C Trace Context padroniza traceparent e tracestate para propagação de rastreamento distribuído [16]. OpenTelemetry define APIs e modelos para traces, métricas e logs [17].

Sinais essenciais:

  • taxa de requisições;
  • taxa de erros por classe e operação;
  • latência p50, p95 e p99;
  • saturação de workers, pools e dependências;
  • consumo por cliente/tenant;
  • erros de autorização;
  • versões e endpoints ainda utilizados;
  • dependências externas e retries.

Média de latência esconde cauda. Uma API com média de 100 ms e p99 de 8 s causa experiência inconsistente e pode esgotar pools.

20.15 REST, GraphQL, gRPC e SOAP

Estilo Melhor encaixe Força Custo
REST/HTTP APIs públicas e recursos ecossistema, cache e depuração múltiplas chamadas e convenções
GraphQL clientes com composições variadas seleção de campos e schema tipado autorização, custo e cache complexos
gRPC comunicação interna tipada Protobuf, streaming e eficiência tooling, proxies e browser
SOAP contratos B2B/legados WS-* WSDL e padrões maduros envelope e complexidade operacional

GraphQL usa um schema para permitir que o cliente selecione dados [20]. gRPC define serviços por IDL e usa Protocol Buffers por padrão [21]. SOAP 1.2 é especificação W3C baseada em envelope de mensagens [22]. Não existe vencedor universal: escolha conforme consumidores, rede, governança, compatibilidade e operação.

20.16 Checklist de revisão de API

  • recurso e jornada estão claros?
  • método e código respeitam HTTP?
  • entrada e saída possuem schema e exemplo?
  • erros seguem formato estável?
  • autorização é verificada no objeto e função?
  • há idempotência onde retry pode duplicar efeito?
  • paginação possui ordem determinística?
  • timeout, tamanho, taxa e concorrência têm limites?
  • contrato é testado no CI?
  • versão e depreciação possuem política?
  • logs, métricas e traces preservam privacidade?
  • existe owner e canal de suporte?

Notas Zettelkasten — Capítulo 20

ZK-072 — API é produto operacional

Contrato, consumidor, owner, suporte, SLO e depreciação pertencem à API tanto quanto endpoints. Publicar rota sem ciclo de vida cria passivo corporativo.

Conecta-se a: ZK-070, ZK-073 e ZK-080.

ZK-073 — Compatibilidade inclui comportamento

Tipos e campos são apenas parte do contrato. Ordenação, autorização, códigos, limites e tempo de conclusão também podem quebrar consumidores.

Conecta-se a: ZK-061, ZK-064 e ZK-072.

ZK-074 — Identidade não concede acesso

Token válido prova identidade dentro de condições específicas; a aplicação ainda precisa autorizar ação, função, tenant e objeto.

Conecta-se a: ZK-072, ZK-077 e ZK-080.

Exercícios — Capítulo 20

  1. Modele URLs e métodos para publicar, arquivar e restaurar um artigo.
  2. Escreva um Problem Details para arquivo acima do limite.
  3. Classifique cinco mudanças de contrato como compatíveis ou incompatíveis.
  4. Compare cursor e offset para listar chamados que mudam continuamente.
  5. Explique por que @login_required não impede BOLA.
  6. Defina limites para uma API de resumo por IA considerando mais que requests por minuto.

Capítulo 21 — Microsserviços e sistemas distribuídos

Objetivos de aprendizagem

Ao concluir este capítulo, você deverá ser capaz de:

  • comparar monólito, monólito modular, SOA e microsserviços;
  • encontrar fronteiras de serviço por capacidade de negócio;
  • compreender propriedade de dados e consistência eventual;
  • desenhar sagas e mecanismos de resiliência;
  • avaliar prontidão organizacional e operacional;
  • planejar extração incremental sem produzir um monólito distribuído.

21.1 Microsserviço não é “API pequena”

Microsserviços organizam um sistema como serviços alinhados a capacidades de negócio, implantáveis e evoluíveis com autonomia. O tamanho em linhas de código não define a fronteira. Sam Newman enfatiza encapsulamento e capacidade independente de mudança [23]; a literatura prática também destaca organização ao redor de capacidades, dados descentralizados e automação [24].

Características desejáveis:

  • responsabilidade de domínio clara;
  • propriedade de dados;
  • contrato explícito;
  • implantação independente;
  • observabilidade própria;
  • capacidade de falhar sem derrubar tudo;
  • time responsável do desenvolvimento à operação;
  • evolução compatível.

Se dois serviços compartilham tabelas, precisam do mesmo deploy e chamam-se sincronamente para cada regra, a separação é principalmente física.

21.2 Comparando estilos

Estilo Implantação Dados Governança Melhor contexto
Monólito simples única banco único central produto inicial e domínio pequeno
Monólito modular única banco único com propriedade lógica módulos explícitos equipe pequena, domínio em evolução
SOA tradicional serviços integrados, frequentemente por ESB variados centralizada integração corporativa e legado
Microsserviços independente por serviço propriedade descentralizada federada com padrões de plataforma múltiplos times e ritmos distintos

SOA e microsserviços compartilham orientação a serviços; diferem mais em práticas de autonomia, deploy, governança e granularidade do que em uma fronteira absoluta. Chamar toda arquitetura antiga de SOA e toda arquitetura nova de microservices não ajuda a análise.

21.3 Quando o monólito é a decisão profissional

Um monólito reduz:

  • latência de rede interna;
  • autenticação serviço a serviço;
  • quantidade de pipelines;
  • coordenação de schemas distribuídos;
  • custo de observabilidade;
  • superfície de falha;
  • necessidade de plataforma especializada.

Para equipe pequena e produto ainda buscando encaixe, monólito modular costuma maximizar velocidade de aprendizado. A disciplina é preservar módulos, impedir banco como interface universal e preparar telemetria. “Começar monólito” não significa ignorar arquitetura; significa adiar distribuição até que suas vantagens sejam justificadas.

21.4 Drivers legítimos para separar serviços

  • módulos possuem taxas e perfis de escala muito diferentes;
  • deploy de uma capacidade bloqueia vários times;
  • requisitos de isolamento ou compliance divergem;
  • tecnologia especializada produz ganho mensurável;
  • uma fronteira de domínio estável possui owner claro;
  • falhas precisam ser isoladas por processo/infraestrutura;
  • parte do sistema possui ciclo de vida independente.

Motivações fracas:

  • “é mais moderno”;
  • “Netflix usa”;
  • “cada tabela será um serviço”;
  • “Kubernetes já está disponível”;
  • “assim o sistema escala”, sem indicar qual componente e carga;
  • “vai organizar o código”, sem primeiro tentar modularidade.

21.5 Bounded Context e fronteira de negócio

Domain-Driven Design usa Bounded Context para delimitar um modelo e sua linguagem [32]. A palavra “usuário” pode significar identidade autenticada no contexto de acesso, colaborador no RH e solicitante no suporte. Forçar um modelo universal cria campos, regras e conflitos demais.

Serviços não devem ser desenhados diretamente a partir de entidades do banco. UserService, StatusService e FileService tendem a produzir chamadas em cadeia. Fronteiras mais coesas representam capacidades: Identity, Knowledge Publishing, Document Processing, Support Integration.

Heurísticas:

  • quais regras precisam de consistência imediata?
  • quais dados mudam juntos?
  • qual equipe compreende e opera a capacidade?
  • quais termos possuem significado próprio?
  • qual parte escala ou falha de modo distinto?
  • a fronteira consegue entregar valor sem conversar com cinco serviços?

21.6 Banco por serviço e propriedade de dados

“Banco por serviço” significa que somente o serviço proprietário acessa diretamente seus dados. Bancos podem estar no mesmo servidor físico ou cluster, desde que propriedade e credenciais sejam separadas. O objetivo é impedir contrato por tabela.

Consequências:

  • joins atravessando domínios deixam de ser triviais;
  • relatórios precisam de projeções, APIs ou pipeline analítico;
  • transações globais tornam-se processos;
  • duplicação controlada de dados passa a ser normal;
  • schemas podem evoluir independentemente;
  • backup e restore precisam respeitar dependências.

Compartilhar a mesma instância PostgreSQL pode ser um passo econômico. Compartilhar todas as tabelas e permissões elimina autonomia.

21.7 Consistência eventual sem eufemismo

Consistência eventual significa que réplicas ou modelos derivados podem divergir temporariamente e convergir quando atualizações forem processadas. Isso não autoriza apresentar qualquer dado errado por tempo indefinido.

Defina:

  • qual informação pode ficar atrasada;
  • por quanto tempo;
  • como o usuário percebe “processando”;
  • qual ação exige leitura consistente;
  • como conflitos são resolvidos;
  • como divergência é detectada;
  • como reprocessar.

Exemplo: a busca pode levar 30 segundos para refletir um artigo publicado, mas a autorização do documento não pode depender de um índice desatualizado. Segurança e saldo normalmente exigem controles mais fortes que feed e analytics.

21.8 Saga: transação como sequência compensável

Uma saga coordena transações locais. Cada etapa confirma seu próprio estado; se uma etapa posterior falhar, ações compensatórias tentam produzir um resultado de negócio aceitável. O conceito foi formalizado no trabalho original sobre Sagas [27].

Exemplo de publicação:

  1. reservar versão do artigo;
  2. persistir conteúdo;
  3. indexar busca;
  4. publicar visibilidade;
  5. notificar consumidores.

Compensar não é voltar no tempo. Um e-mail enviado não pode ser “desenviado”; uma compensação pode registrar correção ou enviar nova mensagem. Por isso, a saga deve modelar semântica de negócio.

Coordenação Vantagem Risco
Coreografia por eventos baixo acoplamento central fluxo difícil de visualizar e ciclos
Orquestração estado e caminho explícitos orquestrador vira componente crítico

Para processos longos e auditáveis, uma máquina de estados persistida costuma ser mais clara que uma cadeia invisível de listeners.

21.9 Timeout, retry, circuit breaker e bulkhead

Em sistemas distribuídos, falha parcial é normal. O NIST SP 800-204 aborda estratégias de segurança para aplicações baseadas em microsserviços [25]; arquiteturas service mesh acrescentam mecanismos de comunicação, identidade e política, sem eliminar o comportamento correto da aplicação [26].

Padrões de resiliência possuem funções distintas:

  • timeout: limita quanto uma chamada pode ocupar recursos;
  • retry: tenta novamente falha transitória;
  • circuit breaker: interrompe chamadas provavelmente destinadas a falhar;
  • bulkhead: separa pools para conter saturação;
  • fallback: entrega resposta degradada explicitamente;
  • load shedding: rejeita trabalho para preservar funções críticas;
  • queue: absorve pico quando o trabalho pode esperar.
stateDiagram-v2
    [*] --> Fechado
    Fechado --> Aberto: falhas excedem limite
    Aberto --> MeioAberto: tempo de recuperação
    MeioAberto --> Fechado: teste bem-sucedido
    MeioAberto --> Aberto: nova falha

Retry e circuit breaker não devem ser empilhados em todas as camadas. Três tentativas no gateway, serviço e SDK podem transformar uma requisição em 27 chamadas. Microsoft documenta o risco de retry storms e recomenda limite, backoff, jitter e observação [28].

21.10 Disponibilidade, dependências e degradação

Pergunte para cada dependência:

  • ela é obrigatória para esse caminho?
  • existe cache seguro?
  • o resultado pode ser adiado?
  • qual timeout cabe no orçamento total?
  • a falha deve ser visível, enfileirada ou degradada?
  • há isolamento de pool?

No upload, Ollama não precisa participar da confirmação HTTP. O arquivo pode ser recebido e processado depois. No login, o provedor de identidade pode ser obrigatório, mas sessões válidas talvez continuem durante indisponibilidade curta. Degradação é decisão de produto e segurança, não apenas técnica.

21.11 Service discovery, configuração e identidade

Instâncias mudam. Service discovery resolve nome lógico para endpoints disponíveis. Em Kubernetes, Services e DNS oferecem descoberta; em ambientes menores, DNS e configuração podem bastar. Não construa um registry próprio sem necessidade.

Configuração dinâmica precisa de versão, validação e rollback. Segredo não é configuração comum: exige armazenamento apropriado, rotação, acesso mínimo e auditoria. Identidade de workload permite políticas por serviço em vez de uma senha global.

21.12 Deploy independente exige compatibilidade

Durante deploy, versões antiga e nova coexistem. Mudanças precisam ser compatíveis:

  • adicionar campo antes de exigir seu uso;
  • expandir schema antes de migrar leitores;
  • executar backfill;
  • mudar aplicação;
  • remover campo somente após provar desuso.

Essa sequência é conhecida como expand-and-contract. Migração de banco não deve presumir que todos os processos mudam simultaneamente. Feature flags ajudam a separar deploy de ativação, mas criam estados que precisam de owner e data de remoção.

21.13 Operabilidade e plataforma

Cada serviço adicional pede:

  • repositório e pipeline;
  • artefato e inventário;
  • configuração e segredo;
  • logs, métricas e traces;
  • dashboards e alertas;
  • health checks;
  • política de backup;
  • atualização de dependências;
  • runbook e plantão;
  • testes de contrato;
  • gestão de custo.

Uma plataforma interna pode oferecer caminhos padronizados — template, CI/CD, telemetria, identidade e deploy — sem retirar autonomia do domínio. Sem esse suporte, a quantidade de serviços cresce mais rápido que a capacidade de operá-los.

21.14 Segurança em microsserviços

Distribuição amplia superfície: mais identidades, endpoints, certificados, dependências e telemetria. Princípios:

  • identidade e autorização entre workloads;
  • TLS e, quando adequado, mTLS;
  • escopo mínimo por serviço;
  • validação de entrada em cada fronteira;
  • segredo rotacionável;
  • políticas de saída para reduzir SSRF e exfiltração;
  • imagens mínimas e SBOM;
  • logs sem credenciais ou dados sensíveis;
  • autorização de domínio permanece no serviço.

Gateway autenticar o usuário não autoriza automaticamente toda chamada interna. Contexto de identidade precisa ser propagado com integridade, e o serviço decide sobre seus recursos.

21.15 Economia e prontidão organizacional

Microsserviços fazem sentido quando o benefício de autonomia supera o custo fixo distribuído. Avalie:

Pergunta Sinal favorável Sinal de alerta
Quantos times mudam o sistema? vários times autônomos uma equipe pequena
Fronteiras estão claras? domínio e ownership estáveis regras ainda descobertas
Deploy atual é gargalo? coordenação frequente comprovada problema é teste lento
Perfis de escala diferem? processamento isolado domina carga tudo cresce junto
Operação está madura? CI/CD, OTel, SLO e plantão logs locais e deploy manual
Dados podem ser separados? propriedade clara joins globais constantes

Não existe número mágico de usuários que obrigue microsserviços. Um monólito bem construído pode atender grande carga; microsserviços podem falhar com poucos usuários se a operação for imatura.

21.16 Migração incremental

O Strangler Fig Pattern substitui capacidades gradualmente ao redor do legado [29]:

  1. instrumentar o monólito;
  2. estabelecer fronteiras internas;
  3. escolher capacidade com driver real;
  4. colocar facade/gateway na rota;
  5. extrair dados e comportamento;
  6. operar versões em paralelo quando necessário;
  7. migrar consumidores;
  8. remover caminho antigo.

Boa primeira extração possui fronteira clara e benefício mensurável. Processamento de PDF/IA pode escalar e falhar de modo diferente da Web, sendo candidato melhor que extrair “usuários” apenas porque aparece em muitas tabelas.

Notas Zettelkasten — Capítulo 21

ZK-075 — Autonomia é propriedade central do serviço

Processo separado sem autonomia de contrato, dados, deploy e operação é distribuição física, não independência arquitetural.

Conecta-se a: ZK-060, ZK-063 e ZK-077.

ZK-076 — Consistência distribuída é processo

Sem transação local única, o sistema precisa representar estados intermediários, compensação, idempotência e reconciliação. “Eventual” precisa de prazo e critério de convergência.

Conecta-se a: ZK-065, ZK-071 e ZK-079.

ZK-077 — Distribuição cobra capacidade operacional

Cada serviço adiciona identidade, deploy, telemetria, segurança e suporte. A decisão só se sustenta quando autonomia e isolamento pagam esse custo.

Conecta-se a: ZK-062, ZK-074 e ZK-080.

Exercícios — Capítulo 21

  1. Compare monólito modular e microsserviços para uma equipe de três pessoas.
  2. Proponha três bounded contexts para a KnowledgeBasePro e explique a linguagem de cada um.
  3. Modele uma saga de publicação com uma compensação que não seja simples rollback.
  4. Mostre como retries em três camadas podem multiplicar chamadas.
  5. Planeje uma migração expand-and-contract para renomear uma coluna usada por duas versões.
  6. Escolha o primeiro módulo a extrair do monólito e justifique por driver mensurável.

Capítulo 22 — Mensageria, eventos e processamento assíncrono

Objetivos de aprendizagem

Ao concluir este capítulo, você deverá ser capaz de:

  • distinguir job, comando, evento, fila, tópico e log distribuído;
  • compreender acknowledgement, confirmação, ordenação e semânticas de entrega;
  • comparar Redis/RQ, RabbitMQ e Kafka pelo problema resolvido;
  • implementar consumidores idempotentes, retry e DLQ;
  • fechar a inconsistência banco–broker com Outbox;
  • medir backlog, idade, vazão e saturação.

22.1 Assíncrono muda o modelo do produto

Quando o processamento deixa a requisição HTTP, a aplicação precisa representar o tempo entre aceitação e conclusão. O usuário não recebe apenas sucesso ou erro; recebe estados como received, queued, processing, indexed e failed.

Mensageria resolve problemas específicos:

  • desacoplar disponibilidade simultânea;
  • absorver picos;
  • distribuir trabalho;
  • permitir fan-out;
  • reter eventos para releitura;
  • integrar tecnologias diferentes.

Ela não garante automaticamente que a regra execute uma vez, em ordem global ou dentro da mesma transação do banco.

22.2 Job, comando e evento

Tipo Expressa Nome recomendado Consumidores
Job trabalho técnico a executar extract_pdf(document_id) worker específico
Comando intenção dirigida GenerateDocumentSummary normalmente um responsável
Evento fato que já ocorreu DocumentIndexed zero ou vários

IndexDocument no imperativo é comando. DocumentIndexed no passado é evento. Chamar uma ordem de evento não muda a semântica; confunde ownership e tratamento de erro.

Eventos de domínio podem permanecer dentro de um módulo. Eventos de integração são contratos públicos, estáveis e geralmente carregam menos detalhes internos. Nem todo clique ou alteração de coluna merece virar evento corporativo.

22.3 Fila, tópico e log

Fila de trabalho: uma mensagem é processada por um consumidor do grupo. Boa para distribuir jobs.

Tópico pub/sub: cada assinatura interessada recebe uma cópia lógica. Bom para notificar capacidades independentes.

Log distribuído: eventos são anexados de forma ordenada por partição; consumidores controlam posição e podem reler. Bom para streaming, múltiplas projeções e retenção.

RabbitMQ roteia mensagens por exchanges para filas. Kafka organiza eventos em tópicos particionados e retidos. Redis/RQ representa filas de jobs Python sobre Redis. O AMQP 1.0 especifica um protocolo aberto para mensagens interoperáveis, mas um produto e uma versão concreta ainda determinam recursos operacionais [38]. As ferramentas se sobrepõem, e o modelo dominante influencia desenho e operação. Uma análise mais ampla de logs, replicação, particionamento e consistência pode ser encontrada em Kleppmann [40].

22.4 Redis/RQ, RabbitMQ e Kafka

Critério Redis + RQ RabbitMQ Kafka
Abstração principal job Python broker de mensagens/filas log distribuído de eventos
Curva inicial baixa média alta
Roteamento simples por fila exchanges, bindings e routing keys tópico e partição
Retenção/releitura orientada ao ciclo do job mensagens normalmente saem após ack parte central do modelo
Fan-out requer filas/jobs próprios natural por múltiplas filas grupos leem independentemente
Ordem depende da fila/workers fila com ressalvas por partição
Melhor uso background jobs em app Python workflows, comandos e roteamento streams, integração e analytics em escala

Para KnowledgeBasePro com equipe pequena e processamento Python, RQ pode ser escolha adequada. Se surgirem múltiplos consumidores independentes, roteamento avançado e necessidade forte de filas replicadas, RabbitMQ ganha atratividade. Se retenção, replay, alto throughput e várias projeções se tornarem centrais, Kafka pode justificar o custo.

Não escolha Kafka para substituir uma chamada de função lenta. Não trate Redis/RQ como event store corporativo. Comece pelas garantias exigidas.

22.5 Publicar não é processar

Existem confirmações diferentes:

  1. produtor entregou bytes ao cliente local;
  2. broker aceitou a publicação;
  3. mensagem foi persistida/replicada;
  4. consumidor recebeu;
  5. consumidor concluiu o efeito;
  6. efeito tornou-se observável no sistema de destino.

RabbitMQ distingue publisher confirms de consumer acknowledgements: a confirmação cobre a publicação; o ack informa que o consumidor assumiu e concluiu conforme sua política [33]. Em quorum queues, confirms estão ligados à replicação por quorum, e consumidores devem usar acknowledgement manual para segurança adequada [34].

Dar ack antes de persistir o resultado pode perder trabalho. Dar ack depois permite redelivery se o consumidor cair — e, portanto, exige idempotência.

22.6 At-most-once, at-least-once e exactly-once

Semântica Possível perda Possível duplicidade Uso
At-most-once sim não telemetria descartável
At-least-once não após garantias assumidas sim padrão prático para processos importantes
Exactly-once depende do escopo escondida/evitada no escopo pipelines com condições rigorosas

“Exactly-once” nunca deve ser aceito sem perguntar: exatamente uma vez onde? Kafka oferece mecanismos idempotentes e transacionais dentro de escopos definidos [35], mas não torna uma chamada ao GLPI e um e-mail externos atomicamente únicos. O efeito de negócio ponta a ponta ainda depende de chave, transação local e reconciliação.

22.7 Consumidor idempotente e Inbox

def handle_document_indexed(message, uow):
    with uow:
        if uow.inbox.was_processed(message.id, consumer="search-projector"):
            return

        projection = build_search_projection(message.data)
        uow.search.upsert(message.data["document_id"], projection)
        uow.inbox.mark_processed(message.id, consumer="search-projector")
        uow.commit()

O marcador de processamento e o efeito devem participar da mesma transação quando estão no mesmo banco. Se o efeito é externo, guarde a intenção e um identificador idempotente aceito pelo destino; se o destino não oferece idempotência, reconciliação torna-se ainda mais importante.

22.8 Ordem é local, não absoluta

Múltiplos produtores, consumidores paralelos e reentrega quebram a intuição FIFO. Kafka preserva ordem dentro de uma partição; RabbitMQ entrega em ordem de fila, mas requeue, prioridade e vários consumidores alteram a ordem de processamento final.

Se a ordem importa por documento, use document_id como chave de partição ou controle versão:

{
  "document_id": "doc-42",
  "version": 7,
  "event_type": "DocumentTitleChanged"
}

O consumidor ignora versão anterior à já aplicada e detecta lacunas. Exigir ordem global reduz paralelismo e disponibilidade; normalmente o requisito real é ordem por agregado, cliente ou conta.

22.9 Retry e mensagens venenosas

Uma mensagem pode falhar por dependência transitória, limite, dado inválido, bug ou incompatibilidade de schema. Repetir todas da mesma forma produz loop e bloqueia fila.

Política recomendada:

  • classificar falha;
  • limitar tentativas;
  • usar backoff com jitter;
  • preservar contagem e último erro;
  • mover falha permanente para quarentena/DLQ;
  • alertar por idade e volume;
  • permitir replay controlado após correção.

DLQ não é lixeira. Cada mensagem precisa de contexto para diagnóstico, retenção, owner e ação. Reprocessar a DLQ inteira sem corrigir causa pode repetir efeitos ou derrubar novamente o consumidor.

22.10 Outbox Pattern

Considere:

save_document()
commit_database()
publish_event()  # processo cai aqui

O documento existe, mas o evento nunca foi publicado. Inverter a ordem cria o oposto: evento publicado para transação que pode falhar.

Outbox grava mudança de negócio e mensagem na mesma transação local:

BEGIN;

UPDATE documents
SET status = 'indexed', version = version + 1
WHERE id = :document_id;

INSERT INTO outbox (
    event_id, aggregate_id, event_type, payload, occurred_at
) VALUES (
    :event_id, :document_id, 'DocumentIndexed', :payload, now()
);

COMMIT;

Um relay publica registros pendentes e marca o resultado. Ele pode publicar duplicado se cair entre enviar e marcar; consumidores continuam idempotentes. O padrão é amplamente descrito no catálogo de microservices patterns [39].

sequenceDiagram
    participant A as Aplicação
    participant D as Banco
    participant R as Relay
    participant B as Broker
    participant C as Consumidor
    A->>D: Estado + evento outbox
    R->>D: Lê pendentes
    R->>B: Publica evento
    B->>C: Entrega
    C->>C: Deduplica e processa

22.11 Schema e envelope de evento

CloudEvents define atributos comuns para descrever eventos de forma interoperável [36]. AsyncAPI 3.0 descreve APIs orientadas a mensagens em formato legível por máquinas [37]. Um envelope pode conter:

{
  "specversion": "1.0",
  "id": "01K4E8R9K7P2",
  "source": "/knowledge/documents",
  "type": "br.com.projetoplataforma.document.indexed.v1",
  "subject": "documents/doc-42",
  "time": "2026-09-02T14:30:00Z",
  "datacontenttype": "application/json",
  "traceparent": "00-7f45b8d4d9a14c2a9a91fd61f43c81d1-2eaa-01",
  "data": {
    "document_id": "doc-42",
    "tenant_id": "osten",
    "content_version": 7
  }
}

Não inclua o texto inteiro do PDF se o consumidor pode buscar por referência autorizada. Mensagens grandes pressionam broker, rede e retenção. O Claim Check Pattern guarda o conteúdo em storage e envia referência, integridade e autorização.

22.12 Evolução de schema

Regras conservadoras:

  • acrescente campos opcionais com defaults claros;
  • consumidores ignoram campos desconhecidos;
  • não reutilize campo com novo significado;
  • preserve eventos antigos durante a retenção;
  • versionamento maior indica quebra semântica;
  • teste consumidores com exemplos antigos e novos;
  • use schema registry quando escala e governança justificarem.

Eventos são fatos históricos. Reescrever silenciosamente a semântica do passado compromete replay e auditoria.

22.13 Backpressure e Lei de Little

Se a chegada supera a capacidade de processamento por tempo suficiente, o backlog cresce. A Lei de Little relaciona itens médios no sistema, taxa de chegada e tempo médio:

\[L = \lambda W\]

Nessa expressão, L é a quantidade média de itens no sistema, λ é a taxa média de chegada e W é o tempo médio no sistema. Se chegam 10 documentos por minuto e cada um permanece 6 minutos no pipeline, há em média 60 documentos no sistema, sob condições estáveis.

Escalar consumidores sem limite pode saturar PostgreSQL, storage ou Ollama. Backpressure pode exigir:

  • limite de fila e admissão;
  • concorrência por tenant;
  • prefetch controlado;
  • prioridade explícita;
  • lote;
  • autoscaling baseado em idade, não apenas tamanho;
  • rejeição ou agendamento quando capacidade acaba.

22.14 Observabilidade da fila

Monitore:

  • taxa de publicação e conclusão;
  • tamanho e idade da mensagem mais antiga;
  • duração por tipo de job;
  • retries e motivo;
  • mensagens não confirmadas;
  • DLQ por tipo e versão;
  • utilização dos consumidores;
  • saturação da dependência;
  • diferença entre outbox pendente e publicado.

Fila vazia não prova saúde: produtores podem ter parado. Fila grande não prova incidente: pode ser lote esperado. Métrica precisa de contexto, tendência e SLO.

22.15 Pipeline BookWave

flowchart TB
    U["Upload confirmado"] --> Q1["Fila de extração"]
    Q1 --> E["Texto e estrutura"]
    E --> Q2["Fila de indexação"]
    Q2 --> X["Busca e embeddings"]
    X --> P["Documento pesquisável"]

Cada etapa grava estado e versão. Extração é idempotente pelo hash do arquivo e versão do extrator. Embeddings incluem modelo e dimensão. Uma mudança de modelo cria nova projeção sem destruir a anterior até validação. O documento só fica pesquisável quando autorização e índice da versão correta estão disponíveis.

Notas Zettelkasten — Capítulo 22

ZK-078 — Assíncrono exige estado visível

Retirar trabalho da requisição melhora desacoplamento temporal, mas cria período intermediário que produto, banco e observabilidade precisam representar.

Conecta-se a: ZK-066, ZK-076 e ZK-080.

ZK-079 — At-least-once pede idempotência

Ack após efeito evita perda, mas permite reentrega. A confiabilidade emerge da combinação broker, transação local, deduplicação e reconciliação.

Conecta-se a: ZK-065, ZK-071 e ZK-076.

ZK-080 — Fila é sistema operacional de negócio

Backlog representa trabalho e expectativa de usuário. Idade, prioridade, erro, owner e capacidade precisam ser operados como parte do produto.

Conecta-se a: ZK-061, ZK-069, ZK-072 e ZK-077.

Exercícios — Capítulo 22

  1. Classifique ProcessPdf, DocumentIndexed e SendSlackNotification como job, comando ou evento.
  2. Explique por que publisher confirm não prova que o consumidor concluiu.
  3. Projete idempotência para um worker que gera embeddings.
  4. Defina chave de partição para preservar ordem por documento.
  5. Calcule L para 5 jobs/s com tempo médio de 20 s.
  6. Desenhe um procedimento seguro de replay de DLQ.

Capítulo 23 — Padrões de projeto e padrões arquiteturais

Objetivos de aprendizagem

Ao concluir este capítulo, você deverá ser capaz de:

  • interpretar padrões como linguagem de decisão;
  • distinguir padrões de criação, estrutura, comportamento, domínio e arquitetura;
  • aplicar Strategy, Factory, Adapter, Facade, Observer, Repository e Unit of Work;
  • comparar MVC, CQRS, Event Sourcing, Hexagonal e Clean Architecture;
  • reconhecer pattern soup e abstrações sem benefício;
  • justificar um padrão por força, contexto e consequência.

23.1 Padrão é solução nomeada em contexto

O catálogo clássico de Gamma, Helm, Johnson e Vlissides descreve padrões de design orientado a objetos [41]. Um padrão não é trecho de código para copiar. Ele registra:

  • contexto recorrente;
  • forças em tensão;
  • estrutura da solução;
  • consequências;
  • relações com outros padrões.

Nomear uma classe Factory não prova que Factory era necessário. O valor do vocabulário aparece quando alguém diz “usaremos Adapter para impedir que o modelo do GLPI atravesse a fronteira” e o time entende intenção e consequência.

23.2 Classificação útil

Escala Exemplos Problema
Criação Factory, Builder construção variável ou complexa
Estrutura Adapter, Facade, Decorator composição e interface
Comportamento Strategy, Observer, Command algoritmos e colaboração
Persistência Repository, Unit of Work acesso e transação
Aplicação CQRS, Saga, Outbox coordenação de casos de uso
Arquitetura Layered, Hexagonal, Event-Driven estruturas sistêmicas

Padrões em escalas diferentes podem coexistir. Hexagonal pode usar adapters; uma aplicação CQRS pode continuar monolítica; microsserviços não exigem Event Sourcing.

23.3 Factory

Factory centraliza criação quando tipo, configuração ou invariantes variam:

def build_language_model(settings) -> LanguageModelPort:
    if settings.provider == "ollama":
        return OllamaAdapter(settings.ollama_url, settings.model)
    if settings.provider == "openai":
        return OpenAIAdapter(settings.api_key, settings.model)
    raise UnsupportedProvider(settings.provider)

Se um construtor simples basta, Factory adiciona navegação sem ganho. Ela não deve esconder dependências globais ou I/O inesperado.

23.4 Strategy

Strategy encapsula algoritmos intercambiáveis sob um contrato. Bom para políticas de prioridade, chunking e cobrança. A seleção pode ocorrer no composition root ou por regra explícita.

class ChunkingStrategy(Protocol):
    def split(self, text: str) -> list[str]: ...

class HeadingAwareChunking:
    def split(self, text): ...

class FixedTokenChunking:
    def split(self, text): ...

As estratégias precisam cumprir pós-condições comuns, como tamanho máximo e preservação de referência ao documento.

23.5 Observer e eventos

Observer notifica interessados quando o sujeito muda. Dentro do processo, é útil para extensões locais. Em rede, um broker implementa pub/sub com falhas e garantias muito diferentes. Não confunda callback em memória com evento durável.

Riscos:

  • ordem implícita entre observers;
  • erro silencioso;
  • efeitos laterais difíceis de testar;
  • ciclo de notificações;
  • fluxo invisível.

Quando a ordem importa, um orquestrador explícito pode ser melhor.

23.6 Adapter e Facade

Adapter traduz uma interface para outra. Facade oferece uma entrada simplificada para um subsistema. Um GlpiAdapter converte OpenTicket em payload GLPI. Um MonthlyReportFacade coordena consulta, gráfico, PDF e entrega.

class GlpiTicketAdapter(TicketGateway):
    def open_ticket(self, command):
        payload = {
            "name": command.title,
            "content": command.description,
            "entities_id": self.entity_map[command.tenant_id],
            "itilcategories_id": self.category_map[command.category],
        }
        response = self.client.post("/Ticket", json={"input": payload})
        return ExternalTicket(id=str(response["id"]), system="glpi")

O adapter não deve retornar toda a resposta externa e obrigar o domínio a interpretá-la. Também não deve engolir erros e devolver False; traduza falhas em categorias que o caso de uso possa decidir.

23.7 Repository

Repository oferece uma coleção orientada ao domínio e encapsula persistência [42]:

class DocumentRepository(Protocol):
    def get(self, document_id: str) -> Document | None: ...
    def add(self, document: Document) -> None: ...
    def find_stalled(self, before, limit: int) -> list[Document]: ...

Um repository genérico save/find/delete pode esconder semântica e ainda vazar ORM. Consultas analíticas complexas podem usar query services próprios; não precisam fingir que retornam agregados de domínio.

23.8 Unit of Work

Unit of Work acompanha alterações e delimita transação. No design apresentado, coordena repositories, inbox e outbox. Seu escopo costuma ser o caso de uso.

with uow:
    ticket = uow.tickets.get(ticket_id)
    ticket.mark_external_created(glpi_id)
    uow.outbox.add(TicketCreated.from_entity(ticket))
    uow.commit()

Ela não transforma APIs externas em participantes da transação. O commit local termina antes do relay e dos efeitos remotos.

23.9 MVC

MVC separa modelo, visão e controle de entrada. Frameworks Web adaptam o padrão de modos diferentes. Em Flask:

  • route/controller traduz HTTP;
  • template renderiza a visão;
  • modelo não precisa ser sinônimo de ORM;
  • service/case handler coordena aplicação.

Colocar todo comportamento no controller produz fat controllers; colocar tudo no ORM produz active records difíceis de separar. MVC organiza interface, mas não define toda a arquitetura do sistema.

23.10 CQRS

Command Query Responsibility Segregation separa modelos de escrita e leitura. Em nível simples, comandos aplicam regras por casos de uso e queries retornam DTOs otimizados. Isso não exige bancos separados, eventos ou microsserviços.

Command: PublishArticle(article_id, actor_id, expected_version)
Query:   ArticleDashboard(tenant_id, period, filters)

CQRS é útil quando regras de escrita e necessidades analíticas divergem. Em CRUD pequeno, duplica modelos sem retorno. Separar responsabilidades conceitualmente é diferente de operar dois bancos consistentes.

23.11 Event Sourcing

Event Sourcing persiste eventos como fonte de verdade do estado:

DocumentReceived
FileStored
ExtractionStarted
TextExtracted
IndexBuilt
DocumentPublished

O estado é reconstruído por replay. Benefícios: trilha temporal, modelos derivados e explicação do estado. Custos: evolução de eventos, snapshots, projeções, consistência eventual, privacidade e curva operacional.

Tabela de auditoria não é Event Sourcing. Publicar eventos depois de salvar estado também não é. No Event Sourcing, o log de eventos aceitos é a persistência autoritativa do agregado.

23.12 Arquitetura Hexagonal, Clean e Onion

Essas famílias colocam regras centrais dentro e mecanismos nas bordas. Clean Architecture reforça que dependências de código apontam para políticas de maior nível [43]. Ports and Adapters descreve portas oferecidas/requeridas e adapters tecnológicos.

flowchart TB
    W["Web e CLI"] --> IN["Portas de entrada"]
    IN --> D["Casos de uso e domínio"]
    D --> OUT["Portas de saída"]
    OUT --> X["Banco, fila e APIs"]

O diagrama não obriga quatro projetos, uma classe por círculo ou duplicação universal. Dependência lógica e separação de deploy são decisões diferentes.

23.13 Event-Driven Architecture

Em arquitetura orientada a eventos, fatos significativos provocam reações. Benefícios incluem fan-out e desacoplamento temporal. Riscos incluem causalidade difícil, duplicidade, contratos históricos e observabilidade distribuída.

Eventos não devem esconder comandos para contornar ownership. CustomerValidationRequested é intenção, apesar do passado gramatical. Nomeie fatos que já se tornaram verdade.

23.14 Pattern soup e arquitetura por moda

Pattern soup ocorre quando CQRS, Event Sourcing, Saga, Mediator, Repository e microservices são combinados porque parecem profissionais. O resultado possui mais peças que problema.

Use este teste:

  1. qual força concreta exige o padrão?
  2. qual alternativa mais simples foi considerada?
  3. que complexidade o padrão remove?
  4. que complexidade adiciona?
  5. como verificaremos o benefício?
  6. quando removeríamos ou substituiríamos a decisão?

O princípio de ocultação de informação de Parnas permanece atual: decomponha de modo que decisões difíceis ou prováveis de mudar fiquem escondidas atrás de módulos [44]. Essa ideia é mais profunda que copiar estruturas de pasta.

23.15 Matriz de escolha

Problema Padrão candidato Não usar quando
algoritmo varia Strategy regra única e estável
provedor externo incompatível Adapter já existe contrato adequado
subsistema complexo para consumidor Facade esconde capacidades necessárias
criação depende de configuração Factory construtor é direto
transação envolve repositories locais Unit of Work operação é simples e isolada
leitura e escrita divergem muito CQRS CRUD possui mesmo modelo
estado histórico é fonte de verdade Event Sourcing auditoria simples resolve
domínio precisa ignorar framework Hexagonal/Clean cerimônia supera variação

Notas Zettelkasten — Capítulo 23

ZK-081 — Padrão é linguagem de forças

O nome só é útil quando comunica contexto, problema e consequência. Implementação parecida sem a força correspondente é coincidência, não aplicação consciente.

Conecta-se a: ZK-068, ZK-082 e ZK-083.

ZK-082 — Fronteira protege decisão volátil

Adapter, módulo e porta escondem detalhes que mudam por razões externas. A fronteira vale quando impede propagação real de mudança.

Conecta-se a: ZK-063, ZK-066 e ZK-081.

ZK-083 — Simplicidade também é requisito arquitetural

Cada abstração cobra compreensão, teste e manutenção. A melhor solução é a menor que protege as variações e riscos relevantes, não a que contém mais padrões.

Conecta-se a: ZK-060, ZK-068 e ZK-081.

Exercícios — Capítulo 23

  1. Diferencie Adapter e Facade usando a integração com GLPI.
  2. Dê um caso válido e um caso prematuro para Strategy.
  3. Explique por que Repository genérico pode enfraquecer o domínio.
  4. Modele CQRS simples no mesmo banco, sem eventos.
  5. Diferencie auditoria, event notification e Event Sourcing.
  6. Analise um padrão do capítulo com as seis perguntas de escolha.

Estudo de caso integrado — KnowledgeBasePro, BookWave e ITSM

1. Problema de negócio

A plataforma precisa transformar documentos dispersos em conhecimento pesquisável e acionável. O objetivo B2B não é competir com uma suíte de arquivos apenas pelo armazenamento. É conectar conteúdo, aprendizagem e operação: um procedimento explica como resolver; a busca localiza o conhecimento; uma dúvida pode originar chamado; a solução validada volta para a base; relatórios mostram lacunas recorrentes.

Capacidades principais:

  • receber e versionar PDFs, artigos e anexos;
  • extrair texto e estrutura;
  • indexar para busca textual e semântica;
  • responder com IA citando fontes autorizadas;
  • oferecer leitura organizada no BookWave;
  • integrar solicitações e conhecimento ao GLPI;
  • automatizar notificações pelo n8n e Slack;
  • isolar tenants, entidades e níveis de acesso;
  • operar em Docker com equipe pequena.

2. Drivers e restrições

Tipo Driver
Negócio reduzir tempo para encontrar solução e registrar aprendizado
Experiência confirmar upload rapidamente e mostrar progresso real
Segurança impedir vazamento entre empresas, entidades e grupos
Confiabilidade reinício de worker não pode perder documento ou duplicar ticket
Operação diagnóstico deve ser possível com equipe enxuta
Custo aproveitar Flask, Redis/RQ, Nginx e Ollama já conhecidos
Evolução extrair processamento ou busca somente quando houver driver
Restrição externa contrato e permissões do GLPI 10.0.15

Decisão inicial: monólito modular para Web e API, workers separados para trabalho pesado, PostgreSQL como estado transacional, storage persistente para binários e Redis/RQ como fila de jobs. Isso não é arquitetura puramente monolítica nem uma malha de microsserviços; é separação física seletiva onde perfil de execução justifica.

3. Contexto do sistema

flowchart TB
    P["Profissional e estudante"] --> K["Plataforma de conhecimento"]
    E["Equipe de TI e suporte"] --> K
    K --> G["GLPI"]
    K --> N["n8n e Slack"]
    K --> M["Modelos de IA"]

A plataforma é o sistema em foco. Nginx, banco e Redis não aparecem na visão de contexto porque são detalhes internos. O objetivo aqui é explicar fronteiras externas e pessoas.

4. Visão de containers

flowchart TB
    U["Navegador"] --> X["Nginx"]
    X --> A["Flask Web/API"]
    A --> D[("PostgreSQL")]
    A --> S[("Storage persistente")]
    A --> Q[("Redis/RQ")]
    Q --> W["Workers"]
    W --> D
    W --> S
    W --> I["Ollama ou provedor de IA"]
    A --> G["Adapter GLPI/n8n"]

Mesmo em Docker Compose, nomes e limites precisam de contrato. Nginx termina TLS e aplica limites de borda. Flask autentica, autoriza e coordena casos de uso. PostgreSQL é fonte oficial de metadados e estado. Storage guarda arquivos fora da camada gravável do container. Redis/RQ transporta jobs, mas não substitui o estado persistido do documento. Workers extraem, indexam e consultam IA. O adapter encapsula APIs externas.

Se a implantação futura usar Kubernetes, Service e DNS podem oferecer descoberta estável para Pods mutáveis [30]. Isso não muda automaticamente o desenho de domínio.

5. Módulos do monólito

Módulo Responsabilidade Dados próprios
Identity & Access usuário, tenant, grupo e política ident
Documents metadados, versões e ciclo de vida documents, document_versions
Knowledge artigos, coleções e publicação articles, collections
Processing jobs lógicos, extração e falhas processing_jobs, artifacts
Search projeções e índice search_documents, embedding_versions
Learning/BookWave leitura, progresso e trilhas reading_progress, tracks
Support Integration solicitações e vínculo GLPI support_requests, external_refs
Audit eventos de segurança e operação audit_entries

Uma tabela pode residir no mesmo PostgreSQL, mas somente o módulo proprietário altera sua semântica. Consultas de dashboard usam read models ou views documentadas, evitando que qualquer módulo escreva em qualquer tabela.

6. Identidades e identificadores

Cada entidade precisa de identificadores distintos:

  • document_id: identidade estável do documento lógico;
  • document_version_id: versão de conteúdo;
  • content_sha256: integridade e possível deduplicação contextual;
  • job_id: execução específica;
  • event_id: mensagem única;
  • trace_id: cadeia técnica de uma operação;
  • external_ticket_id: referência atribuída pelo GLPI;
  • idempotency_key: intenção repetível sem efeito duplicado.

UUIDs são padronizados pelo RFC 9562, que também define UUIDv7 ordenável por tempo [47]. Identificador técnico não substitui regra de autorização e não deve carregar segredo.

Timestamps de integração usam UTC e formato compatível com RFC 3339 [46]. Horário local é apresentado na interface, não embutido de modo ambíguo no contrato.

7. Fluxo confiável de upload

sequenceDiagram
    actor U as Usuário
    participant A as Flask
    participant D as Banco/Storage
    participant Q as Redis/RQ
    participant W as Worker
    U->>A: POST documento + chave
    A->>D: Arquivo, estado e outbox
    A-->>U: 202 + URL de status
    A->>Q: Relay agenda job
    Q->>W: Processar versão
    W->>D: Resultado ou falha

Passos e garantias

  1. Nginx rejeita corpo acima do limite antes de consumir recursos indevidos.
  2. Flask autentica e verifica permissão de criar no tenant.
  3. O arquivo é transmitido para área temporária controlada, com limite e cálculo de hash.
  4. Validação não confia apenas em extensão ou Content-Type enviado pelo cliente.
  5. O binário é movido para chave imutável de storage.
  6. Metadados, estado stored e outbox são confirmados em transação.
  7. A resposta 202 informa recurso de status.
  8. Relay agenda job idempotente.
  9. Worker marca início, extrai e salva artefatos versionados.
  10. Falha é classificada; retry respeita política.
  11. Índice só publica versão autorizada e completa.
  12. Reconciliação encontra estados parados.

Se o processo cair após o passo 6, a outbox preserva a intenção. Se RQ redeliver, a versão e a chave impedem duplicação destrutiva. Se o worker morrer durante OCR, o job fica recuperável; o documento não desaparece porque Redis não é a fonte única.

8. Máquina de estados do documento

stateDiagram-v2
    [*] --> Received
    Received --> Stored: arquivo validado
    Received --> Rejected: inválido
    Stored --> Queued: job registrado
    Queued --> Processing: worker iniciou
    Processing --> Indexed: artefatos completos
    Processing --> Failed: erro classificado
    Failed --> Queued: retry ou reprocessamento
    Indexed --> Published: política permite

O estado técnico indexed não equivale ao estado de negócio published. Um documento pode estar pronto para busca, mas aguardando revisão. Separar dimensões evita usar um campo status com significados incompatíveis.

9. Contrato HTTP essencial

POST   /v1/documents
GET    /v1/documents/{document_id}
GET    /v1/documents/{document_id}/versions
GET    /v1/documents/{document_id}/processing-jobs/latest
POST   /v1/documents/{document_id}/reprocessing
POST   /v1/articles/{article_id}/publication
POST   /v1/support-requests
GET    /v1/support-requests/{request_id}

POST /documents aceita Idempotency-Key, responde 202, Location: /v1/documents/{id} e representação inicial. Reprocessar cria uma nova execução, não apaga o histórico. Erros seguem RFC 9457. Endpoints retornam apenas objetos autorizados no tenant.

10. Evento de integração

{
  "specversion": "1.0",
  "id": "01991d43-57d2-7a16-9eaf-37d8b4208ac1",
  "source": "/knowledgebasepro/documents",
  "type": "br.com.projetoplataforma.document.indexed.v1",
  "subject": "documents/doc-42/versions/7",
  "time": "2026-09-02T14:30:00Z",
  "data": {
    "document_id": "doc-42",
    "version": 7,
    "tenant_id": "osten",
    "artifact_key": "artifacts/doc-42/v7/extracted.json"
  }
}

Consumidores recebem referência, não o texto sensível. O acesso ao artefato exige identidade e autorização. O schema inclui versão; evento e payload são testados.

11. Integração GLPI em duas etapas

No GLPI, abrir o ticket e anexar evidência são operações separadas. Um fluxo confiável representa isso:

requested
  → ticket_created
  → document_created
  → document_linked
  → notification_sent

O adapter realiza:

  1. inicia ou renova sessão;
  2. cria Ticket com entidade e categoria autorizadas;
  3. registra external_ticket_id localmente;
  4. cria o arquivo como Document;
  5. vincula por Document_Item com itemtype=Ticket;
  6. confirma IDs e registra auditoria.

Se o usuário técnico não possui permissão para listar categorias, a integração pode usar mapeamento administrado e validado, não descobrir IDs a cada execução. No ambiente de exemplo, categoria e entidade são configurações por tenant; valores não ficam replicados em vários workflows.

Timeout após criar ticket produz resultado incerto. Antes de criar outro, o sistema consulta pela referência idempotente ou executa reconciliação. Se a API não oferece chave idempotente nativa, a plataforma persiste a intenção e usa marca externa rastreável no título/campo adequado, respeitando as possibilidades do GLPI.

12. Autorização multi-tenant

Uma defesa em camadas inclui:

  • tenant_id obrigatório no contexto autenticado;
  • repositories que filtram pelo tenant;
  • autorização por objeto no caso de uso;
  • storage sem chaves adivinháveis e sem acesso público direto;
  • URLs temporárias curtas quando necessárias;
  • índice de busca filtrado antes da recuperação dos trechos;
  • workers carregando tenant e política no job;
  • auditoria de leitura sensível;
  • testes tentando cruzar tenants.

Para arquiteturas de microsserviços, o NIST SP 800-204B discute controle de acesso baseado em atributos com service mesh [31]. Ainda assim, atributos de negócio e decisão final pertencem à aplicação. Mesh não sabe que um analista pode ver chamados da entidade TI, mas não do Jurídico.

13. IA e RAG como dependência não determinística

O pipeline precisa versionar:

  • extrator e configuração de OCR;
  • algoritmo de chunking;
  • modelo de embedding e dimensão;
  • prompt e modelo gerador;
  • documentos recuperados;
  • filtros de autorização;
  • resposta e citações;
  • avaliação de qualidade.

Ollama pode estar indisponível sem impedir download e leitura de documentos. Geração deve possuir timeout e limite de concorrência. Resposta sem evidência suficiente é marcada como incerta; não inventa procedimento. Conteúdo recuperado é dado não confiável e não pode sobrescrever instruções do sistema.

14. SLO, orçamento de erro e sinais

SLOs devem separar jornadas:

  • disponibilidade da leitura de artigos;
  • aceitação de upload;
  • tempo até documento pesquisável;
  • criação e vínculo de chamado;
  • qualidade da resposta fundamentada.

Se o SLO de disponibilidade é S em uma janela de duração T, o orçamento teórico de indisponibilidade é:

\[B = (1-S) \times T\]

Nessa expressão, B é o orçamento de erro, S é o objetivo decimal e T é o tempo total da janela. Para 99,9% em 30 dias, o orçamento é cerca de 43 minutos. O conceito orienta equilíbrio entre confiabilidade e velocidade de mudança, como discutido na literatura de Site Reliability Engineering [45].

Jornada Indicador Meta ilustrativa
abrir artigo taxa de sucesso e p95 99,9%; p95 < 500 ms
aceitar upload sucesso sem duplicidade 99,5%; p95 < 2 s
processar PDF tempo até indexed 95% < 5 min por faixa de tamanho
abrir ticket conclusão ou pendência visível 99% < 2 min
buscar precisão autorizada e latência zero vazamento; p95 < 700 ms

Metas são exemplos; precisam de medição real e compromisso de negócio.

15. Linha do tempo de uma falha

08:55 — nova versão aumenta concorrência de embeddings.
09:01 — Ollama satura GPU/RAM; duração cresce.
09:03 — workers mantêm muitos documentos em processamento.
09:05 — clientes repetem upload porque interface não mostra estado.
09:08 — backlog cresce e Redis pressiona memória.
09:10 — container de worker sofre OOM e jobs são redelivered.
09:14 — efeitos duplicados aparecem porque um handler não era idempotente.

Correções arquiteturais:

  • status e progresso visíveis evitam repetição humana;
  • chave idempotente protege upload;
  • concorrência de IA isolada por semaphore/bulkhead;
  • fila de extração separada da fila de IA;
  • limite de job e tamanho;
  • métricas de idade antecipam backlog;
  • artefatos versionados tornam redelivery seguro;
  • circuit breaker interrompe chamadas improdutivas;
  • estado no PostgreSQL permite reconstrução após Redis;
  • runbook orienta dreno, pausa e replay.

O incidente não possui uma única “causa raiz”. Saturação, falta de feedback, política de retry e ausência de idempotência formam um mecanismo causal.

16. Threat model resumido

Ameaça Superfície Controle
upload malicioso parser/OCR validação, sandbox, limite e scanner
BOLA API de documentos autorização por tenant e objeto
SSRF importação por URL allowlist, egress e resolução segura
prompt injection conteúdo recuperado separação de instrução, filtro e avaliação
segredo em log adapters redaction e log estruturado
evento forjado webhook/broker assinatura, identidade e ACL
dependência comprometida build lock, análise, SBOM e proveniência
abuso de IA endpoint caro quota por tenant e concorrência

O NIST Secure Software Development Framework integra práticas seguras ao ciclo de desenvolvimento [48]. O threat model é revisado quando fluxo, ativo ou fronteira de confiança muda.

17. ADRs principais

ADR-A — Monólito modular com workers separados

Decisão: manter Web/API em uma implantação modular e separar workloads pesados em workers.
Razão: equipe pequena, domínio ainda evoluindo e perfis de carga diferentes.
Consequência: módulos exigem testes de fronteira; workers compartilham contratos internos versionados.
Gatilho de revisão: deploys ou escala de um módulo bloquearem repetidamente o restante.

ADR-B — PostgreSQL como fonte do estado de processamento

Decisão: Redis/RQ transporta trabalho; PostgreSQL registra estado, tentativas e resultados.
Razão: reconstrução e auditoria não podem depender apenas da retenção da fila.
Consequência: existe dupla representação e reconciliação obrigatória.

ADR-C — Outbox para eventos de integração

Decisão: registrar evento e alteração de domínio na mesma transação.
Razão: evitar sucesso no banco sem publicação correspondente.
Consequência: relay, idempotência e monitoramento de pendentes.

ADR-D — Adapter dedicado para GLPI

Decisão: payload, sessão, documentos e vínculo ficam fora do domínio.
Razão: evolução do GLPI não deve contaminar módulos de conhecimento.
Consequência: testes de contrato e mapeamentos por tenant.

18. Evolução orientada por evidências

Fase Arquitetura Gatilho para avançar
1 monólito modular, PostgreSQL, volume, Redis/RQ validar produto e fluxos
2 storage compatível com S3, outbox e OTel escala, backup e diagnóstico
3 busca/IA com workers e filas isoladas saturação e ritmo de deploy próprios
4 serviço extraído por bounded context autonomia de time e driver comprovado
5 streaming/analytics dedicado replay, múltiplos consumidores e volume

Kafka, Kubernetes ou service mesh entram quando requisitos justificam. A arquitetura evolui por pressão observada, não por lista de tecnologias.


Laboratório prático — Do upload ao processamento confiável

Objetivo

Construir um fluxo mínimo que aceite um documento, persista seu estado, registre intenção de job, processe de forma idempotente e exponha diagnóstico. O laboratório deve provar comportamento sob falha.

1. Modelo mínimo

CREATE TABLE documents (
    id uuid PRIMARY KEY,
    tenant_id varchar(80) NOT NULL,
    title varchar(250) NOT NULL,
    storage_key text NOT NULL,
    sha256 char(64) NOT NULL,
    status varchar(30) NOT NULL,
    version integer NOT NULL,
    last_error_code varchar(80),
    created_at timestamptz NOT NULL,
    updated_at timestamptz NOT NULL,
    UNIQUE (tenant_id, sha256, version)
);

CREATE TABLE outbox (
    event_id uuid PRIMARY KEY,
    event_type varchar(120) NOT NULL,
    aggregate_id uuid NOT NULL,
    payload jsonb NOT NULL,
    occurred_at timestamptz NOT NULL,
    published_at timestamptz
);

CREATE INDEX ix_outbox_pending
ON outbox (occurred_at)
WHERE published_at IS NULL;

Adapte a deduplicação ao negócio. O mesmo arquivo pode ser legítimo em tenants distintos ou em novas versões.

2. Porta de fila

from typing import Protocol

class JobDispatcher(Protocol):
    def enqueue_document(self, document_id: str, version: int) -> str: ...

class RqJobDispatcher:
    def __init__(self, queue):
        self.queue = queue

    def enqueue_document(self, document_id, version):
        job_key = f"document:{document_id}:v{version}"
        job = self.queue.enqueue(
            "app.processing.jobs.process_document",
            document_id,
            version,
            job_id=job_key,
            job_timeout="15m",
            result_ttl=86400,
            failure_ttl=604800,
        )
        return job.id

O job_id determinístico reduz agendamento duplicado, mas o handler continua idempotente: retenção ou remoção do job pode permitir nova entrada.

3. Handler idempotente

def process_document(document_id: str, expected_version: int):
    with uow_factory() as uow:
        document = uow.documents.lock(document_id)

        if document.version != expected_version:
            return {"result": "obsolete_version"}

        if document.status in {"indexed", "published"}:
            return {"result": "already_processed"}

        document.start_processing()
        uow.commit()

    try:
        artifact = extractor.extract(document.storage_key)
        index_result = indexer.build(document.id, document.version, artifact)
    except UnsupportedDocument as error:
        mark_failed(document_id, "unsupported_document", retryable=False)
        raise
    except TemporaryModelFailure as error:
        mark_failed(document_id, "model_unavailable", retryable=True)
        raise

    with uow_factory() as uow:
        document = uow.documents.lock(document_id)
        if document.version != expected_version:
            return {"result": "obsolete_after_processing"}
        document.finish_indexing(index_result.artifact_key)
        uow.outbox.add(DocumentIndexed.from_document(document))
        uow.commit()

Operações pesadas ficam fora de transação longa. Isso exige revalidação de versão antes de gravar o resultado. O índice usa chave versionada para que execução concorrente não sobrescreva publicação atual.

4. Relay da outbox

def publish_outbox(batch_size=100):
    events = outbox_repository.claim_pending(batch_size)

    for event in events:
        try:
            broker.publish(
                topic=event.event_type,
                key=str(event.aggregate_id),
                message_id=str(event.event_id),
                payload=event.payload,
            )
            outbox_repository.mark_published(event.event_id)
        except TransientBrokerError:
            outbox_repository.release(event.event_id)

claim_pending precisa impedir que dois relays processem indefinidamente o mesmo lote, por exemplo com lock e SKIP LOCKED. Marcar após publicar preserva at-least-once. Consumidor deduplica event_id.

5. Endpoint de status

{
  "id": "doc-42",
  "version": 7,
  "status": "processing",
  "progress": {
    "stage": "embedding",
    "completed_units": 38,
    "total_units": 120
  },
  "links": {
    "self": "/v1/documents/doc-42",
    "latest_job": "/v1/documents/doc-42/processing-jobs/latest"
  }
}

Progresso deve vir de marcos confiáveis, não de um contador inventado que chega a 99% e para. Se total é desconhecido, mostre etapa e tempo, não porcentagem falsa.

6. Testes obrigatórios

  1. enviar duas vezes a mesma chave e payload: retorna o mesmo recurso;
  2. reutilizar chave com payload diferente: 409;
  3. matar worker após extrair e antes do commit final: redelivery sem duplicar publicação;
  4. reiniciar Redis: documentos pendentes continuam identificáveis no banco;
  5. indisponibilizar Ollama: leitura permanece funcional e job entra em retry limitado;
  6. alterar versão durante job: resultado antigo não vira versão publicada;
  7. tentar ler documento de outro tenant: resposta não revela existência;
  8. simular outbox publicada sem marcação: consumidor ignora duplicata;
  9. deixar job parado: reconciliação o detecta;
  10. enviar arquivo grande ou inválido: rejeição controlada e sem resíduo órfão.

7. Métricas do laboratório

http_requests_total{route,status}
http_request_duration_seconds{route}
documents_total{status,tenant}
document_processing_duration_seconds{stage}
queue_oldest_job_age_seconds{queue}
jobs_total{result,error_code}
outbox_pending_total
external_requests_total{system,result}

Evite document_id, user_id ou URL bruta como label de métrica. Esses valores geram alta cardinalidade; pertencem a logs e traces controlados.

8. Critério de conclusão

O laboratório termina quando as falhas acima são reproduzidas e explicadas por evidências. “Funcionou uma vez” valida apenas o caminho feliz. Arquitetura confiável é demonstrada pela capacidade de conter, observar e recuperar falhas.


Projeto prático final — Arquitetura de um ecossistema corporativo

Cenário

Uma empresa possui portal Web, aplicativo, ERP, CRM, GLPI, identidade corporativa, automações n8n, analytics e uma plataforma de IA. O objetivo é conectar solicitações, documentos e conhecimento sem criar um banco compartilhado universal.

Entregáveis

  1. Contexto: pessoas, sistema em foco e sistemas externos.
  2. Containers: aplicações, stores, workers, brokers e protocolos.
  3. Mapa de domínio: bounded contexts, owner e linguagem.
  4. Contratos: uma API OpenAPI, um evento AsyncAPI/CloudEvents e um lote por arquivo.
  5. Dados: sistema de registro, réplicas, retenção e reconciliação.
  6. Qualidade: cinco cenários mensuráveis e SLOs.
  7. Segurança: ativos, fronteiras, ameaças e controles.
  8. Confiabilidade: timeout, retry, idempotência, DLQ, outbox e restore.
  9. Decisões: pelo menos cinco ADRs com alternativas.
  10. Operação: sinais, dashboards, alertas e runbooks.
  11. Evolução: roadmap com gatilhos, não apenas datas.
  12. Custos: componentes, unidades de consumo e limite de orçamento.

Rubrica de avaliação

Critério Peso Evidência esperada
alinhamento negócio–arquitetura 20% drivers ligados a decisões
fronteiras e dados 15% ownership e consistência explícitos
contratos 15% exemplos, erros e compatibilidade
confiabilidade 15% falhas parciais e recuperação
segurança 15% autorização por objeto e trust boundaries
operação 10% métricas, traces e runbooks
simplicidade e trade-offs 10% alternativas e custo reconhecidos

Uma solução com mais tecnologias não recebe nota maior. A avaliação considera coerência, justificativa e capacidade de verificação.


Exercícios gerais da Parte 4

Os 48 exercícios distribuídos pelos capítulos compõem a avaliação principal. As orientações abaixo não substituem a resolução; indicam o raciocínio mínimo esperado.

Orientações de resposta

1. Defina carga, concorrência, volume, percentil e limite temporal. “Escalar automaticamente” não é cenário mensurável.
2. Diferencie negócio, operação e segurança; não atribua a todos a mesma preocupação.
3. Compare durabilidade, custo, backup, acesso e migração; registre consequência e gatilho de revisão.
4. Mostre dependências serialmente obrigatórias e use a multiplicação apenas com a hipótese declarada.
5. O contexto contém pessoas e sistemas externos; omita banco, classes e containers internos.
6. Relacione a decisão à capacidade real da equipe, como plantão, observabilidade ou domínio de tecnologia.

7. Apresente responsabilidades e direção de dependências, não apenas nomes de pastas.
8. Mostre por que request, domínio, persistência, resposta e evento mudam por razões distintas.
9. Faça commit da regra e da outbox localmente; trate publicação como passo repetível.
10. Uma implementação temporária viola o contrato de durabilidade mesmo com a mesma assinatura.
11. Aponte dependências de dados, deploy e chamadas síncronas que mantêm o sistema acoplado.
12. Use a alternativa mais simples que localize mudanças; não escolha estilo apenas por familiaridade.

13. Classifique identidade, payload, tempo, operação e ownership no fluxo Slack–n8n–GLPI.
14. A fila permite consumidor offline, mas produtor e consumidor continuam ligados ao significado do evento.
15. Compare pré-condições, pós-condições, erros e efeitos, não somente os métodos.
16. Separe leitura, escrita e anexos conforme necessidades dos consumidores.
17. O resultado é 0,75; interprete direção das dependências e contexto antes de concluir.
18. Identifique custo de navegação ou manutenção removido pela simplificação.

19. Declare sistema de registro, réplica, frequência e regra de conflito para cada dado.
20. Webhook reduz latência e polling reduz dependência do emissor; compare retry, segurança e reconciliação.
21. Combine identidade do evento Slack, tenant e operação; persista payload hash e resultado.
22. A operação pode ter concluído e somente a resposta ter se perdido.
23. Diferencie erro transitório, permanente, autenticação, limite e resultado incerto.
24. Compare registros Document com vínculos Document_Item e permita reparo sem recriar ticket.

25. Use recursos e transições; justifique quando um comando de domínio é mais claro que PATCH.
26. Inclua type, title, status, detail, instance, código estável e trace_id, sem stack trace.
27. Considere tipo, obrigatoriedade, semântica, ordem, autorização e códigos.
28. Cursor favorece lista mutável e grande; offset favorece salto simples e conjuntos menores.
29. Login prova identidade, mas não confere tenant, papel, função nem propriedade do objeto.
30. Limite tokens, tamanho, concorrência, duração, custo por tenant e fila, além de taxa HTTP.

31. Compare custo operacional com necessidade de autonomia; equipe pequena tende ao monólito modular.
32. Fronteiras devem representar capacidades e linguagens, não tabelas.
33. Inclua estados persistidos e compensação sem fingir que efeitos externos podem ser apagados.
34. Três tentativas por camada podem gerar até 27 chamadas no pior caminho.
35. Expanda schema, atualize leitores/escritores, faça backfill e só então contraia.
36. Escolha pela diferença de escala, falha, deploy ou ownership e defina métrica de sucesso.

37. ProcessPdf é comando/job conforme o contrato; DocumentIndexed é fato; SendSlackNotification é comando.
38. Confirm é relação produtor–broker; ack é relação broker–consumidor; efeito externo exige evidência adicional.
39. Use documento, versão, modelo e configuração como identidade do artefato.
40. Particione por document_id quando a ordem relevante é por documento.
41. Pela Lei de Little, L = 5 × 20 = 100 jobs médios no sistema.
42. Corrija a causa, selecione mensagens, simule, reenvie com limite e confirme deduplicação.

43. Nomeie o problema que Factory resolve; se apenas chama construtor, remova-a.
44. Strategy deve preservar contrato de saída e invariantes apesar do algoritmo variável.
45. Adapter traduz interface; Facade simplifica acesso a um conjunto de operações.
46. Repository serve ao domínio; query service pode retornar projeção otimizada para leitura.
47. Event Sourcing usa eventos como fonte de verdade; auditoria registra fatos ao lado do estado atual.
48. Responda contexto, força, alternativa simples, benefício, custo, verificação e condição de abandono.

Active Recall

Sem consultar o texto, explique:

  1. Por que diagrama não é a própria arquitetura?
  2. Como transformar um atributo de qualidade em cenário?
  3. Qual a diferença entre módulo e processo?
  4. Que dimensões de acoplamento uma API mantém?
  5. Por que contrato é maior que schema?
  6. Quando 202 Accepted é apropriado?
  7. Como BOLA ocorre mesmo com token válido?
  8. O que torna deploy de microsserviço realmente independente?
  9. Qual a diferença entre saga e transação distribuída global?
  10. Por que retry pode causar indisponibilidade?
  11. O que publisher confirm, ack e idempotência garantem separadamente?
  12. Como Outbox fecha uma janela de falha?
  13. Onde a ordem de mensagens costuma ser preservada?
  14. O que distingue evento de comando?
  15. Quando CQRS não exige infraestrutura separada?
  16. Como reconhecer pattern soup?

Método Feynman

Explique a uma pessoa não técnica:

  1. por que arquitetura é um conjunto de decisões e não um desenho;
  2. por que um sistema pode confirmar o recebimento antes de terminar o trabalho;
  3. como uma chave evita criar dois chamados quando a rede falha;
  4. por que dividir um programa em muitos serviços pode torná-lo mais frágil;
  5. como uma fila absorve pico e por que ela também pode ficar congestionada;
  6. por que o sistema precisa saber quem é dono de cada informação;
  7. por que “usuário autenticado” não significa “pode ver qualquer documento”;
  8. como logs, métricas e traces contam partes diferentes da mesma história.

Mapa das notas Zettelkasten

Faixa Núcleo conceitual
ZK-060 a ZK-062 decisão, qualidade e memória arquitetural
ZK-063 a ZK-065 fronteiras, modelos e transação local
ZK-066 a ZK-068 acoplamento, coesão e SOLID
ZK-069 a ZK-071 integração, contrato e reconciliação
ZK-072 a ZK-074 API, compatibilidade e autorização
ZK-075 a ZK-077 autonomia, consistência e custo distribuído
ZK-078 a ZK-080 estado assíncrono, entrega e operação da fila
ZK-081 a ZK-083 padrões, fronteiras e simplicidade

As notas continuam a numeração da Parte 3 e devem ser conectadas às notas anteriores sobre sistemas operacionais, rede, dados, containers, cloud e segurança.


Glossário técnico

Acknowledgement (ack): confirmação do consumidor ao broker conforme a política de processamento.

ADR: registro do contexto, decisão arquitetural, alternativas e consequências.

Anti-Corruption Layer: fronteira que traduz um modelo externo e protege o modelo interno.

API Gateway: entrada controlada para roteamento, autenticação, limites e políticas de APIs.

At-least-once: entrega que evita perda dentro das garantias assumidas, admitindo duplicidade.

Backpressure: mecanismo para impedir que produtores excedam indefinidamente a capacidade de processamento.

Bounded Context: limite dentro do qual um modelo e sua linguagem possuem significado consistente.

BOLA: falha de autorização em nível de objeto, comum quando apenas o identificador é alterado.

Bulkhead: isolamento de recursos para conter saturação ou falha em uma partição.

C4 Model: abstrações e diagramas de contexto, containers, componentes e código.

Circuit Breaker: máquina de estados que interrompe chamadas provavelmente destinadas a falhar.

CloudEvents: especificação de atributos comuns para descrição de eventos.

Coesão: grau em que elementos colaboram para uma responsabilidade e razão de mudança comuns.

Command: mensagem que expressa intenção dirigida a um responsável.

Consumer: componente que recebe e processa mensagem ou evento.

Contract test: teste que verifica se produtor e consumidor preservam um contrato de integração.

Correlation ID: identificador que liga registros e etapas de uma operação.

CQRS: separação de responsabilidades e modelos de comando e consulta.

Dead Letter Queue: destino controlado para mensagens que não puderam ser processadas pela política normal.

Domain event: fato relevante ocorrido dentro de um domínio.

ETag: validador de representação HTTP, útil para cache e concorrência otimista.

Event Sourcing: persistência em que eventos aceitos são a fonte autoritativa do estado.

Facade: interface simplificada para um subsistema mais complexo.

Idempotência: propriedade que permite repetir intenção equivalente sem duplicar o efeito observável.

Inbox: registro usado pelo consumidor para deduplicar mensagens processadas.

Jitter: variação aleatória adicionada ao atraso de retry para evitar repetição sincronizada.

Job: unidade técnica de trabalho destinada a processamento em background.

Monólito distribuído: serviços fisicamente separados que continuam acoplados em deploy, dados ou execução.

Monólito modular: uma unidade de implantação com fronteiras internas explícitas.

Observabilidade: capacidade de inferir o estado interno por evidências como traces, métricas e logs.

OpenAPI: especificação legível por máquinas para descrever APIs HTTP.

Outbox: registro transacional de mensagens a publicar após alteração local.

Owner: pessoa ou equipe responsável por evolução e operação de uma capacidade.

Problem Details: formato padronizado pelo RFC 9457 para erros de APIs HTTP.

Producer: componente que publica comando, job ou evento.

Publisher confirm: confirmação do broker ao produtor dentro das garantias configuradas.

Rate limit: controle da taxa de consumo em determinada janela e chave.

Reconciliação: comparação entre fontes e estados para encontrar e reparar divergências.

Repository: abstração orientada ao domínio que encapsula acesso e reconstrução de agregados.

Retry storm: amplificação de carga causada por repetição simultânea ou em múltiplas camadas.

Saga: sequência de transações locais coordenadas com ações compensatórias.

Schema: definição estrutural e de validação de dados; não contém toda a semântica do contrato.

Service discovery: resolução de nome lógico para instâncias de serviço disponíveis.

SLO: objetivo mensurável para um indicador de serviço em uma janela.

Trace: representação do caminho distribuído de uma operação por spans correlacionados.

Unit of Work: padrão que coordena alterações e delimita uma transação local.

Vertical Slice: organização por caso de uso ou funcionalidade ponta a ponta.

Webhook: requisição iniciada por um produtor para notificar evento a um receptor.


Referências técnicas e bibliográficas

As especificações e documentações Web foram verificadas em 2 de setembro de 2026. Em implantação, confirme a versão efetivamente suportada por cada produto.

[1] ISO/IEC/IEEE. 42010:2022 — Software, systems and enterprise — Architecture description. Disponível em: https://www.iso.org/standard/74393.html.

[2] ISO/IEC. 25010:2023 — Systems and software engineering — Product quality model. Disponível em: https://www.iso.org/standard/78176.html.

[3] BROWN, Simon. The C4 Model for Visualising Software Architecture. Disponível em: https://c4model.com/.

[4] JSON SCHEMA. Draft 2020-12. Disponível em: https://json-schema.org/draft/2020-12.

[5] MICROSOFT. Anti-Corruption Layer Pattern. Azure Architecture Center. Disponível em: https://learn.microsoft.com/azure/architecture/patterns/anti-corruption-layer.

[6] FIELDING, Roy Thomas. Architectural Styles and the Design of Network-based Software Architectures. University of California, Irvine, 2000. Disponível em: https://roy.gbiv.com/pubs/dissertation/top.htm.

[7] IETF. RFC 9110 — HTTP Semantics. Disponível em: https://www.rfc-editor.org/info/rfc9110.

[8] IETF. RFC 9457 — Problem Details for HTTP APIs. Disponível em: https://www.rfc-editor.org/info/rfc9457.

[9] OPENAPI INITIATIVE. OpenAPI Specification 3.2.0. Disponível em: https://spec.openapis.org/oas/v3.2.0.html.

[10] OWASP. API Security Top 10 — 2023. Disponível em: https://owasp.org/API-Security/editions/2023/en/0x11-t10/.

[11] IETF. RFC 9700 — Best Current Practice for OAuth 2.0 Security. Disponível em: https://www.rfc-editor.org/info/rfc9700.

[12] OPENID FOUNDATION. OpenID Connect Core 1.0 incorporating errata set 2. Disponível em: https://openid.net/specs/openid-connect-core-1_0.html.

[13] IETF. RFC 8705 — OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens. Disponível em: https://www.rfc-editor.org/info/rfc8705.

[14] IETF. RFC 9449 — OAuth 2.0 Demonstrating Proof of Possession. Disponível em: https://www.rfc-editor.org/info/rfc9449.

[15] IETF. RFC 6585 — Additional HTTP Status Codes. Disponível em: https://www.rfc-editor.org/info/rfc6585.

[16] W3C. Trace Context. Disponível em: https://www.w3.org/TR/trace-context/.

[17] OPEN TELEMETRY. OpenTelemetry Specification. Disponível em: https://opentelemetry.io/docs/specs/otel/.

[18] IETF. RFC 9745 — The Deprecation HTTP Response Header Field. Disponível em: https://www.rfc-editor.org/info/rfc9745.

[19] IETF. RFC 8594 — The Sunset HTTP Header Field. Disponível em: https://www.rfc-editor.org/info/rfc8594.

[20] GRAPHQL FOUNDATION. GraphQL Specification. Disponível em: https://spec.graphql.org/.

[21] GRPC AUTHORS. Introduction to gRPC. Disponível em: https://grpc.io/docs/what-is-grpc/introduction/.

[22] W3C. SOAP Version 1.2 Part 1: Messaging Framework. Disponível em: https://www.w3.org/TR/soap12-part1/.

[23] NEWMAN, Sam. Building Microservices. 2. ed. O’Reilly, 2021.

[24] LEWIS, James; FOWLER, Martin. Microservices: a definition of this new architectural term. Disponível em: https://martinfowler.com/articles/microservices.html.

[25] NIST. SP 800-204 — Security Strategies for Microservices-based Application Systems. Disponível em: https://csrc.nist.gov/pubs/sp/800/204/final.

[26] NIST. SP 800-204A — Building Secure Microservices-based Applications Using Service-Mesh Architecture. Disponível em: https://csrc.nist.gov/pubs/sp/800/204/a/final.

[27] GARCIA-MOLINA, Hector; SALEM, Kenneth. Sagas. ACM SIGMOD, 1987. DOI: https://doi.org/10.1145/38713.38742.

[28] MICROSOFT. Retry Storm Antipattern. Azure Architecture Center. Disponível em: https://learn.microsoft.com/azure/architecture/antipatterns/retry-storm/.

[29] FOWLER, Martin. Strangler Fig Application. Disponível em: https://martinfowler.com/bliki/StranglerFigApplication.html.

[30] KUBERNETES. Service — Networking. Disponível em: https://kubernetes.io/docs/concepts/services-networking/service/.

[31] NIST. SP 800-204B — Attribute-based Access Control for Microservices-based Applications Using a Service Mesh. Disponível em: https://csrc.nist.gov/pubs/sp/800/204/b/final.

[32] EVANS, Eric. Domain-Driven Design: Tackling Complexity in the Heart of Software. Addison-Wesley, 2003.

[33] RABBITMQ. Consumer Acknowledgements and Publisher Confirms. Disponível em: https://www.rabbitmq.com/docs/confirms.

[34] RABBITMQ. Quorum Queues. Disponível em: https://www.rabbitmq.com/docs/quorum-queues.

[35] APACHE KAFKA. Documentation. Disponível em: https://kafka.apache.org/documentation/.

[36] CNCF. CloudEvents Specification 1.0.2. Disponível em: https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md.

[37] ASYNCAPI INITIATIVE. AsyncAPI Specification 3.0.0. Disponível em: https://www.asyncapi.com/docs/reference/specification/v3.0.0.

[38] OASIS. Advanced Message Queuing Protocol — AMQP Version 1.0. Disponível em: https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-overview-v1.0-os.html.

[39] RICHARDSON, Chris. Pattern: Transactional Outbox. Disponível em: https://microservices.io/patterns/data/transactional-outbox.html.

[40] KLEPPMANN, Martin. Designing Data-Intensive Applications. O’Reilly, 2017.

[41] GAMMA, Erich et al. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994.

[42] FOWLER, Martin et al. Patterns of Enterprise Application Architecture. Addison-Wesley, 2002.

[43] MARTIN, Robert C. Clean Architecture: A Craftsman’s Guide to Software Structure and Design. Prentice Hall, 2017.

[44] PARNAS, David L. On the Criteria To Be Used in Decomposing Systems into Modules. Communications of the ACM, 1972. DOI: https://doi.org/10.1145/361598.361623.

[45] BEYER, Betsy et al. Site Reliability Engineering: How Google Runs Production Systems. O’Reilly, 2016. Disponível em: https://sre.google/sre-book/table-of-contents/.

[46] IETF. RFC 3339 — Date and Time on the Internet: Timestamps. Disponível em: https://www.rfc-editor.org/info/rfc3339.

[47] IETF. RFC 9562 — Universally Unique IDentifiers (UUIDs). Disponível em: https://www.rfc-editor.org/info/rfc9562.

[48] NIST. SP 800-218 — Secure Software Development Framework Version 1.1. Disponível em: https://csrc.nist.gov/pubs/sp/800/218/final.

Leituras complementares

  • BASS, Len; CLEMENTS, Paul; KAZMAN, Rick. Software Architecture in Practice. 4. ed. Addison-Wesley, 2021.
  • FORD, Neal et al. Software Architecture: The Hard Parts. O’Reilly, 2021.
  • HOHPE, Gregor; WOOLF, Bobby. Enterprise Integration Patterns. Addison-Wesley, 2003.
  • RICHARDSON, Chris. Microservices Patterns. Manning, 2018.
  • RICHARDS, Mark; FORD, Neal. Fundamentals of Software Architecture. O’Reilly, 2020.
  • VERNON, Vaughn. Implementing Domain-Driven Design. Addison-Wesley, 2013.

Encerramento da Parte 4

Esta parte começou com uma pergunta sobre organização de software e terminou com uma constatação operacional: arquitetura é a disciplina de preservar capacidades e qualidades quando componentes, equipes, dados e dependências mudam.

Camadas protegem razões de mudança. Módulos delimitam responsabilidades. APIs e eventos tornam contratos visíveis. Filas deslocam trabalho no tempo, mas exigem estado, idempotência e observação. Microsserviços compram autonomia ao preço da distribuição. Padrões dão nomes a soluções recorrentes, mas só criam valor quando respondem a forças reais.

O Analista de Sistemas não precisa defender monólito ou microsserviços como identidade. Precisa demonstrar:

  • qual problema de negócio está sendo resolvido;
  • quais atributos de qualidade são prioritários;
  • onde vivem dados e responsabilidades;
  • que falhas são possíveis;
  • como repetir, reparar e reconciliar;
  • como autorização acompanha cada objeto;
  • quais evidências validarão a decisão;
  • quando a arquitetura deve ser revista.

Com isso, o Volume I fecha seus fundamentos: do bit ao sistema corporativo; da execução local ao contrato distribuído; da funcionalidade isolada à operação sustentável. O Volume II poderá aprofundar requisitos, UML, BPMN, modelagem de processos, histórias de usuário e documentação funcional sem tratar diagramas como fim em si mesmos. Cada modelo passará a servir às decisões e aos riscos estabelecidos aqui.


Ficha editorial da Parte 4

Tema central: organização, integração e evolução de sistemas corporativos.
Tese: a menor arquitetura adequada é aquela que torna fronteiras, contratos, falhas, dados e evidências explícitos sem adicionar distribuição desnecessária.
Público: estudantes e profissionais de Análise e Desenvolvimento de Sistemas, sustentação, integração, desenvolvimento Web e arquitetura.
Aplicações: KnowledgeBasePro, BookWave, Flask, GLPI, n8n, Slack, Redis/RQ, RabbitMQ, Kafka, PostgreSQL, Nginx e Ollama.
Continuidade: Volume II — Engenharia de Requisitos, Modelagem, UML, BPMN e Análise de Sistemas Aplicada.

Fim do artigo Volume I — Parte 4 — Arquitetura de Software, Integração entre Sistemas, Padrões de Projeto, Microsserviços, Mensageria e APIs Corporativas