Pular para o conteúdo
fernando.moretes.com
BlogEstudosCursosE-booksOpen SourcePodcasts
Loading…
Fernando Azevedo

Arquiteto de TI Especialista

Arquitetura, AWS, IA em produção, sistemas financeiros e FinOps, escritos a partir do que foi operado, com os números.

  • LinkedIn
  • GitHub
  • E-mail

Conteúdo

  • Blog
  • Estudos de arquitetura
  • Cursos
  • E-books
  • Open Source
  • Podcasts
  • Assuntos
  • Trilhas

Ferramentas

  • Moretes Architect MCP
  • Well-Architected self-check
  • Arquitetura deste site
  • Números públicos
  • Histórico de estudo

Sobre

  • Comunidade
  • IA Builder
  • Perfil
  • Currículo (CV)
  • Trabalhe comigo
  • Media kit
  • RSS do blog
  • RSS dos estudos
  • Artigos narrados (podcast)
  • llms.txt
  • Termos, privacidade e uso de IA

(c) 2026 Fernando Francisco Azevedo

MCP para Arquitetos: o Moretes Architect na prática/Desenhar e contratar: C4, sequência, eventos e OpenAPI
Módulo 2 · Trabalhar· Aula 04/10

Desenhar e contratar: C4, sequência, eventos e OpenAPI

Quatro ferramentas que transformam uma lista de elementos em diagrama Mermaid validado e uma quinta que lê o contrato da API e devolve achados por severidade. Tudo sobre a plataforma de pedidos do marketplace, com a saída real de cada chamada.

12 min de leitura

Escolha como aprender

combine como quiser

Dica: o vídeo é um resumo visual rápido; o áudio e o texto trazem a aula completa. Terminar o áudio ou o vídeo, ou passar no quiz, já conta a aula como concluída.

Assista a esta aula
Narrado por Fernando AzevedoMP4PDF
Ouça esta aula
0:0016:03
Velocidade
Baixar

No Módulo 1 você conectou o Moretes Architect MCP ao seu agente e viu que ele devolve cálculos e estruturas, nunca a opinião de um modelo. Nesta aula a gente usa isso para desenhar e contratar a plataforma de pedidos do marketplace: C4 em dois níveis, o diagrama de sequência do pedido com idempotência, o contrato do evento orders.OrderPlaced e uma revisão heurística da Orders API em OpenAPI. Ao final você sabe o que cada ferramenta produz, como ler a saída e o que continua sendo trabalho de linter ou de gente.

Por que nós explícitos vencem o desenho à mão

Peça a qualquer agente "desenhe a arquitetura em Mermaid" e ele desenha. O problema aparece na terceira iteração: o container que se chamava Orders API virou orders-svc num diagrama e OrderService no outro, uma seta aponta para uma caixa que não existe mais, e um rótulo com [ no meio quebra o render em silêncio. Ninguém percebe até a revisão de arquitetura, e aí o desenho vale menos que o quadro branco.

As três ferramentas de diagrama do servidor (c4_model, sequence_diagram, architecture_diagram) invertem a ordem: primeiro a lista, depois o traço. Toda caixa tem um id que precisa casar com a expressão ^[a-zA-Z][a-zA-Z0-9_]{0,39}$, e o servidor valida a lista antes de gerar uma linha:

  • id duplicado falha com Duplicate element id. Dois api no mesmo diagrama não existem.
  • aresta solta falha com a mensagem exata do que faltou. Eu testei apontar buyer -> orders sem declarar orders e recebi: Relation endpoint not declared: buyer -> orders. Use "system_Orders_platform" for the system itself at context level.
  • rótulo perigoso passa por safeLabel, que troca por espaço os caracteres [ ] " < > { } | ; #, o acento grave e as quebras de linha, e corta em 160 caracteres. O texto que você escreveu vira texto; nunca vira sintaxe.

O custo é ter que listar os elementos antes de ver o desenho. Esse custo é o ganho: a lista é o modelo, o desenho é uma vista dele. Quando o agente erra, erra em uma mensagem de erro que ele mesmo lê e corrige, não em uma imagem bonita e inconsistente. Os limites são generosos para um sistema de 4 pessoas: 15 pessoas, 20 sistemas externos, 30 contêineres e 80 relações por diagrama.

c4_model: contexto primeiro, contêiner depois

c4_model tem dois níveis e é rígido sobre o que cabe em cada um. Em level: "context" entram só people e externalSystems; se você mandar containers, a resposta é Context level takes people and external systems; move containers to level=container. O próprio sistema ganha um id derivado do nome, system_ mais o nome com tudo que não é letra ou dígito trocado por _: para "Orders platform" é system_Orders_platform, e é esse id que as relações usam no nível de contexto.

Eu rodei o contexto da plataforma de pedidos com uma pessoa (buyer), um sistema externo (psp, o provedor de pagamento) e três relações. A saída tem elements: { people: 1, externalSystems: 1, containers: 0, relations: 3 }, unreferenced: [] e um campo nextLevel que diz literalmente o próximo passo: Call again with level=container and the containers inside the system boundary. Foi o que fiz.

No nível de contêiner entram os quatro de dentro da fronteira: Storefront (Next.js), Orders API (Node.js on Fargate), Orders DB (PostgreSQL) e Order events (EventBridge), com seis relações. O Mermaid que volta está no bloco abaixo, expandido: a pessoa vira um nó arredondado, o sistema vira um subgraph, o externo vira caixa dupla, e as duas classDef no fim pintam pessoa e externo. Repare em unreferenced: qualquer elemento que não aparece em nenhuma relação é listado ali. Ou você esqueceu a seta, ou a caixa não pertence a esse diagrama.

Como ver o desenho. Claude Code, Codex e Kiro mostram o texto Mermaid na resposta. Se você pedir ao agente que grave num arquivo .md dentro de um bloco ``` `mermaid ``, o preview do VS Code (com a extensão de Mermaid) e o GitHub renderizam na hora. Sem nada disso, cole o texto em mermaid.live e o diagrama aparece. O resultado vem com kind: "heuristic-design-aid" e validated: false`: a ferramenta validou o grafo, não a arquitetura.

Saída real de c4_model (level=container) para a plataforma de pedidos: o campo mermaid, expandido. elements: 1 pessoa, 1 externo, 4 contêineres, 6 relações; unreferenced: [].
text
flowchart TB
  buyer(["Buyer<br/>Places and tracks orders"])
  subgraph system_Orders_platform["Orders platform"]
    web["Storefront [Next.js]"]
    api["Orders API [Node.js on Fargate]"]
    db["Orders DB [PostgreSQL]"]
    bus["Order events [EventBridge]"]
  end
  psp[["Payment provider [REST + webhooks]"]]
  buyer -->|"uses"| web
  web -->|"calls (HTTPS/JSON)"| api
  api -->|"reads and writes"| db
  api -->|"authorizes payment"| psp
  psp -->|"webhook: payment captured"| api
  api -->|"publishes OrderPlaced"| bus
  classDef person fill:#1f3b63,stroke:#5b9bd5,color:#fff
  class buyer person
  classDef external fill:#4a4a4a,stroke:#9a9a9a,color:#fff
  class psp external

sequence_diagram e architecture_diagram: o fluxo crítico e a vista rápida

O C4 mostra quem fala com quem. Ele não mostra em que ordem, e ordem é onde mora o bug de pagamento. sequence_diagram recebe participants (2 a 15, cada um com id, label e kind actor ou participant) e messages (1 a 60) com quatro estilos de seta: sync vira ->>, async vira -), reply vira -->> e reply-async vira --). Cada mensagem aceita activate, uma note e um block do tipo alt, else, loop, opt, par ou end. A validação é a mesma filosofia: mensagem para participante não declarado falha com Unknown participant in message, else sem alt aberto falha, e bloco que você abriu e esqueceu de fechar é fechado no fim.

O exemplo que uso é "Place order with idempotency": seis mensagens entre Storefront, Orders API, Orders DB e provedor de pagamento. A saída (bloco abaixo) sai com title, autonumber e o deactivate api no final, porque a primeira mensagem tinha activate: true. O que o desenho deixa visível, e o C4 não: o INSERT order status=pending acontece antes de authorize(amount, orderId). Se a API cai entre os dois, existe um pedido pendente no banco, e é o Idempotency-Key da primeira linha que permite ao retry do Storefront encontrar esse pedido em vez de criar outro. A ferramenta é honesta sobre o que isso é: limitation: "Illustrates the intended interaction; it is not a trace of a real system."

architecture_diagram é a ferramenta de 20 segundos. Recebe nodes (1 a 40, id e label) e edges (até 80, from, to, label opcional) e devolve um flowchart LR. Só uma validação: Duplicate node or unknown edge endpoint. No exemplo da plataforma são cinco nós (Storefront, Orders API, OrderPlaced queue, Billing, Inventory) e quatro arestas, duas delas sem rótulo. Não tem pessoa, não tem fronteira de sistema, não tem tecnologia. Se você precisa disso, é c4_model; se precisa só mostrar que a fila alimenta dois consumidores, é esta.

Saída real de sequence_diagram para "Place order with idempotency" (messages: 6). O activate da primeira mensagem gera o deactivate no fim.
text
sequenceDiagram
  title Place order with idempotency
  autonumber
  participant web as Storefront
  participant api as Orders API
  participant db as Orders DB
  participant psp as Payment provider
  web->>api: POST /orders (Idempotency-Key)
  activate api
  api->>db: INSERT order status=pending
  api->>psp: authorize(amount, orderId)
  psp-->>api: authorized
  api->>db: UPDATE status=authorized
  api-->>web: 201 Created
  deactivate api

event_contract: orders.OrderPlaced com envelope CloudEvents

A seta publishes OrderPlaced do C4 é uma promessa para três times que você não controla: billing, inventory e notifications. event_contract transforma essa seta em contrato. A entrada é pequena: eventName em PascalCase pontuado (orders.OrderPlaced; a regex rejeita order_placed), producer, a lista de consumers, os payloadFields com type entre string, number, integer, boolean, timestamp, object, array, id e money (cada campo com required, padrão true, e pii, padrão false), mais ordering (none, per-key, global; padrão per-key) e deliveryGuarantee (at-least-once, at-most-once, effectively-once).

A saída tem quatro partes que eu leio nesta ordem:

  1. Envelope. CloudEvents 1.0 attributes (id, type, source, time, subject, dataschema) plus traceparent. O id é a chave de deduplicação do consumidor, o subject é a chave de partição, o traceparent liga o evento ao trace W3C de quem publicou.
  2. JSON Schema (draft 2020-12). O campo total, declarado como money, vira um objeto com amount do tipo string, descrição Decimal as string; never float, e currency com padrão ^[A-Z]{3}$. Centavos em float é o erro mais caro de corrigir depois de um ano de eventos gravados.
  3. Rules. Idempotência: Consumers keep an idempotency store keyed by event id (TTL >= max redelivery window). Ordenação per-key: Partition by subject; a single consumer per partition preserves order per key only. Evolução em quatro linhas: só mudança aditiva dentro da versão maior, quebra vira novo sufixo de type ou nova versão de dataschema com publicação dupla, consumidor ignora campo desconhecido, nunca reusar nome com outro significado. Falha: DLQ depois de retries limitados com o evento original e os metadados do erro, replay é operação autorizada com janela de tempo e consumidor alvo, nunca reenvio cego, mensagem venenosa vai para quarentena.
  4. Warnings. Duas no exemplo: a de money e a de PII, porque customerEmail veio marcado pii: true: consider referencing by id instead of embedding, define retention in the broker and in every consumer. Com mais de 5 consumidores aparece uma terceira pedindo um registro de consumidores com dono e contato.

Cada consumidor volta com mustDefine: tratamento da chave de idempotência, política de retry e DLQ, versão de schema tolerada e dono. É um template determinístico: ele não sabe se o seu broker é EventBridge, SNS ou Kafka, e por isso termina com nextTools: architecture_diagram, failure_rehearsal, decision_record.

Saída real de event_contract para orders.OrderPlaced, encurtada: o campo money no schema, as regras e as duas warnings.
json
{
  "eventName": "orders.OrderPlaced",
  "envelope": "CloudEvents 1.0 attributes (id, type, source, time, subject, dataschema) plus traceparent",
  "jsonSchema": {
    "required": ["id", "type", "specversion", "source", "time", "data"],
    "properties": {
      "data": {
        "required": ["orderId", "customerId", "total", "placedAt"],
        "properties": {
          "total": {
            "type": "object",
            "required": ["amount", "currency"],
            "properties": {
              "amount": { "type": "string", "description": "Decimal as string; never float" },
              "currency": { "type": "string", "pattern": "^[A-Z]{3}$" }
            }
          },
          "customerEmail": { "type": "string" }
        }
      }
    }
  },
  "rules": {
    "idempotency": "Consumers keep an idempotency store keyed by event id (TTL >= max redelivery window) and make side effects conditional.",
    "ordering": "Partition by subject; a single consumer per partition preserves order per key only.",
    "evolution": [
      "Additive changes only within a major version (new optional fields)",
      "Breaking change = new type suffix or dataschema major version, dual-publish during migration",
      "Consumers ignore unknown fields",
      "Never reuse a field name with a different meaning"
    ],
    "failure": [
      "Dead-letter after bounded retries with the original event and error metadata",
      "Replay is an authorized operation with a time window and a target consumer, never a blind re-send",
      "Poison messages are quarantined, not dropped silently"
    ]
  },
  "warnings": [
    "Money fields (total) use decimal strings plus currency; floats are rejected.",
    "PII fields (customerEmail): consider referencing by id instead of embedding, define retention in the broker and in every consumer."
  ],
  "consumers": [
    { "consumer": "billing", "mustDefine": ["idempotency key handling", "retry and DLQ policy", "schema version tolerated", "owner"] }
  ],
  "nextTools": ["architecture_diagram", "failure_rehearsal", "decision_record"]
}

openapi_review: a Orders API sob 12 checagens, e o que fica para o linter

openapi_review recebe um único argumento, document, que é o JSON OpenAPI 3.x inteiro (2.0 falha com OpenAPI 3.x document required; o teto é 300 paths). Pela CLI você passa o arquivo cru: node dist/cli.js call openapi_review --input orders-api.json --format sarif --fail-on medium. O documento da Orders API que eu usei tem 2 paths e 4 operações: GET e POST /orders, GET e DELETE /orders/{id}, com um scheme bearer e security global.

O summary voltou { high: 0, medium: 2, info: 10 }. Os dois medium estão no mesmo lugar, GET /orders: não documenta nenhuma resposta 4xx nem default, e é uma coleção sem parâmetro de paginação (Collection GET without pagination parameters; unbounded responses grow with data). Com 300 pedidos por minuto no pico, esse endpoint devolve 432 mil itens por dia dentro de um mês de operação; paginação não é estilo, é mudança no formato da resposta, e por isso é medium. Entre os info: POST /orders sem header Idempotency-Key (retries may duplicate side effects, exatamente o que a sequência da seção anterior resolve), DELETE /orders/{id} sem If-Match/ETag nem 409/412 (concurrent writes can overwrite each other silently), nenhuma das quatro operações documenta 429, e nenhuma tem summary ou description. Um high apareceria se não houvesse securitySchemes ou se um servers[].url fosse http://; aqui não houve.

O que não está nessa lista é tão importante quanto: convenção de nomes, casing de campos, qualidade do texto das descrições, completude dos schemas. Isso é trabalho de linter, e a própria resposta diz: Static subset of checks; use Spectral or vendor linters for style rules. As outras duas limitations são Cannot evaluate authorization logic or runtime behavior e Empty findings is not an API approval. Leia a segunda duas vezes antes de levar um zero achado para a reunião.

O prompt api_design_review. O servidor publica 20 prompts, e este é o fluxo de revisão de API. O texto é curto e vale copiar: Ask for the OpenAPI document and the consumers. Run openapi_review, then sequence_diagram for the two most critical flows and latency_budget for the end-to-end objective. For events use event_contract. Findings need owner and validation; style rules belong to a linter. No Claude Code ele aparece como comando /mcp__moretes-architect__api_design_review; em outros clientes, no menu de prompts. Ele encadeia três das ferramentas desta aula e uma da próxima (latency_budget), e termina na frase que eu mais repito em review: achado sem dono e sem validação é só uma linha de JSON.

Saída real de openapi_review sobre a Orders API, encurtada: summary, os dois medium e três dos dez info.
json
{
  "kind": "openapi-heuristic-review",
  "openapi": "3.1.0",
  "paths": 2,
  "operations": 4,
  "securitySchemes": ["bearer"],
  "findings": [
    { "severity": "medium", "path": "GET /orders", "message": "No 4xx or default error response documented." },
    { "severity": "medium", "path": "GET /orders", "message": "Collection GET without pagination parameters; unbounded responses grow with data." },
    { "severity": "info", "path": "POST /orders", "message": "POST without an Idempotency-Key header; retries may duplicate side effects." },
    { "severity": "info", "path": "DELETE /orders/{id}", "message": "Mutation without If-Match/ETag or 409/412; concurrent writes can overwrite each other silently." },
    { "severity": "info", "path": "GET /orders", "message": "No 429 response; document rate limiting behavior." }
  ],
  "summary": { "high": 0, "medium": 2, "info": 10 },
  "limitations": [
    "Static subset of checks; use Spectral or vendor linters for style rules",
    "Cannot evaluate authorization logic or runtime behavior",
    "Empty findings is not an API approval"
  ]
}
Ligar

Cada ferramenta e o artefato que ela produz

Toque num conceito e depois na definição.

O que você leva desta aula

Diagrama nasce de uma lista de ids validada: id duplicado e aresta solta viram erro antes de existir desenho, e rótulo passa por safeLabel.
c4_model é rígido por nível: contexto só aceita pessoas e externos, contêiner exige ao menos um contêiner; o sistema tem id system_<nome> e unreferenced lista o que ficou sem seta.
sequence_diagram mostra a ordem que o C4 esconde: na sequência do pedido, o INSERT pending vem antes do authorize, e o Idempotency-Key é o que salva o retry.
event_contract devolve envelope CloudEvents, JSON Schema com money como string decimal + currency, regras de evolução e DLQ/replay autorizado, e avisa sobre PII embutida.
openapi_review é triagem: 0 high, 2 medium e 10 info na Orders API; estilo é do Spectral, lógica de autorização é de gente, e zero achado não é aprovação.
Quiz

Checagem rápida

1. Em c4_model, você declara a relação buyer -> orders mas nenhum elemento tem id orders. O que acontece?
2. Como event_contract representa um payloadField do tipo money, como o total de orders.OrderPlaced?
3. Na revisão da Orders API, qual destes achados saiu como medium?
4. O que openapi_review NÃO faz, segundo as próprias limitations da resposta?

Referências

The C4 model for visualising software architecture · Simon BrownCloudEvents specification v1.0.2 · CNCF CloudEventsOpenAPI Specification v3.1.0 · OpenAPI InitiativeThe Idempotency-Key HTTP Header Field (draft-ietf-httpapi-idempotency-key-header) · IETF HTTPAPI Working GroupSpectral: open-source API linter · StoplightMermaid Live Editor · MermaidMoretes Architect MCP (repositório, MIT) · Fernando AzevedoConnect Claude Code to tools via MCP · Anthropic
Concluir e ir para a próxima Aula anterior