O projeto "Parque de Dinossauros" é uma aplicação backend desenvolvida em Node.js com Express e MongoDB (Mongoose) cujo objetivo é gerenciar entidades relacionadas a um parque temático de dinossauros. Contempla o cadastro, listagem e remoção de dinossauros, além da estrutura para gerenciamento de recintos (habitats). A arquitetura busca ser extensível, modular e alinhada a princípios de engenharia de software como SOLID, Clean Code, KISS e recomendações de Object Calisthenics.
- Fornecer uma API REST simples e evolutiva para operações CRUD de domínio.
- Estabelecer base arquitetural organizada para futura expansão (ex.: alocação de dinossauros em recintos, controle de capacidade, relatórios).
- Demonstrar boas práticas de modelagem com Mongoose e separação de responsabilidades.
- Conexão com MongoDB.
- Modelos Mongoose:
DinossauroeRecinto. - Repositórios para abstração de persistência.
- Rotas REST para dinossauros (
/dinossauros). - Script de inicialização que realiza seed inicial (exemplo didático).
- Autenticação e autorização.
- Testes automatizados (unitários/integrados).
- Versionamento de API.
- Observabilidade (logs estruturados, métricas, tracing).
A organização segue um padrão modular visando clareza e expansão progressiva.
src/
index.js # Ponto de entrada (bootstrap da aplicação)
controllers/
mongo.js # Conexão com MongoDB
models/
Dinossauro.js # Schema e Model Mongoose de Dinossauro
Recinto.js # Schema e Model Mongoose de Recinto
respositories/
DinossauroRepository.js # Operações de persistência para Dinossauro
RecintoRepository.js # Operações de persistência para Recinto
routes/
dinossauro.js # Definição das rotas REST de Dinossauro
services/ # (Reservado para regras de negócio mais complexas)
entities/ # (Legado: modelos de domínio puros, em transição)
| Camada | Responsabilidade Principal | Observações |
|---|---|---|
index.js |
Inicializar servidor, conectar ao banco, registrar rotas e semear dados de exemplo | Evitar lógica de domínio extensa |
controllers |
Coordenação de aspectos técnicos (ex.: conexão a banco) | Pode evoluir para controladores HTTP |
models |
Definição de Schemas e Models Mongoose | Define constraints e valores padrão |
respositories |
Abstração de persistência (CRUD) | Facilita testes e futura troca de ODM/BD |
routes |
Mapeamento HTTP → handlers | Deve permanecer fino e delegar regras |
services |
Regras de negócio (futuro) | Orquestra operações complexas |
entities |
Modelos de domínio puros (legado) | Podem ser removidos ou absorvidos pelos serviços |
- Separação de Responsabilidades (SRP): Cada diretório tem foco reduzindo acoplamento.
- Evolução Controlada: A introdução futura de
servicesisola regras de negócio. - Testabilidade: Repositórios e serviços podem ser mockados.
- Substituibilidade: Troca de banco demanda alterações pontuais.
- Cliente envia requisição para
/dinossauros. - Express roteia para o handler em
routes/dinossauro.js. - Handler consulta o Model
Dinossauro(ou repositório, em evolução futura). - Resposta JSON retornada ao cliente.
Futuro: rota → serviço (validações, regras) → repositório → model → banco.
Campos:
nome(String, obrigatório, trim)especie(String, obrigatório, trim)idade(Number, padrão 0, mínimo 0)recintoId(ObjectId, referência opcional aRecinto)
Decisões:
- Uso de
trimpara sanitização. recintoIdé opcional permitindo criação antes de alocar habitat.- Validação de tipos delegada ao Mongoose.
Campos:
tipo(String, obrigatório) – ex.: Carnivoro, Herbivoro.nome(String, obrigatório, único desejável futuramente).capacidade(Number, mínimo 1) – limite de dinossauros.
Relação 1:N – um Recinto pode conter vários dinossauros (referência armazenada no Dinossauro). Futuro: criar índice composto ou campo derivado para contagem e validação de capacidade.
- Índices:
{ nome: 1 }em Dinossauro e Recinto para busca rápida. - Restrições adicionais: unicidade de
nomede recinto. - Validação de compatibilidade (
tipodo recinto vs espécie) via serviço.
- Carregamento de dependências em
index.js. - Execução de
connectMongo()com tratamento de erro e encerramento em falha. - Inicialização do servidor Express e registro de rotas.
- Execução de bloco assíncrono para semear dados (exemplo didático).
- Atendimento de requisições REST.
Risco atual: o seed executa a cada inicialização podendo gerar duplicações. Recomenda-se mecanismo idempotente.
Base URL padrão: http://localhost:3000
GET /dinossauros
Resposta 200:
[
{
"_id": "...",
"nome": "Rex",
"especie": "Tyranossaurus",
"idade": 5,
"recintoId": null
}
]POST /dinossauros
Body JSON:
{ "nome": "Blue", "especie": "Velociraptor", "idade": 2 }Respostas:
- 201 Created (objeto persistido)
- 400 Bad Request (falha de validação)
DELETE /dinossauros/:id
Respostas:
- 200 (mensagem de sucesso)
- 404 (não encontrado)
| Princípio | Situação Atual | Oportunidade de Melhoria |
|---|---|---|
| SRP | Camadas separadas (models, repos, rotas) | Mover lógica de seed e futuras regras para services |
| OCP | Repositórios permitem extensão | Isolar interfaces e permitir múltiplas implementações |
| LSP | Não aplicável diretamente ainda | Definir interfaces para repositórios antes de especializações |
| ISP | Interfaces ainda inexistentes | Criar interfaces finas para serviços (ex.: AlocacaoService) |
| DIP | Rotas dependem diretamente de Models | Introduzir inversão via injeção de repositórios/serviços |
Regras (resumo) e estado: Lista de regras e situação:
- Small Classes: Estrutura enxuta. Serviços ainda não criados (melhorar ao extrair regras).
- Small Methods: Handlers curtos; manter assim ao evoluir.
- No Primitives Obsession: Ainda há uso de tipos primitivos para espécie/tipo; pode-se criar Value Objects futuros.
- Use First-Class Collections: Listas retornadas diretamente; pode-se encapsular coleção de dinossauros se regras agregadas surgirem.
- One Level of Indentation per Method: Atendido na maioria dos métodos.
- No Else: Pouco uso de
else; early return pode ser reforçado. - Wrap All Primitives and Strings: Futuro para atributos sensíveis (ex.: NomeDinossauro).
- First-Class Collections: Ver item 4.
- No Classes with More Than Two Instance Variables: Models Mongoose possuem poucos campos – adequado.
Aplicações: Lista de aspectos aplicados:
- Estrutura mínima necessária (KISS).
- Nomes descritivos (
DinossauroRepository,connectMongo). - Evita lógica complexa nas rotas (Clean Code – thin controllers).
- Uso de defaults e validações no schema reduz boilerplate.
Melhorias sugeridas: Melhorias sugeridas:
- Introduzir camada de serviços para não ampliar rotas.
- Remover duplicação de seed repetido.
- Centralizar mensagens de erro.
Atual: Situação atual:
- Validações de obrigatoriedade e tipos delegadas ao Mongoose.
- Try/catch em criação de dinossauro (rota POST).
- Encerramento do processo em falha de conexão (fail fast).
Sugestões: Recomendações:
- Middleware global de tratamento de erros.
- Padronização de payload de erro:
{ codigo, mensagem, detalhe }. - Validação adicional semântica (compatibilidade de recinto) em serviço.
Itens a considerar: Itens a considerar:
- Uso de variáveis de ambiente (
dotenv) para URI do Mongo. - Desabilitar seed automático em produção.
- Logs estruturados (ex.: pino / winston).
- Rate limiting (ex.: para POST/DELETE).
- Helmet para cabeçalhos HTTP de segurança.
| Prioridade | Item | Justificativa |
|---|---|---|
| Alta | Idempotência do seed | Evitar dados duplicados |
| Alta | Camada de serviços | Encapsular regras e evitar crescimento das rotas |
| Alta | Alocação de dinossauro a recinto com validação de capacidade | Requisito de domínio |
| Média | Testes unitários e de integração | Garantia de qualidade |
| Média | Middleware de erros | Respostas consistentes |
| Média | Variáveis de ambiente | Configuração segura |
| Baixa | Value Objects (Nome, Espécie) | Robustez semântica |
| Baixa | Documentação OpenAPI/Swagger | Facilidade de consumo |
Pré-requisitos: Node.js LTS, MongoDB em execução local (mongodb://localhost:27017).
Instalação de dependências:
npm installExecução:
node src/index.js- Baixo acoplamento inicial.
- Fácil extensão de endpoints.
- Repositórios simplificam substituição de persistência.
Projeto didático; nenhuma licença específica definida ainda.
Documento completo – versão inicial consolidada.
Condição de corrida ocorre quando duas ou mais operações concorrentes acessam e modificam o mesmo recurso sem coordenação adequada, produzindo estado inconsistente. Em aplicações com banco de dados, isso pode gerar efeitos como ultrapassar capacidade, duplicar registros ou retornar dados parcialmente atualizados.
- Criação simultânea do mesmo dinossauro (mesmo
nome). - Alocação concorrente de múltiplos dinossauros no mesmo recinto (risco de exceder
capacidade). - Seed executado repetidamente em múltiplas instâncias (dados duplicados).
- Remoção de dinossauro enquanto outra requisição tenta associá-lo a um recinto.
- Atualização de
recintoIdenquanto o recinto é removido (estado órfão).
- Índices Únicos: Definir
unique: true(ex.: camponomede Recinto e, se regra de negócio exigir, de Dinossauro) evita duplicação em nível de banco. - Verificação Atômica de Capacidade: Em vez de contar manualmente e depois inserir, usar padrão de atualização condicional / transação.
- Variante simples: contar e comparar dentro de uma transação (necessita replicaset:
mongod --replSet rs0). - Padrão: manter no Recinto um campo
ocupadoe usarfindOneAndUpdate({ _id, ocupado: { $lt: capacidade } }, { $inc: { ocupado: 1 } })antes de criar o dinossauro.
- Variante simples: contar e comparar dentro de uma transação (necessita replicaset:
- Transações (Sessions Mongoose): Agrupar sequência (incrementar ocupação + criar dinossauro) garantindo atomicidade.
- Optimistic Locking: Usar
versionKey(__v) para detectar atualização concorrente; ao salvar, erro de versão sinaliza retry. - Idempotência de Seed: Substituir inserções simples por
updateOne({ nome: ... }, { $setOnInsert: {...} }, { upsert: true })evitando duplicações. - Soft Delete / Flags: Antes de remoções físicas, marcar como inativo para evitar condição em curso (dependendo de requisitos futuros).
- Timeouts e Retry Exponencial: Ao detectar erro de concorrência (duplicate key, version error), reprocessar com limite de tentativas.
async function alocarDinossauroNoRecinto(dinoData, recintoId, session) {
const recinto = await Recinto.findOneAndUpdate(
{ _id: recintoId, ocupado: { $lt: capacidade } },
{ $inc: { ocupado: 1 } },
{ new: true, session }
);
if (!recinto) throw new Error("Capacidade esgotada");
const dino = await Dinossauro.create([{ ...dinoData, recintoId }], {
session,
});
return dino[0];
}Requer campo adicional ocupado no schema de Recinto e ambiente com transações habilitadas.
- Prevenção: constraints (unique), transações e atualizações condicionais.
- Detecção: optimistic locking (falha tardia) para retry.
| Ordem | Ação | Justificativa |
|---|---|---|
| 1 | Idempotência de seed | Evita sujar ambiente |
| 2 | Índice único em Recinto.nome |
Consistência de referência |
| 3 | Campo ocupado + update atômico |
Garante capacidade |
| 4 | Transações para alocação | Atomicidade completa |
| 5 | Optimistic locking para atualizações complexas | Controle fino de concorrência |
| Problema | Causa Raiz | Solução Aplicada / Recomendada |
|---|---|---|
ReferenceError: require is not defined |
Projeto marcado como ES Module ("type": "module") mas uso de require |
Migrar para import/export ou remover "type": "module" se quiser CommonJS |
TypeError: Dinossauro is not a constructor |
Exportação incorreta (faltava export default) ou import errado |
Garantir export default no model e usar import Dinossauro from ... |
ERR_MODULE_NOT_FOUND |
Falta de extensão .js em import ES Modules |
Adicionar sufixo .js em todos os imports relativos |
connectMongo is not a function |
Export nomeado vs default inconsistente | Unificar para export default e ajustar import |
Cannot read properties of null (reading '_id') |
Consulta a Recinto antes de criar (ordem de execução) | Reordenar seed: criar recintos antes de referenciá-los e validar retorno |
| Duplicação de registros em reinicialização | Seed sempre insere dados | Tornar seed idempotente (upsert) ou condicional a ambiente |
| Mistura de repositório e acesso direto a Model | Duplicação de lógica e inconsistência | Centralizar acesso via repositórios ou via serviços abstraídos |
| Crescimento não controlado das rotas | Lógica de domínio dentro de handlers | Extrair para camada de serviços |
| Falta de validação de capacidade | Não existe controle transacional | Implementar fluxo atômico conforme seção 15 |
Nomes divergentes (respositories) |
Erro de digitação no nome do diretório | Renomear para repositories (alterar imports) |
- Registrar mensagem completa e stack trace.
- Classificar: configuração, dependência, lógica de domínio, concorrência.
- Reproduzir em ambiente local isolado.
- Criar teste que falha (quando houver suíte de testes).
- Corrigir com mudança mínima.
- Documentar no README (tabela acima) se recorrente.
| Código | Situação |
|---|---|
| DINO_DUPLICATE | Tentativa de criar dinossauro já existente |
| RECINTO_CAPACITY | Capacidade excedida |
| RECINTO_NOT_FOUND | Recinto inexistente |
| DINO_NOT_FOUND | Dinossauro não encontrado |
| VALIDATION_FAILED | Erro de validação de entrada |
| INTERNAL_ERROR | Falha inesperada |
| Prioridade | Item | Justificativa |
|---|---|---|
| Alta | Seed idempotente (upsert) | Eliminar duplicação (condição de corrida e reinício) |
| Alta | Índice único Recinto.nome |
Integridade de referência |
| Alta | Fluxo atômico de alocação (ocupado + transação) |
Evitar ultrapassar capacidade |
| Média | Otimistic locking para atualizações complexas | Detectar conflitos |
| Média | Middleware de erro padronizado | Consistência de respostas |
| Média | Refatorar respositories → repositories |
Clareza e padrão |
| Baixa | Value Objects (Nome, Espécie) | Semântica forte |
| Baixa | Soft delete para Recinto | Evitar órfãos temporários |
| Baixa | OpenAPI/Swagger | Comunicação externa |