Clean Architecture é uma abordagem de design de software proposta por Robert C. Martin (também conhecido como “Uncle Bob”), que visa organizar o código em camadas separadas, cada uma com sua responsabilidade específica. Esta arquitetura permite criar sistemas escaláveis, testáveis e fáceis de manter. Neste artigo, discutiremos como implementar a Clean Architecture em um projeto Node.js utilizando o Express, um popular framework web minimalista.
O que é Clean Architecture?
A Clean Architecture tem como objetivo principal criar sistemas desacoplados e independentes de frameworks e bibliotecas externas. Ela divide o sistema em camadas concêntricas, onde cada camada depende apenas das camadas mais internas. Essas camadas são:
- Entidades: Representam os objetos de negócio e as regras de negócio que não dependem de detalhes externos.
- Casos de Uso: Orquestram as regras de negócio e coordenam o fluxo de dados entre as entidades e os detalhes externos.
- Adaptadores de Interface: Fazem a conversão entre os formatos de dados utilizados pelas entidades e os formatos exigidos pelos detalhes externos.
- Detalhes Externos: Incluem frameworks, bibliotecas, bancos de dados e outras fontes externas com as quais o sistema interage.
Configurando o ambiente
Para começar, crie um novo projeto Node.js e instale o Express:
$ mkdir clean-architecture-node-express
$ cd clean-architecture-node-express
$ npm init -y
$ npm install express
Estrutura de diretórios
Organize a estrutura de diretórios do projeto de acordo com as camadas da Clean Architecture:
clean-architecture-node-express
├── src
│ ├── entities
│ ├── use_cases
│ ├── interfaces
│ │ ├── controllers
│ │ ├── repositories
│ │ └── adapters
│ └── frameworks
│ └── express
└── tests
Exemplo: Sistema de gerenciamento de tarefas
Para ilustrar a implementação da Clean Architecture em Node.js com Express, criaremos um sistema simples de gerenciamento de tarefas. O sistema terá as seguintes funcionalidades:
- Criar tarefas
- Listar tarefas
- Atualizar o status de uma tarefa
- Deletar uma tarefa
Entidades
Crie a entidade Task
no diretório entities
:
// src/entities/Task.js
class Task {
constructor(id, title, completed) {
this.id = id;
this.title = title;
this.completed = completed;
}
}
module.exports = Task;
Casos de Uso
Implemente os casos de uso para as funcionalidades do sistema no diretório use_cases
:
// src/use_cases/CreateTask.js
class CreateTask {
constructor(taskRepository) {
this.taskRepository = taskRepository;
}
async execute(title) {
return this.taskRepository.create(title);
}
}
module.exports = CreateTask;
Faça o mesmo para os casos de uso ListTasks
, UpdateTaskStatus
e DeleteTask
.
// src/use_cases/ListTasks.js
class ListTasks {
constructor(taskRepository) {
this.taskRepository = taskRepository;
}
async execute() {
return this.taskRepository.getAll();
}
}
module.exports = ListTasks;
// src/use_cases/UpdateTaskStatus.js
class UpdateTaskStatus {
constructor(taskRepository) {
this.taskRepository = taskRepository;
}
async execute(id, completed) {
return this.taskRepository.updateStatus(id, completed);
}
}
module.exports = UpdateTaskStatus;
// src/use_cases/DeleteTask.js
class DeleteTask {
constructor(taskRepository) {
this.taskRepository = taskRepository;
}
async execute(id) {
return this.taskRepository.delete(id);
}
}
module.exports = DeleteTask;
Adaptadores de Interface
Crie adaptadores de interface para os repositórios no diretório interfaces/repositories
. Por exemplo, para um repositório de tarefas em memória:
// src/interfaces/repositories/InMemoryTaskRepository.js
const Task = require('../../entities/Task');
class InMemoryTaskRepository {
constructor() {
this.tasks = [];
this.idCounter = 1;
}
async create(title) {
const task = new Task(this.idCounter++, title, false);
this.tasks.push(task);
return task;
}
async getAll() {
return this.tasks;
}
async updateStatus(id, completed) {
const task = this.tasks.find(task => task.id === id);
if (task) {
task.completed = completed;
return task;
}
return null;
}
async delete(id) {
const index = this.tasks.findIndex(task => task.id === id);
if (index !== -1) {
this.tasks.splice(index, 1);
return true;
}
return false;
}
}
module.exports = InMemoryTaskRepository;
Controllers
Crie os controladores no diretório interfaces/controllers
. Estes controladores irão receber as requisições HTTP e interagir com os casos de uso:
// src/interfaces/controllers/TaskController.js
const express = require('express');
const CreateTask = require('../../use_cases/CreateTask');
const ListTasks = require('../../use_cases/ListTasks');
const UpdateTaskStatus = require('../../use_cases/UpdateTaskStatus');
const DeleteTask = require('../../use_cases/DeleteTask');
const InMemoryTaskRepository = require('../repositories/InMemoryTaskRepository');
const taskRepository = new InMemoryTaskRepository();
const router = express.Router();
router.post('/', async (req, res) => {
const createTask = new CreateTask(taskRepository);
const task = await createTask.execute(req.body.title);
res.status(201).json(task);
});
router.get('/', async (req, res) => {
const listTasks = new ListTasks(taskRepository);
const tasks = await listTasks.execute();
res.json(tasks);
});
router.patch('/:id/status', async (req, res) => {
const updateTaskStatus = new UpdateTaskStatus(taskRepository);
const task = await updateTaskStatus.execute(parseInt(req.params.id), req.body.completed);
if (task) {
res.json(task);
} else {
res.status(404).send('Task not found');
}
});
router.delete('/:id', async (req, res) => {
const deleteTask = new DeleteTask(taskRepository);
const success = await deleteTask.execute(parseInt(req.params.id));
if (success) {
res.status(204).send();
}
else { res.status(404).send('Task not found'); } });
module.exports = router;
Express Framework
Configure o aplicativo Express no diretório `frameworks/express`:
// src/frameworks/express/app.js
const express = require('express');
const taskController = require('../../interfaces/controllers/TaskController');
const app = express();
app.use(express.json());
app.use('/tasks', taskController);
module.exports = app;
Iniciando o servidor
Adicione o seguinte script no arquivo package.json
:
{
"scripts": {
"start": "node src/frameworks/express/app.js"
}
}
Agora você pode iniciar o servidor usando o comando npm start
e testar as rotas disponíveis.
Conclusão
Neste artigo, exploramos a aplicação da Clean Architecture em um projeto Node.js com Express. Através do exemplo de um sistema de gerenciamento de tarefas, demonstramos como organizar o código em camadas separadas, de acordo com suas responsabilidades, facilitando a manutenção e a escalabilidade do projeto.
A Clean Architecture é uma abordagem eficiente e flexível que pode ser adaptada a diferentes linguagens e frameworks, e seu uso pode melhorar significativamente a qualidade e a longevidade de seu código. Portanto, é importante considerar essa arquitetura ao desenvolver projetos de software, especialmente aqueles com expectativas de crescimento e evolução contínua.