Redes, sistemas e decisões de engenharia
Uma função local costuma terminar com retorno ou exceção. Uma chamada remota acrescenta uma dificuldade: o cliente pode parar de esperar sem saber o que o servidor fez. Para estudar essa diferença, uma aplicação consulta estoque e tenta reservar uma unidade. O caso exige pensar em mensagens, versões, cache, identidade e efeitos; a qualidade do código depende também da qualidade desses contratos.
A preparação combina fundamentos de HTTP e sistemas com decisões de engenharia. Vamos usar cliente e servidor locais para produzir uma falha de acompanhamento verificável. A aula inclui testes, ameaça de vazamento entre contas e critérios éticos e de acessibilidade, porque o impacto não se limita ao status técnico. Ela não substitui disciplinas completas de redes, sistemas distribuídos ou segurança; oferece uma base para discutir fronteiras presentes nas aplicações de IA.
RedesAo terminar esta aula
- Interprete timeout como resultado possivelmente desconhecido.
- Cache, revisão e idempotência protegem problemas diferentes.
- Qualidade técnica inclui fronteiras e impacto para as pessoas.
Antes de continuar: Laboratório: Dados relacionais, SQL e transações
Uma interface remota precisa declarar semântica
FundamentosHTTP carrega uma requisição com método, destino, campos e eventualmente conteúdo; a resposta apresenta status, campos e conteúdo quando aplicável. GET consulta uma representação e POST neste serviço solicita reserva. Os nomes não bastam para entender a regra: precisamos declarar autenticação, formato da quantidade, resultado e erros possíveis. Um corpo JSON válido pode conter quantidade negativa; transporte e domínio validam propriedades distintas. Definir a interface antes da implementação permite construir cliente e servidor de forma independente.
Reutilização exige validade e escopo
FundamentosUma representação de estoque possui revisão. O ETag identifica a versão produzida pelo servidor e If-None-Match permite ao cliente perguntar se a versão mudou. Quando ela permanece equivalente segundo o contrato do recurso, a resposta 304 permite reutilizar a representação anteriormente recebida. Isso economiza transferência; não prova autorização nem garante que o estoque ficará igual no próximo instante. Um cliente ainda precisa tratar uma mudança entre consultar e reservar. A validade da leitura e a validade da alteração são perguntas diferentes.
Cache-Control private restringe armazenamento a cache privado; max-age=0 com must-revalidate exige validação antes de reutilização sob a política do exemplo. No-store teria outro objetivo: proibir armazenamento da resposta. Escolher um campo depende da sensibilidade e da experiência desejada, e não de uma fórmula universal. Um cache implementado pela aplicação também precisa separar identidade, recurso e versão, além de respeitar suas regras de retenção. Compartilhar dados porque duas pessoas pediram a mesma URL pode expor a representação autorizada para outra conta.
Timeout cria incerteza, não prova ausência de efeito
FundamentosA reserva tem três momentos relevantes: pedido enviado, efeito registrado e resposta recebida. Se a conexão termina entre o segundo e o terceiro, o cliente não pode concluir que nada aconteceu. Reenviar como operação nova pode reduzir o saldo duas vezes. Uma chave estável permite ao servidor reconhecer a intenção anterior, desde que o registro seja conservado e ligado ao conteúdo autorizado. Usar a mesma chave com quantidade diferente deve ser conflito, não reaproveitamento silencioso de uma autorização.
If-Match protege uma alteração nova: se a revisão mudou, o servidor recusa a pré-condição antes de alterar. Isso reduz perda de atualização, mas não substitui ledger de operações repetidas. No exemplo, o replay da operação já concluída devolve recibo mesmo trazendo a versão antiga; essa resposta reconhece história, não aprova outra mudança. A ordem precisa ser pensada: autenticar, autorizar o recurso, validar entrada, conferir binding do replay, aplicar pré-condição para operação nova e então efetivar. Cada passo protege uma hipótese distinta.
Consistência trata a relação entre observações e mudanças. Neste processo local, o handler não faz await entre checar saldo e alterá-lo; outras requisições não intercalam esse trecho no mesmo event loop. Isso não é uma transação distribuída nem uma defesa contra dois servidores. Um armazenamento durável com unicidade e update condicional é necessário para ampliar o contrato. Se a resposta usar uma réplica atrasada, o cliente pode observar uma versão anterior mesmo depois de um commit em outro lugar. A arquitetura deve dizer qual observação promete, em vez de esconder atrasos com a palavra "tempo real".
Qualidade inclui ameaça, impacto e experiência
FundamentosUma ameaça concreta é consultar o estoque de outra loja com um token válido. Autenticar o token identifica a pessoa; autorizar compara sua conta ao recurso. O teste deve tentar atravessar essa fronteira e exigir recusa antes de devolver conteúdo. Outra ameaça é reutilizar a chave da operação com outra quantidade. Modelar ativos, atores, entradas e efeitos permite escolher esses casos sem depender de listas genéricas. Um teste de ameaça precisa observar a ausência de efeito ou de exposição, e não somente a presença de uma mensagem de erro.
Uma resposta de erro também chega a pessoas. Se a interface usa somente vermelho para informar falha, alguém que não percebe a cor pode perder o resultado. Rótulo textual, foco previsível, operação por teclado e anúncio de mudanças relevantes ajudam a tornar o fluxo utilizável. A API JSON não implementa esses mecanismos; ela pode oferecer códigos estáveis e mensagens que permitam à interface fazê-lo. Testes automáticos de HTTP, portanto, não demonstram acessibilidade da experiência final.
Uma decisão ética concreta é escolher o que registrar quando uma reserva fica incerta. Gravar token e dados completos do cliente facilita uma investigação superficial, mas amplia a exposição se o log for acessado por outra equipe. Um identificador de operação e eventos de estado podem responder à pergunta operacional com menos dados. Também é preciso considerar quem sofre com duplicar uma reserva e quem consegue contestá-la. Evitar dano e respeitar privacidade orientam a escolha de controles e recuperação, sem dispensar revisão humana e conhecimento do contexto do produto.
Exercício aplicado
O cliente consulta saldo três, envia uma reserva e perde a conexão depois de o servidor alterar o estoque. Um retry sem identidade pode repetir o efeito; um cache compartilhado pode mostrar dados de outra loja. Defina contratos que permitam recuperar o recibo sem confundir falha de acompanhamento com falha do trabalho.
- Desenhe cliente, servidor, armazenamento e fronteiras de identidade antes de executar.
- Use ETag para validar leitura e If-Match para proteger uma nova alteração contra revisão antiga.
- Interrompa o cliente depois do efeito e repita a operação com mesma chave e conteúdo.
- Teste outra identidade, conteúdo alterado e revisão antiga; declare persistência e acessibilidade ainda não implementadas.
Abrir resolução comentada
O cliente não recebeu a primeira resposta, mas o servidor já registrou recibo. A repetição autorizada com a mesma chave e binding devolve o recibo existente; quantidade diferente gera 409. A revisão antiga bloqueia uma operação nova, enquanto o replay reconhecido não representa uma nova alteração.
A autorização ocorre antes de qualquer consulta ao ledger ou de comparar ETag. GET usa validação condicional e cache privado; isso não concede acesso a outro usuário. O servidor real roda somente em loopback e o ledger fica em memória: reinício perde essa proteção. A operação é indivisível apenas dentro deste handler síncrono de um processo; produção exige armazenamento transacional e mecanismo de concorrência apropriados.
const http=require('node:http'),assert=require('node:assert/strict');
const esperar=ms=>new Promise(r=>setTimeout(r,ms));
const identidades=new Map([['ana',{ator:'ana',tenant:'A'}],['bia',{ator:'bia',tenant:'B'}]]);
const estoque={tenant:'A',sku:'I2',saldo:3,revisao:1},recibos=new Map();let efeitos=0;
const etag=()=> '"rev-'+estoque.revisao+'"';
function responder(res,status,body,headers={}){if(res.destroyed)return;
res.writeHead(status,{'content-type':'application/json','cache-control':'private, max-age=0, must-revalidate',...headers});
res.end(status===304?'':JSON.stringify(body));}
const server=http.createServer(async(req,res)=>{try{
const identidade=identidades.get((req.headers.authorization||'').replace(/^Bearer /,''));
if(!identidade)return responder(res,401,{erro:'identidade necessaria'},{'www-authenticate':'Bearer'});
if(req.url!=='/lojas/A/produtos/I2'||identidade.tenant!==estoque.tenant)return responder(res,404,{erro:'recurso nao acessivel'});
if(req.method==='GET'){
if(req.headers['if-none-match']===etag())return responder(res,304,null,{etag:etag()});
return responder(res,200,{sku:estoque.sku,saldo:estoque.saldo,revisao:estoque.revisao},{etag:etag()});}
if(req.method!=='POST')return responder(res,405,{erro:'metodo nao permitido'},{allow:'GET, POST'});
let raw='';for await(const c of req){raw+=c;if(raw.length>4096)return responder(res,413,{erro:'corpo grande'});}
const p=JSON.parse(raw),chave=req.headers['idempotency-key'];
if(!Number.isSafeInteger(p.quantidade)||p.quantidade<=0||typeof chave!=='string'||!chave)return responder(res,400,{erro:'entrada invalida'});
const binding=JSON.stringify([identidade.tenant,identidade.ator,req.url,p.quantidade]);
const key=JSON.stringify([identidade.tenant,identidade.ator,chave]),anterior=recibos.get(key);
if(anterior){if(anterior.binding!==binding)return responder(res,409,{erro:'chave com conteudo diferente'});return responder(res,200,anterior.recibo);}
if(!req.headers['if-match'])return responder(res,428,{erro:'revisao necessaria'});
if(req.headers['if-match']!==etag())return responder(res,412,{erro:'revisao mudou'});
if(p.quantidade>estoque.saldo)return responder(res,409,{erro:'saldo insuficiente'});
// Sem await entre checagem e mutacao neste processo unico.
estoque.saldo-=p.quantidade;estoque.revisao++;efeitos++;
const recibo={id:'REC-'+chave,quantidade:p.quantidade,revisao:estoque.revisao};recibos.set(key,{binding,recibo});
if(req.headers['x-simular-atraso'])await esperar(80);
return responder(res,201,recibo);
}catch(e){return responder(res,400,{erro:'corpo invalido'});}});
(async()=>{await new Promise(r=>server.listen(0,'127.0.0.1',r));
const url='http://127.0.0.1:'+server.address().port+'/lojas/A/produtos/I2';
const headers={authorization:'Bearer ana','content-type':'application/json'};
try{
const r=await fetch(url,{headers}),primeiro=await r.json(),versao=r.headers.get('etag');assert.equal(primeiro.saldo,3);
assert(r.headers.get('cache-control').startsWith('private'));
const validacao=await fetch(url,{headers:{...headers,'if-none-match':versao}});assert.equal(validacao.status,304);
assert.equal((await fetch(url,{headers:{...headers,authorization:'Bearer bia','if-none-match':versao}})).status,404);
assert.equal((await fetch(url)).status,401);
const abort=new AbortController();
const tentativa=fetch(url,{method:'POST',headers:{...headers,'if-match':versao,'idempotency-key':'OP1','x-simular-atraso':'1'},body:JSON.stringify({quantidade:1}),signal:abort.signal});
// Espere o efeito, mas interrompa antes da resposta retardada.
const limite=Date.now()+5000;while(efeitos===0&&Date.now()<limite)await esperar(1);assert.equal(efeitos,1);abort.abort();await assert.rejects(()=>tentativa);await esperar(90);
const replay=await fetch(url,{method:'POST',headers:{...headers,'if-match':versao,'idempotency-key':'OP1'},body:JSON.stringify({quantidade:1})});
assert.equal(replay.status,200);assert.equal((await replay.json()).id,'REC-OP1');assert.equal(efeitos,1);
const mudou=await fetch(url,{method:'POST',headers:{...headers,'if-match':versao,'idempotency-key':'OP1'},body:JSON.stringify({quantidade:2})});assert.equal(mudou.status,409);
const antiga=await fetch(url,{method:'POST',headers:{...headers,'if-match':versao,'idempotency-key':'OP2'},body:JSON.stringify({quantidade:1})});assert.equal(antiga.status,412);
const novo=await fetch(url,{headers:{...headers,'if-none-match':versao}});assert.equal(novo.status,200);assert.equal((await novo.json()).saldo,2);
assert.equal((await fetch(url,{method:'POST',headers:{...headers,'idempotency-key':'INVALIDO'},body:JSON.stringify({quantidade:null})})).status,400);
assert.equal(efeitos,1);console.log('HTTP real: ETag, identidade, timeout, replay vinculado e revisao verificados');
}finally{server.closeAllConnections();await new Promise(r=>server.close(r));}
})().catch(e=>{console.error(e);process.exitCode=1;});Como conferir seu resultado
- Outra identidade não recebe representação nem confirmação de ETag.
- Abort do cliente não é descrito como rollback do servidor.
- Replay preserva um único efeito e conteúdo alterado é recusado.
- Nova operação exige revisão atual; cache não substitui autorização.
- README identifica limites do Map e critérios de interface acessível.
Teste sua compreensão
Responda com suas palavras antes de abrir o comentário. Saber explicar uma decisão é parte do domínio.
1. Abortar fetch desfaz a reserva do servidor?
Não.
Cancelar acompanhamento não é uma confirmação de rollback do trabalho.
2. ETag válido autoriza outra conta a ler o recurso?
Não.
Autorização deve acontecer antes da resposta condicional.
3. Por que chave estável precisa ser ligada ao conteúdo?
Para distinguir replay de uma operação diferente.
Reutilizar id com outra quantidade pode aplicar uma autorização fora de seu significado.
Seu progresso fica salvo neste navegador. Concluir a leitura não substitui demonstrar o domínio nos exercícios.
Referências e aprofundamento
Documentação oficial e trabalhos originais. As referências registram o escopo e as limitações para você conferir o que sustentam.
- RFC9110: HTTP Semantics
IETF / RFC Editor • consulta: 2026-10-06
FundamentosMétodos, status, identidade de representação e requisições condicionais.
Limites: Política de idempotency-key do exemplo e contrato da aplicação, não garantia universal de POST.
- RFC9111: HTTP Caching
IETF / RFC Editor • consulta: 2026-10-06
FundamentosCache-Control, validação e reutilização de representações.
Limites: Cache não autentica nem autoriza acesso ao recurso.
- WAI: Introduction to Web Accessibility
W3C WAI • consulta: 2026-10-06
FundamentosAcessibilidade como propriedade do projeto e experiência das pessoas.
Limites: Introdução não e checklist completo WCAG; API JSON não demonstra acessibilidade da interface.
- ACM Code of Ethics and Professional Conduct
ACM • consulta: 2026-10-06
FundamentosEvitar dano, respeitar privacidade e considerar pessoas afetadas.
Limites: Código profissional não substitui análise legal ou avaliação específica de impacto.