Introdução
A RIDGE & REED vende roupas para atividades ao ar livre. Um cliente quer um item verde, de tamanho médio, que esteja em estoque e custe no máximo 5000 centavos. Um produto pode ter uma variante verde e uma variante média em estoque sem ter uma variante verde média disponível. Mesmo depois de escolher o produto correto, exibir todo o seu array de variantes ou sua opção mais barata indisponível ainda enganaria o cliente.
Você salvará um seletor reutilizável de produtos e criará um pipeline de agregação do MongoDB que retorna todos os SKUs elegíveis e calcula o menor preço dessas mesmas variantes. Você precisa saber navegar pelo terminal e conhecer conexões MongoDB, objetos/arrays e operações básicas de find/update. Modelos de funções JavaScript, expressões e estágios de agregação são explicados antes do uso. Use Terminal durante todo o lab; o MongoDB, o estoque sintético e os clientes somente leitura estão preparados.
Altere apenas selector.js e finder.js. Preserve todos os produtos originais, IDs de variantes, atributos, estoque e preços, além das ferramentas fornecidas. A disponibilidade é uma contagem preparada de estoque, não uma reserva nem uma garantia de compra. Cada variante real tem SKU/cor/tamanho como texto e estoque/preço em centavos como inteiros não negativos. As variantes de um produto podem ser um array, null ou estar ausentes; outros tipos de entrada malformados estão fora deste exercício.
Manter todas as condições em uma única variante
Nesta etapa, você comparará a solicitação de um cliente com um produto real e salvará um seletor reutilizável que rejeita uma correspondência enganosa entre variantes diferentes. Comece em Terminal. cd muda o diretório; use novamente este caminho absoluto se entrar no diretório errado:
cd /home/labex/project/variant-desk
./catalog é um leitor fornecido neste diretório; ./ significa o diretório atual. Informe um ID de produto para ler o documento realmente armazenado:
./catalog TRAIL-1
Trail Jacket tem estas três variantes distintas:
| SKU | Cor | Tamanho | Estoque | Preço em centavos |
|---|---|---|---|---|
| T-GM | green | M | 0 | 1800 |
| T-BM | blue | M | 5 | 2200 |
| T-GL | green | L | 4 | 2100 |
A variante verde média está sem estoque. As variantes azul média e verde grande estão disponíveis, mas nenhuma atende à solicitação completa. Um SKU identifica uma variante; um ID de produto identifica seu produto pai. Preserve esses relacionamentos.
./db conecta o shell do MongoDB ao catálogo somente leitura deste curso. --eval executa o JavaScript entre aspas e sai. Aspas simples no shell protegem espaços e os operadores do MongoDB prefixados por cifrão contra a expansão do shell; as aspas duplas internas pertencem às strings JavaScript. Caminhos com ponto, como "variants.color", acessam campos aninhados. $gt: 0 significa maior que zero; $lte: 5000 significa menor ou igual a 5000, incluindo o limite do orçamento. Valores numéricos são números, não strings de moeda entre aspas.
Esta consulta comum com caminhos com ponto é deliberadamente abrangente demais. O segundo argumento de find projeta apenas o ID do produto: 1 inclui um campo, e _id: 0 omite o ID interno do MongoDB. sort com 1 ordena de forma crescente; toArray() reúne os documentos retornados em um único array legível:
./db --eval 'db.products.find({"variants.color":"green","variants.size":"M","variants.stock":{$gt:0},"variants.price_cents":{$lte:5000}},{_id:0,product_id:1}).sort({product_id:1}).toArray()'
Espere CAMP-3, RIDGE-2 e TRAIL-1. TRAIL-1 é a correspondência falsa: elementos diferentes do array fornecem condições diferentes. A consulta foi executada com sucesso, mas sua resposta de negócio está errada.
$elemMatch coloca todos os critérios em um único elemento do array. Talvez você já conheça esse operador dos fundamentos de arrays do MongoDB; aqui ele é o primeiro componente de um resultado completo voltado ao cliente:
./db --eval 'db.products.find({variants:{$elemMatch:{color:"green",size:"M",stock:{$gt:0},price_cents:{$lte:5000}}}},{_id:0,product_id:1}).sort({product_id:1}).toArray()'
Agora apenas CAMP-3 e RIDGE-2 permanecem. Um produto correspondente tem pelo menos uma variante elegível. Isso ainda não significa que todas as variantes desse produto sejam elegíveis.
Salve um seletor que recebe a solicitação como argumento, em vez de fixar green/M/5000 na consulta. Uma função JavaScript é código reutilizável: function buildSelector(request) dá um nome a ela e recebe um objeto chamado request; request.color lê a propriedade de cor desse objeto. return fornece o objeto de consulta ao cliente preparado. Chaves contêm objetos ou o corpo da função, dois-pontos relacionam as chaves do objeto aos valores, e vírgulas separam propriedades. A interface exigida da função é buildSelector(request) retornando um objeto de consulta. Ela não é um comando do shell nem uma exportação de módulo.
No shell, cat > substitui o arquivo de destino. <<'JS' inicia um here-document literal, preservando cifrões e aspas. Cole o JS final sozinho em uma linha; insira o bloco inteiro no prompt do shell:
cat > selector.js <<'JS'
function buildSelector(request) {
return {
variants: { $elemMatch: {
color: request.color,
size: request.size,
stock: { $gt: 0 },
price_cents: { $lte: request.max_cents }
} }
};
}
JS
./select lê essa função salva e executa seu seletor retornado sem alterações com o find do MongoDB. Seus três argumentos são cor, tamanho e preço máximo em centavos. O cliente converte o orçamento em número; strings como green e M continuam sendo rótulos exatos que diferenciam maiúsculas de minúsculas:
./select green M 5000
Os documentos realmente correspondentes são CAMP-3 e RIDGE-2. Seus arrays originais inteiros ainda aparecem, incluindo variantes que não correspondem. Isso é normal para find: a seleção escolhe documentos, não quais elementos exibir. Mantenha o seletor como um resultado útil e resolva o problema de exibição na próxima etapa.
Se um arquivo estiver ausente, use ls para listar este diretório e verifique o nome do arquivo e o diretório de trabalho absoluto. Para um erro de sintaxe JavaScript, substitua o bloco completo salvo pela versão corrigida e execute o cliente novamente. Se o shell ainda estiver esperando, digite a linha final JS que está faltando. Não altere o estoque de origem, não exclua variantes nem reinicialize o banco de dados para fazer uma consulta parecer correta.
Retornar todos os SKUs elegíveis
Nesta etapa, você transformará a seleção de produtos em um resultado completo de variantes, sem retornar apenas a primeira correspondência nem expor o array inteiro. Comece no mesmo Terminal, em /home/labex/project/variant-desk.
Consulte as duas correspondências genuínas:
./catalog RIDGE-2
./catalog CAMP-3
Para green/M com máximo de 5000 centavos, Ridge Jacket tem dois SKUs elegíveis: R-GM1 a 4500 e R-GM2 a 5000. R-GM3 é caro demais; outros tamanhos/cores e a variante com estoque zero não se qualificam. Camp Vest tem C-GM1 e C-GM2, ambos a 4000. Preços iguais não tornam SKUs diferentes duplicatas.
Uma projeção posicional, "variants.$": 1, retorna apenas o primeiro elemento correspondente do array. Veja por que ela não consegue entregar o conjunto completo desta solicitação:
./db --eval 'db.products.find({variants:{$elemMatch:{color:"green",size:"M",stock:{$gt:0},price_cents:{$lte:5000}}}},{_id:0,product_id:1,"variants.$":1}).sort({product_id:1}).toArray()'
Cada produto tem apenas uma variante nesta saída. O segundo SKU legítimo está ausente, embora esteja disponível. Retornar o array original inteiro falharia no sentido oposto, incluindo variantes indesejadas.
Um pipeline de agregação é um array de estágios processados em ordem. Cada estágio recebe os documentos do estágio anterior. $project escolhe campos de saída e pode calcular um novo campo. $filter calcula um array contendo todos os elementos cuja condição é verdadeira; nenhum limite é aplicado aqui.
Dentro de $filter, input é o array de origem, e as: "v" nomeia a variante atual. "$variants" se refere a um campo do documento; "$$v.color" se refere à cor do elemento atual do array. Esses cifrões são expressões MongoDB dentro de strings JavaScript. request.color é um valor JavaScript da solicitação fornecido pelo cliente.
Comparações de agregação usam um array de operandos: { $eq: [left, right] } verifica igualdade, $gt verifica maior que, e $lte verifica menor ou igual. $and exige que todas as comparações sejam verdadeiras para esse mesmo v. Isso difere da forma do objeto de consulta em $elemMatch, embora as condições de negócio sejam idênticas.
$ifNull: ["$variants", []] substitui um campo de origem null ou ausente por um array vazio. [] é um array JavaScript vazio. Um valor que não seja array, como uma string, está fora do contrato de entrada fornecido; este pipeline não pretende limpar catálogos malformados arbitrários.
Após a filtragem, $match remove produtos sem variantes elegíveis. "variants.0" se refere ao índice zero, o primeiro elemento do array; $exists: true exige que ele exista. Esse é um predicado de consulta em um estágio match, não uma expressão dentro da condição de filtro. O $sort final facilita a leitura da saída real.
Salve o modelo completo em finder.js. Sua interface exigida é buildPipeline(request) retornando um array de estágios. O cliente executará esse pipeline retornado sem alterações; ele não filtra sua resposta depois:
cat > finder.js <<'JS'
function buildPipeline(request) {
return [
{ $project: {
_id: 0, product_id: 1, title: 1,
variants: { $filter: {
input: { $ifNull: ["$variants", []] },
as: "v",
cond: { $and: [
{ $eq: ["$$v.color", request.color] },
{ $eq: ["$$v.size", request.size] },
{ $gt: ["$$v.stock", 0] },
{ $lte: ["$$v.price_cents", request.max_cents] }
] }
} }
} },
{ $match: { "variants.0": { $exists: true } } },
{ $sort: { product_id: 1 } }
];
}
JS
./finder green M 5000
Espere exatamente dois produtos. CAMP-3 contém C-GM1 e C-GM2; RIDGE-2 contém R-GM1 e R-GM2. Cada variante retornada mantém seu SKU, cor, tamanho, estoque e preço em centavos exatos. O produto mantém seus product_id e title exatos. Ainda não há campo de preço mínimo; isso vem na próxima etapa.
O pipeline agora retorna todos os quatro SKUs elegíveis, incluindo as duas variantes de mesmo preço de Camp Vest e o SKU de Ridge exatamente no orçamento. TRAIL-1, arrays vazios e arrays null/ausentes não geram produtos no resultado. Nenhum documento de origem foi excluído nem editado para criar esse resultado.
Se o cliente informar que buildPipeline deve retornar um array de pipeline, verifique se o arquivo salvo inclui a função inteira e return [ ... ];. Se informar um erro de sintaxe, substitua o bloco completo e tente novamente. Mantenha o JavaScript da consulta no arquivo salvo; não adicione comandos do shell, um comando interativo use nem exportações de módulo.
Calcular o preço do conjunto correspondente e reutilizar o buscador
Nesta etapa, você adicionará o menor preço elegível e usará a mesma consulta salva para solicitações diferentes de clientes, incluindo um resultado vazio legítimo. Comece em Terminal, em /home/labex/project/variant-desk.
A variante original mais barata de Ridge Jacket custa 900 centavos, mas tem estoque zero. Outra variante barata tem a cor errada. Anunciar qualquer uma delas como preço inicial de green/M enganaria o cliente, mesmo que o produto em si tivesse sido selecionado corretamente.
$set adiciona ou substitui um campo de saída em um estágio de agregação; ele não atualiza o produto armazenado. Aqui ele adiciona min_price_cents. A expressão $min com um operando array retorna seu menor valor numérico. "$variants.price_cents" acessa os preços dentro do array de variantes do documento atual. Coloque este estágio após a filtragem: ele deve ler o array correspondente produzido por $project, não o array original. Um estágio posterior também torna esse campo alterado disponível de forma inequívoca.
Substitua finder.js pelo pipeline completo abaixo. O único estágio novo calcula o mínimo depois de os arrays vazios terem sido excluídos:
cat > finder.js <<'JS'
function buildPipeline(request) {
return [
{ $project: {
_id: 0, product_id: 1, title: 1,
variants: { $filter: {
input: { $ifNull: ["$variants", []] },
as: "v",
cond: { $and: [
{ $eq: ["$$v.color", request.color] },
{ $eq: ["$$v.size", request.size] },
{ $gt: ["$$v.stock", 0] },
{ $lte: ["$$v.price_cents", request.max_cents] }
] }
} }
} },
{ $match: { "variants.0": { $exists: true } } },
{ $set: { min_price_cents: { $min: "$variants.price_cents" } } },
{ $sort: { product_id: 1 } }
];
}
JS
./finder green M 5000
O resultado deve manter todos os quatro SKUs elegíveis e adicionar o mínimo 4000 de CAMP-3 e o mínimo 4500 de RIDGE-2. min_price_cents é derivado de cada conjunto de variantes retornado. Não elimine preços iguais como duplicatas, não use um mínimo do produto inteiro nem escreva esses dois números manualmente.
Agora reduza o orçamento usando a mesma função. Os valores da solicitação mudam; a consulta salva não muda:
./finder green M 4499
Apenas CAMP-3 permanece, ainda com os dois SKUs de 4000 centavos e mínimo 4000. Ridge Jacket não tem variante green/M elegível abaixo desse novo limite.
Altere a cor e o limite:
./finder blue M 2500
RIDGE-2 agora contém apenas R-BM a 1000; TRAIL-1 contém apenas T-BM a 2200. Um produto corretamente excluído de uma solicitação pode ser incluído em outra. A disponibilidade pertence a um relacionamento específico de variante/solicitação, não a um indicador permanente do produto.
Por fim, experimente um orçamento sem correspondências:
./finder green M 500
O resultado real é []. O cliente não substitui o resultado por uma demonstração nem mostra itens indisponíveis para preencher a tela. Uma consulta ausente ou um erro de banco de dados é informado como erro, não disfarçado como esse array vazio bem-sucedido.
O resultado final do pipeline contém exatamente product_id, title, variants e min_price_cents. Retorne cada produto correspondente uma vez e cada SKU elegível uma vez, com os cinco campos originais da variante. A ordem de produtos/variantes é flexível; as identidades e os valores são o contrato de negócio. A consulta também deve tratar outro catálogo com o mesmo esquema e IDs, preços e estoque diferentes. Os clientes preparados leem resultados reais do MongoDB e não corrigem a seleção, os arrays nem os preços.
Mantenha os dois arquivos JavaScript salvos e todos os produtos/ferramentas originais. Você criou uma consulta reutilizável de catálogo, não um sistema de reservas: alterações concorrentes no estoque ou uma compra posterior ainda podem afetar a disponibilidade. O Challenge a seguir tem seu próprio estoque independente de aluguel e usa os mesmos mecanismos de consulta com nomes de atributos e valores de solicitação diferentes.
Resumo
Você rejeitou correspondências entre variantes diferentes, manteve todos os SKUs elegíveis em vez de apenas o primeiro e calculou os preços exibidos a partir dos arrays filtrados. Suas funções salvas tratam solicitações diferentes, preços iguais e resultados vazios, preservando o catálogo original.



