DevGuideTK
← Trilha Backend

Bancos de documentos

Dados guardados como documentos aninhados, com esquema flexível e as consultas que isso dificulta.

O código desta página foi executado nas cinco linguagens contra um MongoDB 8.3, num conjunto de réplicas de um nó. As medições do nível Conceito vêm do mesmo banco.

Baixar o estoque sem vender o que não existe, em cada linguagem

A função recebe o produto e a quantidade e faz um único findOneAndUpdate: o filtro exige estoque suficiente, e a alteração usa $inc. Se o filtro não encontra o documento, não havia estoque, e nada foi alterado. O programa dispara 30 compras simultâneas de um produto com estoque 10.

import { MongoClient } from "mongodb";

const cliente = new MongoClient(process.env.MONGODB_URL!, { maxPoolSize: 50 });
const produtos = cliente.db("loja").collection<{ _id: number; estoque: number }>("produtos");

// A condição e a alteração vão juntas: o banco confere e grava num passo só
async function baixarEstoque(produtoId: number, quantidade: number): Promise<boolean> {
  const produto = await produtos.findOneAndUpdate(
    { _id: produtoId, estoque: { $gte: quantidade } },
    { $inc: { estoque: -quantidade } },
  );
  return produto !== null; // null: não havia estoque suficiente
}

await produtos.deleteMany({});
await produtos.insertOne({ _id: 7, estoque: 10 });
const resultados = await Promise.all(Array.from({ length: 30 }, () => baixarEstoque(7, 1)));
const final = await produtos.findOne({ _id: 7 });
console.log(`aceitas: ${resultados.filter(Boolean).length}, estoque final: ${final?.estoque}`);
await cliente.close();

O resultado, nas cinco linguagens

DriverCompras aceitasEstoque final
TypeScriptmongodb 7.9100
Pythonpymongo 4.20100
Gomongo-driver/v2 2.10100
Javamongodb-driver-sync 5.5100
C#MongoDB.Driver 3.14100

No teste do nível Conceito, a forma "ler, conferir e gravar" aceitou 30 compras e terminou com estoque 9.

O que muda de uma linguagem para outra

Montar o filtro"Nenhum documento"
TypeScriptObjeto literal { _id, estoque: { $gte } }null
PythonDicionárioNone
Gobson.D com bson.E ordenadosmongo.ErrNoDocuments
JavaFilters.and, eq e gtenull
C#Builders<T>.Filter, combinados com &null

Por padrão, o findOneAndUpdate devolve o documento antes da alteração. Para receber o documento já alterado, use a opção de retorno do documento novo (returnDocument: "after" no Node, ReturnDocument.AFTER no PyMongo e no Java, ReturnDocument.After no C#, SetReturnDocument(options.After) no Go). Quando só importa saber se alterou, updateOne com o mesmo filtro e conferir modifiedCount também serve.

Validação de esquema

A regra testada, criada no mongosh:

db.createCollection("pedidos", {
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["total", "status"],
      properties: {
        total: { bsonType: ["int", "double", "decimal"], minimum: 0 },
        status: { enum: ["aberto", "pago", "enviado"] },
      },
    },
  },
});

Com ela, { total: "120", status: "aberto" } foi recusado com o erro 121 (DocumentValidationFailure), e os detalhes do erro apontaram o campo total. O pedido com total: 120 foi aceito. Para uma coleção que já existe, use collMod com o mesmo validator, e comece com validationAction: "warn" para descobrir os documentos fora do padrão.

Os documentos antigos com o tipo errado aparecem com $type:

db.pedidos.aggregate([{ $group: { _id: { $type: "$total" }, n: { $sum: 1 } } }]);
// no teste: [{ _id: "int", n: 1000 }, { _id: "string", n: 200 }]

Índice em campo aninhado

db.pedidos.createIndex({ "itens.produto_id": 1 });

db.pedidos.find({ "itens.produto_id": 4242 }).explain("executionStats");
// no teste, com 100 mil pedidos: 60 documentos lidos e 1 ms (antes: 100 mil e 81 ms)

Lista de revisão

Modelagem

  • Os documentos foram desenhados a partir das leituras mais frequentes?
  • As listas embutidas têm tamanho limitado?
  • Dados compartilhados copiados nos documentos têm um plano para quando mudarem?
  • Os documentos têm um campo de versão do esquema, se as formas vão conviver?

Esquema

  • Há validação com $jsonSchema na coleção, além da validação na aplicação?
  • Os documentos antigos foram conferidos com $type antes de ligar a validação?

Consistência

  • Alterações com condição usam o filtro e operadores no mesmo comando, e não ler, conferir e gravar?
  • Operações em vários documentos usam transação, e são a exceção?
  • Unicidades têm índice único?

Consultas

  • Os campos consultados, inclusive os aninhados, têm índice?
  • As consultas principais foram conferidas com explain("executionStats")?

Prompts para aprofundar

Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.

  • Implementar operações atômicas

    Quero implementar as operações de negócio do meu sistema num banco de documentos sem perder atualizações.
    
    Pergunte qual banco, linguagem e driver eu uso, e quais operações alteram dados com alguma condição.
    
    Vamos em etapas:
    1. Reescrever cada "ler, conferir e gravar" como atualização condicional com operadores.
    2. Tratar o caso em que a condição não é satisfeita.
    3. Usar transação entre documentos onde for inevitável.
    4. Um teste com dezenas de operações simultâneas para cada uma.
    5. Índices únicos onde há regras de unicidade.
  • Validar o esquema de uma coleção existente

    Quero ligar a validação de esquema numa coleção que já tem dados.
    
    Pergunte qual banco eu uso e como os documentos são hoje.
    
    Me guie:
    1. Escrever o $jsonSchema a partir dos documentos atuais.
    2. Encontrar os documentos fora do padrão com $type e com o próprio esquema no filtro.
    3. Corrigir os documentos antigos em lotes.
    4. Ligar a validação em modo de aviso, acompanhar e depois passar para recusa.
    5. Manter o esquema no repositório, junto com o código.
  • Revisar uma modelagem de documentos

    Vou colar abaixo exemplos de documentos de um sistema e o código que os grava e consulta. Revise a modelagem.
    
    Verifique, e para cada item diga se está correto, incorreto ou ausente, citando o trecho:
    1. Há listas embutidas que crescem sem limite?
    2. Os dados compartilhados copiados têm um plano para quando mudarem?
    3. Há validação de esquema?
    4. Há ler, conferir e gravar em vez de atualização condicional?
    5. Os campos consultados têm índice?
    6. Há junções ($lookup) ou transações em excesso, sinal de dados relacionais?
    
    Para cada problema, descreva o efeito em produção e mostre a correção.
    
    Documentos e código:
    [cole aqui]