APIs, contratos e integrações: fundamentos, prática e diagnóstico
Este capítulo integra o modelo mental e a prática de apis, contratos e integrações em uma única jornada didática. O leitor começa pelo problema operacional, aprende o vocabulário — incluindo HTTP, recurso, endpoint, método, header — e localiza responsabilidades e fronteiras antes de comparar decisões. Em seguida aplica o conceito ao sistema de manutenção conectado ao ERP, executa a menor fatia verificável e provoca falhas de permissão, timeout, duplicata, concorrência ou dado inválido. O caderno operacional mostra como observar o resultado, diagnosticar a primeira divergência e produzir evidência reproduzível. Segurança, custo, critérios de aceite e perguntas de recuperação encerram a unidade sem separar teoria de operação.
APIs, contratos e integrações: modelo mental e decisões
O pedido simples que atravessa muitos sistemas
Um eletricista toca em Concluir OS 742. A tela exibe “concluída”, mas o ERP está lento. A operação terminou? A resposta correta depende do contrato. O sistema de manutenção pode ter persistido a conclusão local e deixado a sincronização como pendente; pode ter exigido confirmação imediata do ERP; ou pode ter falhado antes de gravar qualquer coisa. Um simples 200 OK não explica sozinho qual desses mundos existe.
É por isso que uma API — Application Programming Interface, ou interface de programação de aplicações — não é apenas uma URL. Ela é uma fronteira pela qual um software oferece dados ou operações a outro sob regras conhecidas. Numa API web, essas mensagens normalmente usam HTTP — Hypertext Transfer Protocol. Os dados podem ser representados em JSON — JavaScript Object Notation, mas Java, Python, TypeScript e outras linguagens continuam executando a lógica de cada lado.
Pense numa API como um painel de comando autorizado. O painel expõe operações específicas, exige parâmetros, informa resultados e esconde mecanismos internos. A analogia ajuda, mas tem limite: numa rede, a resposta pode se perder depois que a operação ocorreu, duas pessoas podem agir ao mesmo tempo e uma mensagem pode ser repetida. O contrato precisa tratar essas situações, não apenas o caminho feliz.
Quatro coisas que não devem ser confundidas
- HTTP é o protocolo da conversa. Define a semântica de mensagens, métodos, campos e códigos. A referência atual de semântica é a RFC 9110.
- JSON é uma representação de dados. O objeto
{"status":"concluida"}não consulta banco, não autentica usuário e não executa Java; apenas representa nomes e valores. - A API é a interface contratada. Ela define operações, dados, erros, segurança e comportamento observável.
- O backend implementa regras e coordena dependências. Pode usar banco, fila, arquivos e ERP, além de oferecer uma API.
O aplicativo não deveria consultar diretamente tabelas internas do ERP. Se o fornecedor renomear uma coluna, todos os consumidores quebrariam; pior, o aplicativo poderia ignorar validações e auditoria. A API protege a regra por trás de um contrato mais estável.
Anatomia de uma chamada HTTP
Uma requisição é a mensagem do cliente para o servidor:
POST /api/v1/work-orders/742/completion HTTP/1.1
Host: manutencao.exemplo.com
Authorization: Bearer <credencial-redigida>
Content-Type: application/json
Accept: application/json
Idempotency-Key: 018f-demo-742
traceparent: 00-<trace-id>-<span-id>-01
{
"reading": 328.4,
"evidenceIds": ["ev-19"],
"completedAt": "2026-08-09T13:40:00-03:00"
}O método POST comunica a intenção geral de processar a representação enviada. O caminho identifica a operação sobre a OS 742. Os headers carregam metadados: credencial, formatos, chave de repetição e rastreamento. O body contém os dados. HTTPS significa HTTP protegido por TLS durante o transporte; não corrige autorização errada, segredo em log ou regra vulnerável.
Uma resposta possível é:
HTTP/1.1 202 Accepted
Content-Type: application/json
Location: /api/v1/integrations/erp/jobs/job-91
Retry-After: 5
{
"workOrderId": "742",
"localStatus": "completed",
"erpSync": "pending",
"jobId": "job-91"
}202 Accepted significa que o pedido foi aceito para processamento, não que o ERP confirmou. O recurso em Location torna o estado consultável. Se a alteração estivesse concluída integralmente, 200 OK ou outro status adequado ao contrato poderia ser usado. Se um novo recurso tivesse sido criado, 201 Created costuma vir acompanhado de sua localização.
Erros também são contrato. O formato Problem Details, definido pela RFC 9457, permite devolver campos consistentes:
{
"type": "https://manutencao.exemplo/problems/invalid-transition",
"title": "Transição não permitida",
"status": 409,
"detail": "Uma OS cancelada não pode ser concluída.",
"instance": "/api/v1/work-orders/742/completion"
}Não exponha stack trace, SQL, token ou existência de recurso de outro tenant no detalhe.
Método, recurso e regra de negócio
Os métodos HTTP carregam semântica, mas não substituem o domínio:
GET /work-orders/742consulta uma representação e não deveria produzir efeito de negócio.POST /work-orderssolicita criação.PUT /work-orders/742representa substituição do estado definido pelo contrato.PATCH /work-orders/742aplica modificação parcial.DELETE /work-orders/742solicita remoção conforme política — possivelmente lógica, auditada ou proibida.
Operações como “concluir” podem ser modeladas como transição explícita (POST /work-orders/742/completion) quando isso deixa regras, auditoria e idempotência mais claras. Não existe uma árvore universal de URLs. O critério é a semântica observável e estável, não a aparência “REST perfeita”.
Status importantes para o caso:
| Status | Significado operacional possível |
|---|---|
| 200 | operação concluída e representação devolvida |
| 202 | aceita, ainda pendente; precisa de acompanhamento |
| 400 | mensagem malformada ou parâmetro básico inválido |
| 401 | credencial válida não foi apresentada |
| 403 | identidade conhecida, ação não permitida |
| 404 | recurso não encontrado — ou ocultado por política |
| 409 | conflito com o estado atual ou chave reutilizada indevidamente |
| 412 | precondição, como versão esperada, não foi atendida |
| 422 | formato compreensível, mas dados violam validações declaradas |
| 429 | limite de chamadas atingido; o cliente deve respeitar orientação |
| 500 | erro inesperado do servidor, sem revelar detalhe interno |
| 503 | serviço temporariamente indisponível |
O aplicativo não deve transformar todo 4xx em “senha errada” nem todo 5xx em retry infinito.
Uma chamada não é um registro
Pedir dez usuários ou mil ordens pode continuar sendo uma chamada HTTP. O que muda é o volume de dados, o tempo de consulta, memória, transferência e impacto no banco. “Uma chamada” mede mensagens; “mil registros” mede itens dentro da resposta. Modelos de IA acrescentam outra unidade chamada token, mas ela não altera a contagem HTTP.
Uma API segura evita “retornar tudo” por padrão. Use paginação, filtros e limites:
GET /api/v1/work-orders?status=open&limit=50&cursor=eyJpZCI6Ijc0MiJ9A resposta inclui itens e cursor seguinte. Cursores ajudam listas que mudam durante a navegação; offset pode pular ou repetir itens quando novas linhas entram. Defina limite máximo no servidor, não apenas na interface.
O contrato tem mais de uma camada
Um schema descreve estrutura: campos, tipos, obrigatoriedade e limites. JSON Schema 2020-12 fornece vocabulário para essa validação. OpenAPI descreve operações HTTP, parâmetros, schemas, autenticação e respostas. Mas contrato não termina no YAML:
- estrutural: formato e tipos;
- semântico: o que “completed” significa e quais transições são válidas;
- de segurança: quem pode ver ou alterar cada objeto;
- temporal: timeout, consistência, expiração e estado pendente;
- operacional: limites, observabilidade, suporte e depreciação;
- evolutivo: mudanças compatíveis, janela e migração.
Adicionar um campo opcional costuma ser compatível para clientes tolerantes, mas adicionar valor a um enum pode quebrar um switch fechado. Renomear campo, mudar tipo ou alterar significado é incompatível mesmo que a URL continue igual. Testes de contrato verificam produtor e consumidores; telemetria mostra quem ainda usa a forma antiga.
Repetição, concorrência e resposta perdida
Imagine: o cliente envia “concluir OS”, o servidor grava, mas a conexão cai antes da resposta. O cliente não sabe se deve repetir. A idempotência garante que a mesma intenção, identificada por uma chave e um escopo, produza um único efeito lógico. A chave não torna qualquer operação idempotente sozinha: o servidor precisa armazenar chave, identidade, impressão do pedido e resultado. Reutilizar a mesma chave com conteúdo diferente deve falhar.
Concorrência é outro problema. O supervisor lê a versão 7 enquanto um técnico altera a OS para versão 8. Com ETag: "v7" e If-Match: "v7", o servidor pode responder 412 Precondition Failed em vez de sobrescrever a versão nova. Idempotência trata repetição da mesma intenção; controle de versão trata intenções concorrentes.
Síncrono, assíncrono, webhook, fila ou arquivo?
sequenceDiagram
actor U as Eletricista
participant W as Aplicativo
participant A as API de manutenção
participant D as Banco + outbox
participant R as ERP
U->>W: Concluir OS 742
W->>A: POST + identidade + chave
A->>D: Estado local e evento na mesma transação
A-->>W: 202 local concluído, ERP pendente
D->>R: Entrega assíncrona e idempotente
R-->>D: Confirmação ou timeout
W->>A: GET job-91
A-->>W: sincronizado ou ainda pendenteUse chamada síncrona quando o cliente precisa do resultado imediato e a dependência cabe no orçamento de latência. Use processamento assíncrono quando o trabalho demora ou pode continuar depois da resposta. Uma outbox grava estado e intenção de publicar na mesma transação, reduzindo o risco de salvar a OS e perder o evento.
Um webhook permite ao sistema de origem notificar o consumidor, mas deve ser autenticado, protegido contra replay e processado de forma idempotente; entrega pode duplicar ou atrasar. Polling — consultar periodicamente — é mais simples em alguns cenários e serve como reconciliação. Filas desacoplam disponibilidade e absorvem picos, mas adicionam lag, repetição e operação. Arquivos CSV/XML via canal controlado continuam válidos para lote diário ou fornecedor legado. Escolha pela necessidade, não pela novidade.
Segurança aplicada à fronteira
O OWASP API Security Top 10 de 2023 funciona como catálogo de riscos, não como threat model pronto. Para a OS 742, teste especialmente:
- autorização por objeto: saber o ID 742 não concede acesso; valide site e papel no servidor;
- autorização por função: técnico pode solicitar, supervisor pode aprovar;
- consumo irrestrito: limite corpo, paginação, frequência, concorrência e custo;
- inventário: documente versões e retire endpoints antigos;
- integração insegura: trate resposta do ERP e webhook como dados não confiáveis.
Tokens ficam em headers e logs devem redigi-los. Query strings podem aparecer em histórico, proxy e analytics; não coloque segredo ou dado pessoal nelas. Valide o body antes do domínio, mas revalide regras com a fonte de verdade. O frontend melhora a experiência; não é a autoridade final.
Diagnóstico: o que realmente falhou?
| Sintoma | Hipótese | Evidência | Ação segura |
|---|---|---|---|
| cliente recebeu timeout | rede, API ou ERP lento | trace e estado pela chave | consultar antes de repetir |
| duas baixas no estoque | idempotência ausente | chave, auditoria e efeitos ERP | conter worker e reconciliar |
| somente outro site falha | autorização por objeto | identidade, site e policy | corrigir regra sem abrir acesso |
202 nunca converge |
worker parado ou mensagem morta | lag, retries e dead-letter | reparar dependência e reprocessar |
| clientes quebram após campo novo | parser rígido ou enum fechado | versão e erro por consumidor | restaurar compatibilidade e migrar |
| dashboard verde, usuário bloqueado | métrica mede processo, não jornada | E2E e correlation ID | redefinir SLI da operação |
Logs, métricas e traces são complementares; os conceitos do OpenTelemetry ajudam a correlacionar a jornada. Não registre payload integral por conveniência.
Critérios de aceite do desenho
- A operação declara ator, recurso, precondições, pós-condições e efeitos proibidos.
- Requisição, respostas de sucesso, pendência e erro possuem exemplos e schemas.
- Autorização por objeto e função é validada no servidor e testada negativamente.
- Repetir a mesma chave e intenção produz um efeito lógico; conteúdo diferente é rejeitado.
- Concorrência perdida é impedida ou resolvida explicitamente.
- Timeout não dispara repetição insegura; estado pode ser consultado.
- Integração com ERP possui modo degradado, reconciliação e owner.
- Mudanças incompatíveis têm migração, telemetria e prazo de depreciação.
- Logs e traces permitem diagnóstico sem segredo ou dado pessoal desnecessário.
Exercício, desafio e recuperação ativa
Exercício: escreva o contrato de “concluir OS” contendo método, caminho, headers, body, cinco respostas, regra de autorização, idempotência, timeout e estado pendente. Desenhe o caminho até o ERP e marque onde cada validação ocorre.
Entregável: uma especificação OpenAPI mínima, uma tabela semântica de transições, casos positivos e negativos e uma decisão sobre integração síncrona ou assíncrona. Outra pessoa deve conseguir simular timeout e prever o estado correto.
Desafio: o ERP aceita a conclusão, mas devolve timeout; simultaneamente, um webhook de confirmação chega duas vezes e fora de ordem. Proponha um algoritmo que converta tudo em um único estado auditável. Explique quais garantias pertencem à API, à fila, ao consumidor e ao ERP.
Sem consultar
- Por que JSON não é Java nem API?
- O que muda entre uma chamada com 10 e uma com 1.000 registros?
- Qual diferença entre
401,403e404deliberado? - Por que
202precisa de acompanhamento? - Como idempotência difere de concorrência otimista?
- Quando webhook e polling devem coexistir?
Transição
No caderno operacional, você implementará um contrato mínimo em TypeScript, executará casos de sucesso, entrada inválida, acesso horizontal, timeout e duplicata, e guardará evidência reproduzível. O livro de alta disponibilidade e recuperação aprofunda como restaurar banco, outbox e integração a um estado coerente.
Fontes oficiais
- RFC 9110 — HTTP Semantics
- RFC 9457 — Problem Details for HTTP APIs
- OpenAPI Specification
- JSON Schema Core 2020-12
- OWASP API Security Top 10 — 2023
- OpenTelemetry — Concepts
Revisão editorial: 9 de agosto de 2026. O exemplo usa OpenAPI 3.1 no laboratório; confirme a versão suportada pelas ferramentas reais antes de adotar recursos específicos.
Da compreensão à execução
O modelo anterior explica o que precisa permanecer verdadeiro. Agora o caderno transforma essa compreensão em um experimento. Antes de executar, escreva sua previsão; depois compare o observado com o esperado e registre a primeira fronteira onde o estado divergiu.
Missão, hipótese e limites
Missão: implementar a menor fatia que representa “um técnico conclui uma ordem de serviço e o ERP pode responder ou ficar indisponível”. O laboratório não tenta construir um backend completo. Ele isola as decisões do contrato para que possam ser executadas e discutidas.
Hipótese: validação, autorização por objeto, precondição de versão, outbox e idempotência impedem cinco falhas comuns: dado inválido, acesso horizontal, atualização perdida, efeito duplicado e estado ambíguo após timeout.
Ambiente: Node.js 22 ou superior, TypeScript executável pelo modo de remoção de tipos do Node usado no projeto, editor e terminal. Duração esperada: 120–180 minutos incluindo relatório. Use diretório temporário, IDs sintéticos e nenhuma credencial. Os valores de tempo e o ERP são simulados; o exercício ensina o contrato, não mede uma infraestrutura real.
Entregáveis
Crie fora da fonte canônica:
api-contract-lab/
├── openapi.yaml
├── api-lab.ts
├── predictions.md
├── test-output.txt
├── threat-model.md
└── report.mdO relatório contém versão do runtime, comandos, resultado esperado e observado, uma falha investigada, limitações e decisão. Não inclua token, nome real, URL interna, payload produtivo ou screenshot com dados pessoais.
Passo 1 — faça previsões antes do código
Preencha esta tabela. A última coluna será confirmada depois:
| Caso | Status previsto | Estado muda? | Efeito ERP/outbox esperado | Observado |
|---|---|---|---|---|
| conclusão válida, ERP lento | 202 | uma vez | um evento pendente | — |
| leitura negativa | 422 | não | nenhum | — |
| técnico de outro site | 404 | não | nenhum | — |
| versão antiga | 412 | não | nenhum | — |
| mesma chave e mesmo pedido | mesmo 202 | não novamente | continua um evento | — |
| mesma chave e pedido diferente | 409 | não | nenhum novo | — |
Usar 404 para outro site é uma política didática que reduz descoberta do recurso; algumas organizações escolhem 403. A regra precisa ser consistente e testada. A aplicação continua registrando a negação de modo sanitizado.
Passo 2 — escreva o contrato OpenAPI
Salve como openapi.yaml:
openapi: 3.1.0
info:
title: API didática de manutenção
version: 1.0.0
paths:
/api/v1/work-orders/{id}/completion:
post:
operationId: completeWorkOrder
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema: { type: string, pattern: "^[0-9]+$" }
- name: Idempotency-Key
in: header
required: true
schema: { type: string, minLength: 8, maxLength: 128 }
- name: If-Match
in: header
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required: [reading, evidenceIds]
properties:
reading: { type: number, minimum: 0 }
evidenceIds:
type: array
minItems: 1
maxItems: 10
items: { type: string, minLength: 1 }
responses:
"200": { description: Conclusão local e ERP confirmados }
"202": { description: Conclusão local; ERP pendente }
"404": { description: Ordem não encontrada ou não visível }
"409": { description: Estado ou chave conflitante }
"412": { description: Versão esperada não corresponde }
"422": { description: Dados semanticamente inválidos }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearerLeia cada linha: o path identifica a operação; segurança declara credencial; Idempotency-Key identifica repetição; If-Match leva a versão vista pelo cliente; o body recusa campos extras e exige evidência. OpenAPI documenta a interface, mas a implementação ainda precisa validar identidade, site, estado e efeitos.
Passo 3 — implemente e execute o modelo
Salve o código integral abaixo como api-lab.ts. Ele usa somente node:assert/strict e estruturas em memória. Não é pseudocódigo: os testes ao final são parte do arquivo.
import assert from "node:assert/strict";
type Order = {
id: string;
siteId: string;
status: "open" | "completed" | "cancelled";
version: number;
};
type Actor = { id: string; siteId: string; role: "technician" | "supervisor" };
type Completion = { reading: number; evidenceIds: string[] };
type Reply = { status: number; body: Record<string, unknown> };
const orders = new Map<string, Order>([
["742", { id: "742", siteId: "A", status: "open", version: 7 }],
["800", { id: "800", siteId: "A", status: "open", version: 3 }],
]);
const outbox: Array<{ orderId: string; key: string }> = [];
const idempotency = new Map<string, { fingerprint: string; reply: Reply }>();
const problem = (status: number, title: string): Reply => ({
status,
body: { type: `https://example.invalid/problems/${status}`, title, status },
});
function completeOrder(input: {
orderId: string;
actor: Actor;
key: string;
ifMatch: number;
completion: Completion;
erp: "confirmed" | "timeout";
}): Reply {
const { orderId, actor, key, ifMatch, completion, erp } = input;
if (!Number.isFinite(completion.reading) || completion.reading < 0 ||
completion.evidenceIds.length < 1 || completion.evidenceIds.length > 10) {
return problem(422, "Dados da conclusão são inválidos");
}
const order = orders.get(orderId);
if (!order || order.siteId !== actor.siteId) {
return problem(404, "Ordem não encontrada");
}
const scope = `${actor.id}:${orderId}:${key}`;
const fingerprint = JSON.stringify(completion);
const cached = idempotency.get(scope);
if (cached) {
return cached.fingerprint === fingerprint
? cached.reply
: problem(409, "Chave reutilizada com outro pedido");
}
if (order.version !== ifMatch) return problem(412, "Versão desatualizada");
if (order.status !== "open") return problem(409, "Transição não permitida");
order.status = "completed";
order.version += 1;
outbox.push({ orderId, key });
const reply: Reply = erp === "confirmed"
? { status: 200, body: { orderId, localStatus: "completed", erpSync: "synced" } }
: { status: 202, body: { orderId, localStatus: "completed", erpSync: "pending" } };
idempotency.set(scope, { fingerprint, reply });
return reply;
}
const actorA: Actor = { id: "tech-1", siteId: "A", role: "technician" };
const actorB: Actor = { id: "tech-2", siteId: "B", role: "technician" };
const valid = { reading: 328.4, evidenceIds: ["ev-19"] };
assert.equal(completeOrder({
orderId: "742", actor: actorA, key: "demo-key-742", ifMatch: 7,
completion: { reading: -1, evidenceIds: [] }, erp: "timeout",
}).status, 422);
assert.equal(completeOrder({
orderId: "742", actor: actorB, key: "demo-key-742", ifMatch: 7,
completion: valid, erp: "timeout",
}).status, 404);
assert.equal(completeOrder({
orderId: "800", actor: actorA, key: "demo-key-800", ifMatch: 2,
completion: valid, erp: "confirmed",
}).status, 412);
const first = completeOrder({
orderId: "742", actor: actorA, key: "demo-key-742", ifMatch: 7,
completion: valid, erp: "timeout",
});
const repeated = completeOrder({
orderId: "742", actor: actorA, key: "demo-key-742", ifMatch: 7,
completion: valid, erp: "timeout",
});
assert.equal(first.status, 202);
assert.deepEqual(repeated, first);
assert.equal(outbox.filter((event) => event.orderId === "742").length, 1);
assert.equal(orders.get("742")?.version, 8);
assert.equal(completeOrder({
orderId: "742", actor: actorA, key: "demo-key-742", ifMatch: 8,
completion: { reading: 999, evidenceIds: ["ev-other"] }, erp: "confirmed",
}).status, 409);
console.log("6 cenários aprovados; 1 efeito lógico para a OS 742");Execute:
node --experimental-strip-types api-lab.tsResultado esperado:
6 cenários aprovados; 1 efeito lógico para a OS 742O experimento prova comportamento do modelo em memória, não persistência transacional real. Em produção, tabela de idempotência, ordem, auditoria e outbox precisam participar de uma transação; expiração e concorrência dessa tabela exigem projeto próprio.
Passo 4 — interprete cada proteção
Validação: leitura negativa e lista vazia falham antes de qualquer mutação. Um schema estrutural não conhece sozinho todas as regras; a função também verifica significado.
Autorização por objeto: o ator do site B recebe resposta indistinguível de recurso ausente. O teste confirma que a OS não mudou. O ID vindo da URL nunca é autorização.
Precondição: o cliente que viu versão 2 não altera a versão 3. Em HTTP real, o contrato poderia transportar a versão em ETag/If-Match e responder 412, conforme a semântica da RFC 9110.
Idempotência: a verificação da chave ocorre antes de validar novamente o estado e a versão. Assim, uma repetição depois do commit recebe o resultado anterior. O fingerprint impede usar a mesma chave para outra leitura/evidência.
Outbox: existe exatamente um evento local apesar de duas chamadas. O timeout do ERP vira pending; ele não desfaz uma conclusão já confirmada localmente nem dispara retry cego.
Passo 5 — acrescente observabilidade sem vazar dados
Registre um evento conceitual por tentativa:
{
"event": "work_order.completion",
"order_id": "742",
"site_scope": "A",
"result": "pending_erp",
"http_status": 202,
"contract_version": "1.0.0",
"correlation_id": "demo-01",
"duration_ms": 18
}Não registre bearer token, body completo, evidência bruta nem e-mail. Em métrica, evite order_id e correlation_id como labels de cardinalidade ilimitada. Guarde IDs em logs/traces sob retenção e acesso controlados.
Meça latência p50/p95, respostas por classe, rejeição de schema, conflitos, deduplicações, lag da outbox e idade do estado pending_erp. Todo alerta precisa de owner e ação: “lag acima de dez minutos → verificar worker e ERP; pausar aumento de tráfego; reconciliar por chave”.
Passo 6 — threat model curto
Preencha ativo, ator, entrada, impacto, controle e teste:
| Ameaça | Impacto | Controle | Teste |
|---|---|---|---|
trocar 742 por ID de outro site |
vazamento ou alteração horizontal | autorização por objeto | ator B recebe 404 e estado não muda |
| enviar body enorme | memória/custo | limite no gateway e parser | excedente recebe 413 sem processar |
| repetir rapidamente | duplicação ou exaustão | idempotência e rate limit | outbox continua com um efeito |
| injetar token no log | roubo de credencial | redaction e allowlist de campos | scanner não encontra segredo |
| ERP retorna conteúdo inesperado | corrupção/injeção | schema de resposta e mapeamento | resposta extra é rejeitada/quarentenada |
O modelo não implementa rate limit ou tamanho de transporte porque não há servidor HTTP; marque esses controles como limitações e descreva o teste de integração necessário. Não declare que foram provados.
Passo 7 — falha, diagnóstico e recuperação
Altere temporariamente a ordem de duas linhas: faça a função adicionar na outbox antes da autorização. O teste de acesso horizontal ainda acusa apenas status, mas uma nova asserção sobre o tamanho da outbox deve falhar. Isso demonstra que testar somente a resposta não prova ausência de efeito.
Depois restaure a ordem correta e registre:
Sintoma: evento criado para chamada negada
Hipótese: efeito ocorreu antes da policy
Evidência: tamanho e conteúdo da outbox
Correção: autorização antes da mutação, mais asserção negativa
Prevenção: teste de trajetória e revisão da fronteira de efeitosPara timeout real, não conclua automaticamente que a operação falhou. Consulte pela chave ou pelo recurso de acompanhamento. Retry só ocorre com limite e quando o servidor garante idempotência. Se o ERP já recebeu o efeito, a reconciliação marca a outbox entregue; não envia novamente.
Troubleshooting
| Sintoma | Hipótese | Verificação | Correção segura |
|---|---|---|---|
node não reconhece tipos |
runtime antigo ou flag ausente | node --version |
usar Node suportado ou compilar com tsc |
| YAML não abre | indentação ou : sem aspas |
parser YAML | corrigir estrutura, não remover validação |
| duplicata retorna 409 | fingerprint ou escopo mudou | chave, ator, ID e body | repetir exatamente a mesma intenção |
| primeira chamada retorna 412 | cliente leu versão antiga | versão atual da OS | recarregar, revisar e decidir novamente |
| teste passa após remover policy | asserção verifica só status | estado e outbox | testar ausência de todo efeito |
| pending nunca termina | worker/ERP ausente | lag e consulta remota | reconciliar e manter estado honesto |
Critérios de aceite
-
openapi.yamlé YAML válido e contém segurança, três parâmetros, schema e seis respostas. -
api-lab.tsexecuta com saída esperada em ambiente limpo. - Os seis cenários têm previsão, observação e explicação.
- Negação, validação e precondição produzem zero mutações.
- Duas chamadas equivalentes produzem uma entrada de outbox.
- Reuso conflitante da chave retorna 409.
- Timeout deixa estado pendente consultável, não sucesso inventado.
- Evidências não contêm segredo ou dado real.
- O relatório distingue controles provados de limitações simuladas.
- Outra pessoa reproduz resultado somente pelos arquivos.
Limpeza, solução comentada e desafio
Depois de guardar saída sanitizada, remova apenas o diretório temporário explicitamente criado. Não use glob amplo. Nenhuma credencial precisa ser revogada porque o laboratório não usa uma; se você adaptou o exercício, registre a revogação.
A solução de referência põe validação e autorização antes de efeitos, consulta cache idempotente antes da transição e grava outbox junto ao estado lógico. A ordem das verificações é parte do contrato. Um framework diferente pode implementar a mesma decisão; copiar a função em memória para produção não é o objetivo.
Desafio: adicione um consumidor que recebe duas confirmações do ERP fora de ordem. Ele deve aceitar uma confirmação válida, ignorar duplicata e impedir que evento antigo reverta synced para pending. Escreva três asserções e explique como persistiria inbox e versão.
Transferência: adapte a função para reservar material no estoque. Identifique o que muda se o efeito for reversível, se houver custo financeiro e se dois supervisores aprovarem ao mesmo tempo.
Recuperação ativa
- Por que o cache idempotente é consultado antes da versão atual?
- Que diferença existe entre 409 e 412 neste laboratório?
- O que o OpenAPI prova e o que ele não prova?
- Qual asserção demonstra ausência de efeito em acesso negado?
- Por que
202é mais honesto que200quando o ERP não confirmou?
Transição
Volte ao modelo mental e revise as decisões usando a evidência produzida. Para transformar o modelo em serviço real, conecte este contrato aos livros de backend, banco, filas, identidade, observabilidade e alta disponibilidade sem transferir autoridade ao frontend ou ao ERP.
Fontes oficiais
- RFC 9110 — HTTP Semantics
- RFC 9457 — Problem Details
- OpenAPI Specification
- JSON Schema 2020-12
- OWASP API Security Top 10 — 2023
- Node.js strict assertion test API
Revisão editorial: 9 de agosto de 2026. O laboratório foi projetado para o runtime empacotado do projeto; registre a versão usada na reprodução.
Comprove o que você aprendeu
Responda todas as questões. O gabarito comentado só aparece depois do envio.