Data contracts e schema evolution

TL;DR

Um pipeline de dados depende de um schema que ele não controla — a tabela de pedidos do time de backend, o tópico de eventos de outro serviço. Quando esse schema muda sem aviso, o pipeline quebra em silêncio: os números continuam saindo do outro lado, só que errados ou vazios, e ninguém percebe até um dashboard de faturamento chegar zerado numa reunião. Data contract é o antídoto estrutural: um acordo explícito e versionado entre quem produz o dado e quem consome, cobrindo schema, semântica, garantias de qualidade e ownership — transformando uma dependência implícita e frágil num compromisso testável em CI. O núcleo técnico do contrato é a compatibilidade de schema: quais mudanças um produtor pode fazer sem quebrar consumidores existentes (adicionar campo opcional) e quais sempre quebram (remover, renomear, mudar tipo). Esta nota cobre o problema do silent breakage, o princípio de shift-left (validar na origem, não no fim da linha), as regras de compatibilidade backward/forward/full, onde o contrato vive como código, e o estado dos data contracts como primitivo de warehouse em 2026.

O dashboard que zerou sem nenhum erro

Volte ao e-commerce que abriu esta trilha. O time de backend mantém a tabela pedidos no Postgres transacional, e um pipeline extrai dessa tabela todo dia à noite para alimentar a fato_vendas do warehouse — a mesma tabela de fatos desenhada na nota 03 do sub-galho de modelagem. Uma das colunas que o pipeline lê é preco.

Um dev do time de backend, trabalhando numa feature de preço promocional, decide que preco é um nome ambíguo — ele quer distinguir preço unitário de preço com desconto aplicado. Renomeia a coluna para preco_unitario, roda a migration, faz o deploy. Do ponto de vista dele, é um refactor local, dentro do domínio dele, sem nenhuma mudança de comportamento visível para o usuário do checkout. Os testes do serviço de pedidos passam. O deploy sobe sem incidente.

Três dias depois, alguém do comercial pergunta por que o dashboard de faturamento está mostrando R$ 0 desde terça-feira.

O que aconteceu: o pipeline noturno continuou rodando, todas as noites, sem lançar exceção nenhuma. A query de extração selecionava a coluna preco, que não existe mais — mas dependendo de como a extração foi escrita (um SELECT * que virou SELECT preco implícito via um ORM, ou um cast permissivo), o resultado não foi um erro fatal. Foi um valor nulo, silenciosamente convertido para zero na agregação do warehouse. O pipeline “funcionou”. A tabela fato_vendas recebeu linhas novas todas as noites. Só que com faturamento zero em cada uma.

Silent breakage: o pior tipo de quebra

O que acontece: uma mudança de schema na origem não derruba o pipeline — ela degrada o dado silenciosamente, sem exceção, sem alerta, sem nenhum sinal visível até alguém notar o número errado numa reunião de negócio. Por quê: o produtor do dado (o time de backend) não sabia que a tabela pedidos tinha um consumidor a jusante. Não havia lista de quem lê aquele schema, não havia teste que barrasse o deploy, e o pipeline foi escrito de um jeito tolerante o suficiente para não quebrar ruidosamente diante de uma coluna ausente — o que parecia robustez, mas era só adiar o dano para um lugar onde ele é mais caro de diagnosticar. Como evitar: o assunto desta nota inteira. Em resumo: tornar a dependência explícita (data contract), validar o mais cedo possível (shift-left), e projetar o pipeline para falhar ruidosamente diante de uma mudança de schema, nunca silenciosamente.

O ponto central deste incidente hipotético não é técnico — é organizacional. O dev de backend fez um refactor correto, dentro do seu próprio domínio, seguindo boas práticas de nomenclatura. O erro não foi dele sozinho: foi de um sistema que não deu a ele nenhuma forma de saber, no momento do deploy, que aquela coluna tinha um consumidor fora do radar dele. É exatamente esse gap — a dependência entre produtor e consumidor de dado que existe na prática, mas não existe em lugar nenhum como compromisso explícito — que um data contract fecha.

O que é um data contract

Um data contract é um acordo explícito, versionado, entre quem produz um conjunto de dados e quem consome — cobrindo, tipicamente, quatro dimensões:

  • Schema — os campos, tipos e estrutura que o consumidor pode esperar encontrar, formalizados como artefato verificável (não como comentário em código ou página de wiki que ninguém lê).
  • Semântica — o que cada campo significa. preco é com ou sem imposto? criado_em é a hora do pedido ou a hora que ele entrou na fila de processamento? Um schema correto sintaticamente ainda pode enganar semanticamente.
  • Garantias de qualidade e SLA — com que frequência o dado é atualizado (freshness), que taxa de nulo é aceitável, em quanto tempo uma quebra é corrigida. Este eixo se conecta direto com 01 - Qualidade e observabilidade de dados, que trata a validação contínua dessas garantias — o contrato é o que promete o valor, a observabilidade é o que confirma que a promessa está sendo cumprida.
  • Ownership — quem é responsável quando o contrato quebra. Sem um dono nomeado, “alguém deveria consertar isso” vira ninguém consertando.

O efeito prático de nomear essas quatro coisas por escrito, num artefato versionado, é transformar uma dependência implícita — “o pipeline de analytics lê a tabela pedidos, mas nada no código do backend registra isso” — numa dependência explícita — “a tabela pedidos tem um contrato publicado; qualquer mudança que viole esse contrato precisa passar por um processo, não por um deploy silencioso”.

Shift-left: validar na origem, não no fim da linha

A abordagem reativa — a que o e-commerce tinha antes do incidente — é testar a qualidade do dado só no fim do pipeline: rodar checagens no warehouse, depois que o dado já foi extraído, transformado e carregado. Isso funciona para pegar erros de transformação, mas para um problema de schema na origem ela chega tarde demais: o dado ruim já percorreu o pipeline inteiro, já pode ter alimentado um dashboard ou um modelo de ML, e o diagnóstico agora exige rastrear de volta por várias etapas até achar onde a mudança realmente aconteceu.

Shift-left é o princípio de mover essa validação para o mais cedo possível — idealmente para o momento em que o produtor emite o dado, antes mesmo dele entrar no pipeline. Na prática, isso significa duas coisas trabalhando juntas:

  1. O produtor valida a própria mudança contra o contrato publicado, no seu próprio pipeline de CI, antes do deploy — o dev de backend do exemplo teria visto o build falhar ao tentar renomear preco, porque existe um teste de contrato rodando ali, não três camadas depois.
  2. Quando isso não é possível (o produtor é um sistema de terceiros, ou legado, sem esse tipo de gate), a validação acontece o mais próximo possível do ponto de ingestão — o pipeline recusa dado que não bate com o schema esperado, em vez de aceitar silenciosamente e deixar o erro se propagar.
graph LR
    classDef destaque fill:#FFAA0024,stroke:#FFAA00,color:#E9ECF2
    classDef neutro fill:#1B2029,stroke:#4E5666,color:#C6CCD8
    subgraph Reativo["Abordagem reativa — sem shift-left"]
        A1["Produtor muda<br/>schema"] --> A2["Pipeline extrai<br/>(sem checar)"] --> A3["Transforma"] --> A4["Warehouse"] --> A5["Teste de qualidade<br/>detecta aqui"]
    end

    subgraph ShiftLeft["Shift-left"]
        B1["Produtor tenta mudar<br/>schema"] -->|"CI valida contra<br/>o contrato"| B2{"Quebra o<br/>contrato?"}
        B2 -->|"sim"| B3["Deploy barrado<br/>antes de sair"]
        B2 -->|"não"| B4["Deploy segue,<br/>pipeline confia"]
    end

    class A5 destaque
    class B3 neutro
    class B4 neutro

O ganho de shift-left não é só velocidade de detecção — é quem paga o custo de descobrir o problema. Na abordagem reativa, quem descobre é o consumidor, geralmente muito depois, sem contexto sobre o que mudou. No shift-left, quem descobre é o próprio produtor, no momento exato em que ele tem todo o contexto da mudança na cabeça — o lugar mais barato do mundo para corrigir um erro.

Compatibilidade de schema: o núcleo técnico

Se o contrato promete um schema, a pergunta operacional é: quais mudanças o produtor pode fazer sem quebrar o contrato, e quais exigem negociação, deprecation ou uma nova versão? Essa é exatamente a mesma pergunta que a teoria de versionamento de API síncrona resolve para contratos REST/RPC — a nota Versionamento e evolução de contrato cobre em profundidade backward/forward compatibility, deprecation e o processo de breaking change para APIs. Esta nota não repete essa teoria — ela aplica o mesmo eixo de compatibilidade ao contrato de dados: o schema de uma tabela, de um evento consumido por um pipeline analítico, de um arquivo Parquet num data lake.

As três categorias de compatibilidade, aplicadas a schema de dado[^confluent]:

  • Backward compatible — um consumidor com o schema novo consegue ler dado escrito com o schema antigo. É a garantia que importa quando o consumidor evolui antes do produtor reprocessar dado histórico: o pipeline atualizado ainda precisa conseguir ler as partições antigas do data lake.
  • Forward compatible — um consumidor com o schema antigo consegue ler dado escrito com o schema novo, ignorando o que não reconhece. É a garantia que importa quando o produtor evolui mais rápido que todos os consumidores conseguem acompanhar — um evento novo chega com um campo extra, e os consumidores mais lentos simplesmente o ignoram sem quebrar.
  • Full compatible — as duas garantias ao mesmo tempo. É o alvo ideal para schema que é consumido por muitos times em momentos de atualização diferentes, típico de um schema registry corporativo.

Traduzindo isso em mudanças concretas de schema:

MudançaBackward compatible?Forward compatible?Por quê
Adicionar campo opcional, com valor defaultSimSimConsumidor novo lê dado velho usando o default; consumidor velho ignora o campo novo
Adicionar campo obrigatório, sem defaultNãoSimDado velho não tem o campo — consumidor novo que exige presença dele quebra
Remover campo que ninguém mais usaDependeDependeSó é seguro se nenhum consumidor ativo lê aquele campo — verificável só com um contrato que rastreia consumidores
Renomear campo (precopreco_unitario)NãoNãoDo ponto de vista do schema, é uma remoção mais uma adição — quebra os dois sentidos, como no incidente do e-commerce
Mudar o tipo de um campo (intstring)NãoNãoNenhum consumidor deserializa "12.50" esperando 1250, nem o inverso — mesmo mudanças aparentemente “compatíveis” de tipo (intlong) exigem checagem explícita
Apertar uma constraint (nullablenot null, ou reduzir um enum)Não, para o produtorDado histórico pode já violar a constraint nova; consumidores que dependiam da flexibilidade anterior quebram
Afrouxar uma constraint (not nullnullable)Sim, mas com ressalvaConsumidores que assumiam presença garantida do campo agora podem receber nulo — tecnicamente compatível no schema, mas pode quebrar lógica downstream

A regra prática que emerge dessa tabela: adicionar é quase sempre seguro; remover, renomear e mudar tipo quase nunca são. É a mesma heurística que a nota de Comunicação estabelece para contrato de API — o que muda, no lado de dados, é que a “renomeação” costuma nascer de um refactor bem-intencionado dentro de um domínio que não pensa em si mesmo como “produtor de API”, porque tecnicamente não está expondo endpoint nenhum. É exatamente esse ponto cego — “eu não sei que sou um produtor de contrato” — que o data contract, como artefato explícito, corrige.

Onde o contrato vive: schema como código

Um contrato só cumpre a promessa de shift-left se ele for verificável automaticamente, não um documento que alguém lembra de atualizar. Na prática, isso aparece em três formas que se combinam:

Schema como código. O schema é definido num formato explícito e versionável — JSON Schema, Protobuf, Avro — e vive no mesmo controle de versão que o código do produtor, não numa wiki à parte. Uma mudança de schema é uma mudança de código, revisada em pull request como qualquer outra.

CI que barra deploy que quebra contrato. O pipeline de integração contínua do produtor roda uma checagem de compatibilidade contra a versão publicada do contrato antes de permitir o merge ou o deploy — exatamente o gate que teria pego a renomeação de preco antes dela sair para produção. Essa checagem pode ser tão simples quanto “o novo schema é um superconjunto compatível do anterior” ou tão rica quanto rodar testes de contrato reais contra consumidores conhecidos.

Schema registry, para dado em movimento. Quando o dado trafega como evento — mensageria, streaming —, um schema registry centraliza as versões do schema e aplica a checagem de compatibilidade no momento da publicação, recusando uma mensagem que viole a regra configurada (backward, forward ou full). Esse mecanismo, e como ele se encaixa na arquitetura de mensageria/eventos, é tratado com mais profundidade em Comunicação entre Sistemas — aqui o ponto relevante é que o schema registry é, na prática, um data contract automatizado para dado em fluxo: o contrato deixa de ser um documento e vira um gate executável no caminho do dado.

Data contracts é área quente e volátil em 2026

O rótulo “data contract” virou um dos temas mais discutidos da engenharia de dados nos últimos anos, e a onda de ferramentas e produtos em torno dele ainda está se consolidando — plataformas dedicadas de contrato de dados, integrações de contrato em ferramentas de observabilidade, e uma variedade de abordagens (contract-first vs contract-as-test) competindo por padrão de mercado. Os warehouses e frameworks de transformação também estão internalizando o conceito como primitivo de primeira classe — por exemplo, dbt oferece model contracts desde a versão 1.5 (2023), permitindo declarar e aplicar (enforced) o schema esperado de um modelo antes dele ser materializado1, e o ecossistema de ferramentas dedicadas de contrato (workflows de definição, validação e catálogo de contratos) segue evoluindo rápido. Nomes de produto, formatos de arquivo e integrações específicas tendem a mudar de um ano para o outro. O que não muda é o princípio por trás de qualquer ferramenta que se anuncie assim: tornar a dependência entre produtor e consumidor de dado explícita, versionada e verificada automaticamente, o mais cedo possível no ciclo. Avalie qualquer ferramenta nova contra esse princípio, não contra o quão recente ou badalada ela é.

Voltando ao e-commerce: o contrato da fato_vendas

Fechando o exemplo de abertura: com um data contract em vigor, a tabela pedidos do backend teria um contrato publicado, algo como:

  • Schema: id (uuid, obrigatório), preco (decimal, obrigatório, em centavos, sem imposto), status (enum: pendente|pago|cancelado), criado_em (timestamp, hora de criação do pedido — não da confirmação de pagamento).
  • Freshness prometido: o pipeline consome via captura de mudanças (o mesmo mecanismo coberto em Ingestão de dados) com atraso máximo de 15 minutos entre a escrita no Postgres e a disponibilidade no warehouse.
  • Dono: o time de backend de pedidos, nomeado explicitamente — não “o time que mexeu por último”.
  • O que quebra o consumidor: renomear ou remover qualquer um dos quatro campos acima, mudar o tipo de preco, ou mudar a semântica de criado_em sem publicar uma nova versão do contrato.

Com esse contrato registrado e um teste de compatibilidade rodando no CI do time de backend, a tentativa de renomear preco para preco_unitario teria falhado o build, com uma mensagem apontando exatamente qual contrato e qual consumidor seriam afetados — antes do deploy, não três dias depois do dashboard zerar. O custo de escrever e manter esse contrato é real: alguém precisa nomeá-lo, publicá-lo, mantê-lo atualizado. Mas é uma fração do custo de diagnosticar, em produção, por que um número de negócio está errado sem nenhum erro visível apontando a causa.

Em entrevista

Uma pergunta comum, tanto para data engineer quanto para backend sênior que interage com pipelines analíticos: “como você evitaria que uma mudança no seu serviço quebrasse um pipeline de dados que você nem sabia que existia?” A resposta fraca fica no genérico (“comunicação entre times”). A resposta forte nomeia o mecanismo: um data contract publicado e versionado, com uma checagem de compatibilidade rodando em CI antes do deploy — shift-left, não um processo manual de avisar Slack.

Outra pergunta frequente: “que tipo de mudança de schema você consideraria sempre segura, e qual você trataria como breaking change automático?” A resposta madura nomeia a regra sem hesitar: adicionar campo opcional é seguro; remover, renomear ou mudar tipo de campo existente é sempre um breaking change candidato, e exige o mesmo processo de deprecation que qualquer contrato de API usa — nunca um deploy atômico que troca o nome de um campo de uma vez.

Um terceiro eixo, mais avançado: “como você decide entre backward compatible, forward compatible e full compatible como política padrão para um schema registry corporativo?” A resposta que soa sênior reconhece o trade-off: full compatible é o alvo mais seguro, mas também o mais restritivo — ele barra até mudanças que seriam inofensivas se todos os consumidores atualizassem no mesmo dia. Em ambientes com muitos consumidores heterogêneos, atualizando em ritmos diferentes, full compatible costuma valer o custo de restrição extra; em ambientes pequenos, com produtor e consumidor deployados juntos, uma política mais frouxa pode ser aceitável.

How to explain in English

“A data contract is an explicit, versioned agreement between the producer of a dataset and its consumers — schema, semantics, quality guarantees, and ownership. It exists because schema changes on the producer side tend to break consumers silently: a column gets renamed inside what looks like a self-contained refactor, and three days later a downstream dashboard is showing zero revenue with no error anywhere. Shift-left means validating that contract as close to the source as possible — ideally in the producer’s own CI, before deploy — instead of discovering the break at the end of the pipeline. The technical core is compatibility: additive changes, like a new optional field, are safe; removing, renaming, or retyping an existing field almost never is.”

PTEN
Contrato de dadosData contract
Produtor / consumidor de dadosData producer / data consumer
Quebra silenciosaSilent breakage
Mudança à esquerda (validar na origem)Shift-left
Compatibilidade retroativaBackward compatibility
Compatibilidade prospectivaForward compatibility
Mudança que quebra o contratoBreaking change
Schema como códigoSchema as code
Registro de schemaSchema registry
Contrato de modelo (dbt)Model contract
Dono do dadoData owner

O que vem a seguir

Estabelecemos o que é um data contract, por que a quebra de schema costuma ser silenciosa, o princípio de shift-left, e as regras de compatibilidade que decidem quais mudanças são seguras. Falta ainda responder a uma pergunta mais ampla: mesmo com contrato e qualidade garantidos campo a campo, como alguém descobre que uma tabela existe, entende o que ela significa, e rastreia de onde um número específico veio — em uma organização com centenas de tabelas e dezenas de times?

Fontes

  • dbt Labs — Add model contracts — documentação canônica de model contracts, o primitivo de contrato de dados nativo do dbt (desde 1.5, 2023).
  • Reis, Joe & Housley, Matt — Fundamentals of Data Engineering: Plan and Build Robust Data Systems, O’Reilly, 2022 — capítulo sobre governança e qualidade de dados, incluindo a origem da preocupação com contrato entre produtor e consumidor no ciclo de vida do dado.
  • Confluent — Schema Evolution and Compatibility — referência canônica das regras de compatibilidade backward/forward/full aplicadas a schema registry de eventos.
  • Skarlinski, Chad — The Rise of Data Contracts — uma das formulações iniciais que popularizou o termo “data contract” como resposta ao problema de silent breakage em pipelines analíticos.

Footnotes

  1. dbt Labs, Add model contracts, documentação oficial, desde dbt 1.5 (2023). [^confluent]: Confluent, Schema Evolution and Compatibility, documentação de schema registry.