Buscar/Adicionar código de backend e os testes certos a uma aplicação com vários serviços

Adicionar código de backend e os testes certos a uma aplicação com vários serviços

Para um agente de IA que altera uma aplicação com vários serviços e workers em segundo plano. Confere o ambiente e os serviços, põe o código de banco de dados e de tarefas no lugar certo, define jobs e erros do jeito do projeto, escolhe um tipo de teste e planeja antes de codificar.

Para usar estas regras de IA
## Preparar o ambiente e confirmar que os serviços estão rodando - As dependências do backend ficam em um ambiente virtual gerenciado por arquivo de lock na raiz do repositório. Se ele ainda não existir, crie-o a partir do arquivo de lock congelado e ative-o antes de executar qualquer coisa. - As credenciais e chaves necessárias aos testes ficam no arquivo de ambiente local do repositório. Leia-as de lá, em vez de inventar valores ou pedir novos. - Presuma que todos os serviços da aplicação já estão em execução, mas verifique em vez de confiar: consulte o diretório de logs e confirme que o serviço em questão continua emitindo entradas. - Acesse o banco de dados através do contêiner em execução usando a invocação de cliente documentada pelo projeto, e não através de uma conexão improvisada por você. - Ao exercitar a aplicação web por automação de navegador, entre com a conta que a configuração ponta a ponta cria. Se ela não existir, registre-a pela página de cadastro, já que a primeira conta registrada se torna a administradora. ## Manter as consultas ao banco de dados em uma só camada e enviar as chamadas de teste pelo frontend - Coloque toda operação de banco de dados dentro dos diretórios de acesso a dados designados. Não execute consultas de nenhum outro lugar, para que o conhecimento do esquema permaneça em uma única camada e possa ser revisado em um só lugar. - Ao chamar o backend durante testes manuais ou por script, passe sempre pela origem do frontend, e não diretamente pela porta do backend. Uma requisição que pula o frontend também pula o comportamento de proxy, sessão e autenticação pelo qual o tráfego real passa, de modo que o resultado não é representativo. ## Colocar o trabalho novo no pool de workers em segundo plano que combina com ele O trabalho assíncrono é dividido entre pools de workers especializados, em vez de uma única fila geral. Coloque cada novo trabalho no pool cujo perfil corresponda a ele. - Um pool primário coordena operações que abrangem todo o sistema: exclusão de recursos, sincronização, poda e verificações periódicas. - Os pools de busca puxam dados de fontes externas e geram uma tarefa de processamento por lote, com monitoramento por watchdog para que uma fonte travada seja detectada. - Os pools de processamento executam o pipeline de ingestão: persistem registros, dividem-nos em chunks, enriquecem, geram embeddings, escrevem no índice de busca e atualizam os metadados. - Um pool leve trata operações curtas com alta concorrência. Um pool pesado trata operações intensivas em recursos com baixa concorrência. - Um pool de monitoramento, de thread única porque o monitoramento não precisa de paralelismo, coleta métricas de profundidade de fila, memória e saúde. - Um pool agendador é dono dos gatilhos periódicos, desde verificações em intervalos medidos em segundos até limpezas de hora em hora, e resolve o tenant de cada tarefa agendada. Propriedades comuns a todos os pools: pools de threads em vez de processos para dar estabilidade, isolamento por tenant, filas de prioridade, heartbeat e verificação de vivacidade, nova tentativa automática e recuperação de falhas, um armazenamento compartilhado rápido para a coordenação entre processos, e o banco de dados relacional para o estado durável das tarefas. ## Dar a cada tarefa em segundo plano uma expiração e um timeout próprio - Declare as tarefas com o decorador independente de aplicação do framework, em vez de vinculá-las a uma instância concreta de aplicação, para que importar uma tarefa nunca arraste junto a construção da aplicação. - Mantenha os módulos de tarefa sob os diretórios de tarefas designados para a sua edição ou nível. - Nunca enfileire uma tarefa sem uma expiração. Forneça uma, seja a tarefa enviada pelo agendamento ou por outra tarefa. Submeter código que enfileira sem expiração não é aceitável, porque isso deixa a fila crescer sem limite. - Como as tarefas são executadas em pools de threads, os próprios recursos de limite de tempo do framework ficam silenciosamente desativados. Implemente a lógica de timeout dentro da própria tarefa. - Os workers não recarregam quando o código muda. Depois de alterar o código de um worker, peça ao mantenedor que reinicie o worker antes de testar, e não presuma que o novo código está ativo. ## Adicionar endpoints e lançar erros com o tipo de erro do próprio projeto - Ao adicionar um endpoint, tipe a própria função handler diretamente, em vez de declarar um campo separado de modelo de resposta. - Lance o tipo de erro próprio do projeto em vez da exceção HTTP genérica do framework, e nunca fixe códigos de status no código nem importe constantes de status do framework. - Um handler global converte o erro do projeto em um único corpo de resposta padrão, que carrega um código de erro e um detalhe. Isso elimina código repetitivo e mantém o tratamento de erros consistente em todo o backend. - Os códigos de erro vêm de um registro central. Se for necessária uma nova categoria, adicione-a primeiro ao registro; nunca invente um código no ponto de chamada. - Ao repassar uma falha de um serviço upstream cujo status vem da resposta do upstream, passe esse status pelo argumento de sobrescrita de status do tipo de erro, em vez de abandonar o tipo de erro. ## Manter tipagem estrita e escrever só comentários que continuem úteis - Instale e execute os hooks de pre-commit do repositório sobre todos os arquivos antes de propor uma mudança. - Mantenha tudo estritamente tipado, tanto no backend quanto no frontend. Saídas de emergência sem tipagem não são um atalho aceitável. - Mantenha os comentários curtos e limitados a informações que continuam relevantes a longo prazo. Não escreva comentários que narrem a mudança em andamento, como o que acabou de ser adicionado, removido ou refatorado; eles viram ruído no instante em que a mudança é integrada. ## Escrever migrações de banco de dados à mão para o esquema padrão e o privado - Gere o arquivo de revisão com a ferramenta de migração e depois escreva o corpo da migração manualmente dentro do arquivo que ela cria. Não confie na geração automática para expressar a intenção. - Implantações multi-tenant precisam que a migração seja aplicada à configuração de esquema privado além da configuração padrão, e a mesma distinção vale na hora de criar a revisão. - Trate a migração como parte da mudança em revisão, não como um complemento aplicado depois. ## Escolher o único tipo de teste que comprova a mudança, com poucos mocks Existem quatro níveis. Prefira os níveis de maior fidelidade a um uso pesado de mocks, e escolha aquele que realmente comprova o comportamento. - Os testes unitários presumem que nenhum serviço está acessível e fazem mock de toda interação com o mundo externo. Escreva-os apenas para módulos complexos e isolados cuja lógica valha a pena fixar por si só. - Os testes unitários apoiados em dependências presumem que as dependências externas estão em execução — banco de dados, cache, armazenamento de objetos, índice de busca, rede de saída — enquanto os contêineres da aplicação não estão. As funções são chamadas diretamente e componentes selecionados ainda podem receber mock. Use-os para minimizar o uso de mocks e ao mesmo tempo manter a capacidade de controlar comportamento instável ou de verificar que uma chamada interna aconteceu com argumentos específicos, o que um teste de integração puro não consegue fazer. - Os testes de integração rodam contra uma implantação real e não fazem mock de nada. Prefira esses, recorrendo aos testes unitários apoiados em dependências quando mocks ou verificação interna forem realmente necessários. - Os testes conduzidos por navegador acrescentam o servidor web sobre a implantação completa. Use-os para comportamentos que precisam de coordenação substancial entre frontend e backend. Não teste em excesso. Uma dada mudança geralmente precisa de um tipo de teste, não dos quatro. ## Reaproveitar fixtures e helpers existentes ao escrever testes de integração - Antes de escrever um teste de integração, leia a configuração de testes da raiz para ver as fixtures disponíveis e o diretório de utilitários compartilhados para ver os helpers. - Prefira os helpers de manager desses utilitários a chamar endpoints diretamente com uma biblioteca HTTP crua, e prefira as fixtures a chamar os helpers na mão. Criar um administrador manualmente quando já existe uma fixture de administrador duplica a preparação e se afasta do restante da suíte. - Os testes são paralelizados no nível de diretório, então os arquivos dentro de um diretório não devem depender de estado mutável compartilhado. - Quando um teste faz uma chamada real a um modelo, use o nível barato e rápido de cada provedor, e não um modelo de topo de linha. O custo dos testes cresce junto com a suíte, e o que está sendo verificado não depende da potência do modelo. - Copie o formato de um teste exemplar já existente na mesma categoria, em vez de inventar uma nova estrutura. ## Ler os logs de cada serviço antes de adivinhar por que algo falhou - Ao escrever testes de integração ou conduzir o sistema em execução na mão, leia os arquivos de log de depuração de cada serviço no diretório de logs. Todo serviço direciona continuamente sua saída para lá. - Leia os logs antes de formar uma teoria sobre uma falha. Uma causa não relacionada, como um serviço que não está em execução, parece idêntica a um defeito real até que o log seja lido. ## Não colocar chaves de API no repositório e proteger os endpoints novos - Nunca faça commit de chaves de API ou segredos no repositório. - Armazene as credenciais de integração pelo armazenamento criptografado de credenciais, nunca em configuração em texto simples. - Siga os padrões estabelecidos de controle de acesso baseado em papéis ao adicionar uma funcionalidade. Um novo endpoint não herda nenhuma proteção por padrão. - Valide a entrada com modelos de esquema na fronteira. - Use consultas parametrizadas para que a injeção seja impossível por construção, e nunca monte uma consulta por concatenação de strings. ## Marcar cada chamada a um modelo de IA com um nome de operação registrado para rastreamento Toda chamada a um modelo de linguagem, modelo de embedding, reranker, gerador de imagens, componente de fala ou classificador de intenção deve abrir um span de geração marcado com um valor do registro central de fluxos. Use o wrapper correspondente ao caminho da chamada: um para chamadas feitas pela abstração de modelo, outro para chamadas diretas ao provedor ou por HTTP que a contornam. - Adicione um novo valor ao registro antes de instrumentar uma nova operação. Nunca passe uma string crua como tag. - As tags nomeiam a operação, como reranking ou edição de imagem, e não o provedor. O provedor pertence aos metadados de configuração do modelo. - O fallback automático emite valores sentinela sem tag para as chamadas que chegam à abstração sem um span explícito. Esses sentinelas ficam visíveis nos dashboards e significam que falta instrumentação. Corrija o ponto de chamada em vez de depender do fallback. ## Pesquisar o código e depois escrever um plano sem cronograma e sem código Pesquise antes de escrever. Explore primeiro as partes relevantes da base de código; um plano escrito a partir de suposições é pior do que nenhum plano. Um plano inclui pelo menos estes elementos: - Questões a resolver: o que a mudança deve fazer. - Notas importantes: coisas encontradas durante a pesquisa que importam para a implementação. - Estratégia de implementação: a abordagem de alto nível para fazer a mudança acontecer. - Testes: quais testes vão verificar o comportamento correto, escolhidos deliberadamente e mantidos no mínimo. Não inclua uma seção de cronograma e não inclua um tratamento das etapas de rollback. Não escreva código como parte do plano; mantenha-o em alto nível, embora referir-se a arquivos ou funções específicos seja útil. Esta é uma lista mínima, então acrescente mais quando isso ajudar. ## Ler as regras próprias de um subprojeto antes de trabalhar nele - A orientação da raiz cobre as preocupações compartilhadas. Cada subprojeto mantém seu próprio documento de padrões junto ao seu código, e esse documento rege a sua própria árvore. - Leia o documento de escopo restrito antes de trabalhar naquela área. Não presuma que as regras de um cliente valem para outro: um cliente mobile sem um modelo de objeto de documento, com primitivas de estilo cujos valores de espaçamento são absolutos em vez de uma escala relativa de passos, e com suas próprias primitivas de roteamento e de visualização, segue regras diferentes das de um cliente web. - Práticas de engenharia mais amplas ficam no guia de contribuição do repositório. Leia-o e siga-o além deste documento.