Organização de Projeto em Arquitetura MVC

Área: Backend | Nível recomendado: Iniciante

Dominar a estruturação de projetos backend através da arquitetura Model-View-Controller (MVC), uma abordagem padrão para organizar o código em componentes distintos de model, view e controller. Isso facilita a manutenção e a escalabilidade do projeto.

Vídeo

Link direto: https://youtu.be/TGS1ViNOpVw

Tópicos

  1. Introdução ao MVC:

    • Explicação teórica sobre a arquitetura MVC e seus benefícios.

    • Análise de como o MVC pode ser implementado em uma aplicação Node.js.

  2. Reestruturação do Projeto:

    • Organização do código atual em model, view e controllers.

    • Refatoração das rotas para utilizar os controllers.

  3. Implementação Prática:

    • Criação de um exemplo prático de CRUD utilizando a arquitetura MVC.

Material de Apoio

Repositório no GitHub

PDF para Download

Miro

Exercícios de Fixação

Use esses exercícios para reforçar o que acabou de aprender. Se errar, leia a explicação para entender melhor o conceito.

Qual é a principal responsabilidade de um Model na arquitetura MVC?

A) Gerenciar a interface do usuário.

B) Processar as solicitações do usuário.

C) Acessar e manipular os dados da aplicação.

D) Configurar as rotas da aplicação.

Resposta correta

Qual é a principal responsabilidade de um Model na arquitetura MVC?

Resposta Correta: C

Feedback: O Model é responsável por acessar e manipular os dados da aplicação, incluindo a lógica de negócios associada.


Complete o código para definir uma rota que utilize um controller na arquitetura MVC.
src/produto/produto.router.js
const express = require('express');
const router = express.Router();
const controller = require('./personagem.controller');

router.____('/', controller.readAll);
router.____('/:id', controller.readById);
router.____('/', controller.create);
router.____('/:id', controller.updateById);
router.____('/:id', controller.deleteById);

module.exports = router;
Resposta correta

Complete o código para definir uma rota que utilize um controller na arquitetura MVC.

Resposta Correta:

router.get('/', controller.readAll);
router.get('/:id', controller.readById);
router.post('/', controller.create);
router.put('/:id', controller.updateById);
router.delete('/:id', controller.deleteById);

Feedback: As rotas são configuradas para utilizar os métodos apropriados do controller, seguindo a estrutura RESTful para operações CRUD.


Verdadeiro ou Falso: Na arquitetura MVC, o Controller é responsável por validar os dados recebidos da View.

A) Verdadeiro

B) Falso

Resposta correta

Verdadeiro ou Falso: Na arquitetura MVC, o Controller é responsável por validar os dados recebidos da View.

Resposta Correta: A

Feedback: Verdadeiro. O Controller é responsável por processar as solicitações do usuário, incluindo a validação dos dados recebidos e a interação com os Models.


Qual comando usamos para instalar o pacote dotenv para carregar variáveis de ambiente?

A) npm install dotenv

B) npm install env

C) npm install dotenv-cli

D) npm install environment

Resposta correta

Qual comando usamos para instalar o pacote dotenv para carregar variáveis de ambiente?

Resposta Correta: A

Feedback: O comando npm install dotenv é usado para instalar o pacote dotenv, que carrega variáveis de ambiente de um arquivo .env para process.env.


Qual é a principal vantagem de usar a arquitetura MVC em um projeto?

A) Reduzir o tempo de desenvolvimento.

B) Melhorar a organização e a separação de responsabilidades no código.

C) Facilitar a criação de interfaces gráficas.

D) Aumentar a velocidade de execução do código.

Resposta correta

Qual é a principal vantagem de usar a arquitetura MVC em um projeto?

Resposta Correta: B

Feedback: A arquitetura MVC melhora a organização e a separação de responsabilidades no código, facilitando a manutenção e a escalabilidade do projeto.

Exercícios de Validação

Esses exercícios testarão sua compreensão prática. Revise o feedback para melhorar suas habilidades.

Desafio de Código

Fornecemos um arquivo index.js que contém um endpoint simples. Separe esse código em um projeto seguindo a arquitetura MVC, criando os arquivos necessários para organizar o projeto em Models, Views e Controllers.

Arquivo Inicial

index.js
const express = require('express');
const app = express();
app.use(express.json());

const produtos = [];

app.post('/produtos', (req, res) => {
  const novoProduto = req.body;

  if (!novoProduto.nome || !novoProduto.preco || !novoProduto.categoria) {
    return res.status(400).send('Corpo da requisição deve conter as propriedades nome, preço e categoria.');
  }

  produtos.push(novoProduto);
  res.status(201).send(novoProduto);
});

app.listen(3000, () => {
  console.log(`Servidor rodando em http://localhost:3000`);
});

Tarefas

  1. Configuração Inicial:

    • Crie a estrutura de pastas conforme o padrão MVC.

      src
      ├── db
      │   └── database-connection.js
      ├── produto
      │   ├── produto.controller.js
      │   ├── produto.router.js
      │   └── produto.service.js
  2. Implementação dos Controllers:

    • Mova a lógica de validação e criação do produto para o controller no arquivo produto.controller.js.

  3. Implementação dos Services:

    • Mova a lógica de manipulação de dados para o service no arquivo produto.service.js.

  4. Configuração das Rotas:

    • Configure as rotas no arquivo produto.router.js para utilizar os métodos do controller.

  5. Atualização do Servidor:

    • Atualize o arquivo index.js para usar as rotas configuradas.

Código Esperado

src/produto/produto.controller.js
const service = require('./produto.service');

async function create(req, res) {
  const novoProduto = req.body;

  if (!novoProduto.nome || !novoProduto.preco || !novoProduto.categoria) {
    return res.status(400).send('Corpo da requisição deve conter as propriedades nome, preço e categoria.');
  }

  await service.create(novoProduto);
  res.status(201).send(novoProduto);
}

module.exports = {
  create
};
src/produto/produto.service.js
const produtos = [];

function create(novoProduto) {
  produtos.push(novoProduto);
}

module.exports = {
  create
};
src/produto/produto.router.js
const express = require('express');
const controller = require('./produto.controller');

const router = express.Router();

router.post('/', controller.create);

module.exports = router;
index.js
const express = require('express');
const produtoRouter = require('./src/produto/produto.router');
const app = express();
app.use(express.json());

app.use('/produtos', produtoRouter);

app.listen(3000, () => {
  console.log(`Servidor rodando em http://localhost:${port}`);
});

{%

endcode %}

Feedback: Certifique-se de que a lógica de validação está no controller e a lógica de manipulação de dados está no service. Configure as rotas corretamente para usar o controller.

Revisão de Código

index.js
const express = require('express');
const { MongoClient, ObjectId } = require('mongodb');
const app = express();
app.use(express.json());

const dbUrl = process.env.DATABASE_URL;
const client = new MongoClient(dbUrl);

async function main() {
  await client.connect();
  const db = client.db('app');
  const collection = db.collection('produtos');

  app.post("/produtos", async function (req, res) {
    const novoItem = req.body;

    if (!novoItem.nome || !novoItem.preco || !novoItem.categoria) {
      return res.status(400).send('Corpo da requisição deve conter as propriedades nome, preço e categoria.');
    }

    await collection.insertOne(novoItem);
    res.status(201).send(novoItem);
  });

  app.listen(3000, function () {
    console.log(`Aplicação rodando em http://localhost:3000`);
  });
}

main();
Resposta Correta

Erros:

  1. A lógica de manipulação de dados deve ser movida para o service para manter a separação de responsabilidades.

  2. O código de validação e criação de produtos deve ser movido para o controller.

src/produto/produto.controller.js
const service = require('./produto.service');

async function create(req, res) {
  const novoItem = req.body;

  if (!novoItem.nome || !novoItem.preco || !novoItem.categoria) {
    return res.status(400).send('Corpo da requisição deve conter as propriedades nome, preço e categoria.');
  }

  await service.create(novoItem);
  res.status(201).send(novoItem);
}

module.exports = {
  create
};
src/produto/produto.service.js
const { ObjectId } = require('mongodb');
const { getDatabase } = require('../db/database-connection');

function getCollection() {
  return getDatabase().collection('produtos');
}

function create(novoItem) {
  return getCollection().insertOne(novoItem);
}

module.exports = {
  create
};
src/produto/produto.router.js
const express = require('express');
const controller = require('./produto.controller');

const router = express.Router();

router.post('/', controller.create);

module.exports = router;
index.js
require('dotenv').config();
const express = require('express');
const { connectToDatabase } = require('./src/db/database-connection');
const produtoRouter = require('./src/produto/produto.router');

async function main() {
  await connectToDatabase();

  const app = express();
  app.use(express.json());

  app.use('/produtos', produtoRouter);

  const port = process.env.PORT || 3000;
  app.listen(port, function () {
    console.log(`Servidor rodando em http://localhost:${port}`);
  });
}

main();

Feedback: Certifique-se de que a lógica de manipulação de dados está no service e a lógica de controle está no controller para manter a separação de responsabilidades conforme a arquitetura MVC.

Projeto Prático

Reestruture uma aplicação Node.js existente para seguir a arquitetura MVC. A aplicação deve gerenciar uma entidade "Produtos" com as propriedades "nome", "preço" e "categoria". Crie os arquivos necessários para organizar o projeto em Models, Views e Controllers, e implemente operações CRUD para a entidade "Produtos".

Tarefas

  1. Configuração Inicial:

    • Inicialize um novo projeto NodeJS.

    • Instale o ExpressJS e o MongoDB Driver.

    • Configure o Nodemon para reiniciar o servidor automaticamente.

    • Crie um arquivo .env e adicione as variáveis de ambiente necessárias.

  2. Estrutura de Pastas:

    • Crie a estrutura de pastas conforme o padrão MVC.

      src
      ├── db
      │   └── database-connection.js
      ├── produto
      │   ├── produto.controller.js
      │   ├── produto.router.js
      │   └── produto.service.js
  3. Conexão com o MongoDB:

    • Configure a conexão com o MongoDB utilizando o MongoClient e as variáveis de ambiente no arquivo database-connection.js.

  4. Implementação dos Controllers:

    • Implemente os métodos do controller para operações CRUD no arquivo produto.controller.js.

  5. Implementação dos Services:

    • Implemente os métodos de acesso aos dados no arquivo produto.service.js.

  6. Configuração das Rotas:

    • Configure as rotas no arquivo produto.router.js para utilizar os métodos do controller.

  7. Inicialização do Servidor:

    • Configure a inicialização do servidor no arquivo index.js e adicione as rotas.

Código Esperado
src/db/database-connection.js
const { MongoClient } = require('mongodb');

const dbUrl = process.env.DATABASE_URL;
const dbName = 'mvc-produtos';

const client = new MongoClient(dbUrl);

async function connectToDatabase() {
  console.log('Conectando ao banco de dados...');
  await client.connect();
  console.log('Banco de dados conectado com sucesso!');
}

function getDatabase() {
  return client.db(dbName);
}

module.exports = {
  connectToDatabase,
  getDatabase
};
src/produto/produto.controller.js
const service = require('./produto.service');

async function readAll(req, res) {
  const items = await service.readAll();
  res.send(items);
}

async function readById(req, res) {
  const id = req.params.id;
  const item = await service.readById(id);

  if (!item) {
    return res.status(404).send('Produto não encontrado.');
  }

  res.send(item);
}

async function create(req, res) {
  const novoItem = req.body;

  if (!novoItem || !novoItem.nome || !novoItem.preco || !novoItem.categoria) {
    return res.status(400).send('Corpo da requisição deve conter as propriedades nome, preço e categoria.');
  }

  await service.create(novoItem);
  res.status(201).send(novoItem);
}

async function updateById(req, res) {
  const id = req.params.id;
  const novoItem = req.body;

  if (!novoItem || !novoItem.nome || !novoItem.preco || !novoItem.categoria) {
    return res.status(400).send('Corpo da requisição deve conter as propriedades nome, preço e categoria.');
  }

  await service.updateById(id, novoItem);
  res.send(novoItem);
}

async function deleteById(req, res) {
  const id = req.params.id;
  await service.deleteById(id);
  res.send('Produto removido com sucesso: ' + id);
}

module.exports = {
  readAll,
  readById,
  create,
  updateById,
  deleteById
};
src/produto/produto.service.js
const { ObjectId } = require('mongodb');
const { getDatabase } = require('../db/database-connection');

function getCollection() {
  return getDatabase().collection('produtos');
}

function readAll() {
  return getCollection().find().toArray();
}

function readById(id) {
  return getCollection().findOne({ _id: new ObjectId(id) });
}

function create(novoItem) {
  return getCollection().insertOne(novoItem);
}

function updateById(id, novoItem) {
  return getCollection().updateOne(
    { _id: new ObjectId(id) },
    { $set: novoItem }
  );
}

function deleteById(id) {
  return getCollection().deleteOne({ _id: new ObjectId(id) });
}

module.exports = {
  readAll,
  readById,
  create,
  updateById,
  deleteById
};
src/produto/produto.router.js
const express = require('express');
const controller = require('./produto.controller');

const router = express.Router();

router.get('/', controller.readAll);
router.get('/:id', controller.readById);
router.post('/', controller.create);
router.put('/:id', controller.updateById);
router.delete('/:id', controller.deleteById);

module.exports = router;
index.js
require('dotenv').config();
const express = require('express');
const { connectToDatabase } = require('./src/db/database-connection');

const produtoRouter = require('./src/produto/produto.router');

async function main() {
  await connectToDatabase();

  const app = express();
  app.use(express.json());

  app.get('/', function (req, res) {
    res.send('Hello World');
  });

  app.use('/produtos', produtoRouter);

  const port = process.env.PORT || 3000;
  app.listen(port, function () {
    console.log(`Servidor rodando em http://localhost:${port}`);
  });
}

main();

Feedback Detalhado

  • Verificação dos Endpoints: Certifique-se de testar cada rota (/produtos, /produtos/:id, /produtos com POST, PUT e DELETE) acessando http://localhost:3000 e verificando as respostas.

  • Estrutura do Projeto: Verifique se a estrutura de pastas segue o padrão MVC, com Models, Views e Controllers bem definidos.

  • Uso de Variáveis de Ambiente: Certifique-se de que as informações sensíveis, como a URL do banco de dados, estão sendo carregadas do arquivo .env.

Last updated