Boas-vindas ao curso de Design Patterns ou Padrão de Projetos!
Sou Emerson Laranja, instrutor na Escola de Programação da Alura.
Audiodescrição: Emerson é um homem negro com barba e cabelos escuros. Usa óculos de grau quadrado e uma camiseta verde. Ao fundo, uma parede lisa com iluminação degradê do verde ao azul.
Se você tem experiência com TypeScript criando APIs e busca ir além do código funcional, aplicando boas práticas e padrões de arquitetura conhecidos no mercado, esse curso é para você!
- Criar uma arquitetura limpa;
- Desacoplar a biblioteca de terceiros utilizando o padrão adapter;
- Implementar uma instância única de conexão com seu banco de dados através do padrão singleton;
- Adicionar novas funcionalidades sem alterar o que já funciona com o padrão decorator;
- Agrupar validações com o padrão composite.
Para isso, usaremos uma API para adicionar e remover tarefas na nossa To Do List. É algo similar a uma tarefa do Google, onde temos uma lista. Podemos adicionar uma tarefa e também excluí-la.
Prepararemos a API para respeitar essas duas funcionalidades de adicionar e remover.
Para você aproveitar melhor esse conteúdo, é importante que já tenha feito o curso de Solid com TypeScript.
Além de assistir os vídeos, te convidamos a aproveitar os outros recursos da plataforma como as atividades, comunidade do Discord para encontrar outras pessoas que estejam estudando o mesmo que você e o Fórum para tirar dúvidas.
Te esperamos no vídeo seguinte. Até lá!
Nessa etapa do projeto, queremos criar uma lista de tarefas.
Para isso, seguiremos uma estrutura semelhante ao diagrama abaixo.
Temos um controller, o AddTask
, e quando queremos lidar com as rotas desse controller, instalamos a biblioteca do Express e criamos uma dependência desse controller com o Express.
Geralmente, fazemos o mesmo com o nosso banco de dados. Se queremos fazer manipulações, adicionamos o cliente MongoDB, por exemplo, e para validar nossos dados, instalamos uma biblioteca, como o validator, e tudo isso fica ligado ao nosso controller.
Mas qual é o problema disso? Queremos continuar seguindo as boas práticas, assim como do Solid para codificação. Porém, agora aprenderemos boas práticas relacionadas à arquitetura.
A questão é que o que temos agora não pode ser chamada de arquitetura. Temos um componente que está fazendo muitas coisas, com várias responsabilidades.
Descobriremos a solução para separar as responsabilidades e as camadas que pertencem ao nosso domínio, como, por exemplo, o AddTaskController
, e o que é uma biblioteca externa, como o Validator, MngoDB e Express.
O resultado dessa arquitetura será um diagrama composto por três blocos. Porém, inicialmente focaremos no bloco do adapter, onde fazemos a conexão com o controller.
Temos novamente o AddTaskController
e o Express, porém, na ligação entre eles foram criados mais dois itens. Uma interface Controller
, indicada em pontilhado, e um componente que servirá para adaptar o que o Express precisa para as necessidades do controller. Descobriremos na prática como fazer essa separação.
No VS Code e abrimos o projeto. Acessamos src > adapters > interfaces > controller.ts
. Esse é um dos arquivos que já deixamos prontos. Nesse caso o controller
é quem implementa o método handle()
, que recebe um HTTPRequest
, do tipo HTTPRequest
, que já deixamos implementado, e retornaremos uma promise a HTTPResponse
.
controller.ts
import { HttpRequest, HttpResponse } from "./http";
export interface Controller {
handle(httpRequest: HttpRequest): Promise<HttpResponse>;
}
Copiamos a linha de código referente ao handle()
e fechamos o arquivo. Em sequência, acessamos adapters > controllers > task > addTask.ts
.
addTask.ts
Nesse arquivo temos a classe AddTaskController
que não está implementando o contrato do controller
. Então, próximo à linha 11, após a classe, passamos implements Controller
.
A ferramenta indica um erro, pois espera o formato da assinatura. Então, próximo à linha 13, substituímos o trecho de código após async
pelo que copiamos anteriormente.
//Código omitido
export class AddTaskController implements Controller{
async handle(httpRequest: HttpRequest): Promise<HttpResponse> {
const requiredFields = ["title", "description", "date"];
}
//Código omitido
Feito isso, precisamos retornar uma Promise<HttpResponse>
. Analisaremos o que precisamos alterar no código. O cont requiredFields
, está definindo quais são os campos obrigatórios para criar uma tarefa, que é o title
, description
e date
.
Após, fazemos uma verificação caso algum desses campos seja enviado, caso não retornamos um badRequest()
para a pessoa usuária. Caso todos os campos tenham sido recebidos, o selecionamos, fazemos uma validação para ver se a data é válida com a nossa biblioteca validator.
Se for uma data inválida, retornamos novamente badRequest()
. Caso contrário, mandamos um JSON falando que aquele objeto foi criado. O created()
também é um helper que foi criado para facilitar nossa resposta.
O que precisamos mudar agora é que não temos mais req
e sim httpRequest
. Então, pressionamos "Ctrl + D" para selecionar onde temos o req
e substituímos por httpRequest
.
//Código omitido
for (const field of requiredFields) { I
if (!httpRequest body [field]) {
return badRequest(new MissingParamError(field));
}
//Código omitido
Agora precisamos retornar o objeto que seja do tipo httpResponse
. Como mencionamos, o created()
que criamos como um helper é do tipo httpResponse
. Então, apagamos o res.json
e passamos return created(task)
.
//Código omitido
const task = { title, description, date }
return created(task);
//Código omitido
Salvamos e no terminal, rodamos o projeto passando o comando npm start
.
npm start
Feito isso, recebemos um erro que está acontecendo em taskRoutes.ts
. Isso aconteceu, pois estamos usando o Express para lidar com as rotas. Porém, agora o nosso método handle()
do controller, espera um httpRequest
, ou seja, respeita o que foi definido internamente.
Então, precisamos criar uma nova camada que receberá os dados que precisamos internamente e retornar algo que o Express consegue entender.
Faremos isso na sequência. Até lá!
Estamos tentando desacoplar a biblioteca Express com o controller
, que adicionará uma tarefa.
Quando tentamos fazer isso, tivemos um problema no arquivo taskRoutes.ts
, pois ele espera um argumento, mas está recebendo dois. Isso acontece, pois o método handle()
espera um httpRequest
, como definimos internamente. Porém, estamos usando nas nossas rotas o Express, que espera essa nomenclatura com o rep
e res
.
É como se tivéssemos um notebook novo e tentássemos conectar em um monitor antigo. Geralmente monitores antigos possuem a saída VGA, enquanto o novo apenas HDMI. Dessa forma, não conseguimos fazer o encaixe, exceto se tivermos um adaptador do tipo VGA para HDMI, para que esses dois módulos consigam se comunicar.
O mesmo acontece nesse caso. Para o controller
conseguir se comunicar com o Express, precisaremos criar um adaptador de rotas no Express.
Para isso, no VS Code, acessamos o Explorador na lateral esquerda da tela. Acessamos "src > adapters", na raiz dessa pasta, criaremos um arquivo. Então, acima, clicamos em "Novo arquivo", indicado pelo ícone de uma folha de papel dobrada na ponta. Nomeamos de expressRouteAdapter.ts
.
expressRouteAdapter.ts
Começamos passando o export const expressRouteAdapter
que receberá, entre parênteses, controller: Controller
. Abrimos função e dentro retornaremos algo que o Express entende, nesse caso a função return async(req:Request, res:Response)=>{}
.
import { Request, Response } from "express";
import { Controller } from "./interfaces/controller";
export const expressRouteAdapter = (controller: Controller) => {
return async (req: Request, res: Response) => {
}
Nas chaves, precisamos passar para o controller
um objeto do tipo httpRequest
e capturar o retorno, que será um httpResponse
. Então, passamos controller.handle(httpRequest)
.
Mas quem que é esse httpRequest
? Se no explorador acessarmos a pasta "interfaces" e abrirmos o arquivo http.ts
, notamos que o httpRequest
é alguém que possui um objeto do tipo body
. Então, fechamos o arquivo e voltamos para o expressRouteAdapter.ts
.
Sabendo disso, na linha acima de handle()
, criamos o const httpRequest={}
, nas chaves passamos body:req.body
. Depois, na linha abaixo, antes de controller.handle()
, passamos const httpResponse = await
.
//Código omitido
export const expressRouteAdapter = (controller: Controller) => {
return async (req: Request, res: Response) => {
const httpRequest = {
body: req.body,
};
const httpResponse = await controller.handle(httpRequest);
Agora, precisamos fazer uma verificação. Se o status de tarefa foi criada, ou seja, o 201, retornaremos um status e o body
. Caso contrário, teremos que modificar o body
para ser um erro.
Na linha abaixo, passamos if(httpResponse.statusCode=201){}
. Nas chaves, passamos res.status(httpResponse.statusCode).json()
. A mensagem JSON que retornaremos é o body
, então, nos parênteses passamos httpResponse.body
.
Caso isso não seja verdade, então else{}
retornaremos um res.status(httpResponde.statusCode).json()
passando no JSON
o {error:httpResponse.body.message}
, entre chaves, pois estamos retornando um objeto de erro.
import { Request, Response } from "express";
import { Controller } from "./interfaces/controller";
export const expressRouteAdapter = (controller: Controller) => {
return async (req: Request, res: Response) => {
const httpRequest = {
body: req.body,
};
const httpResponse = await controller.handle(httpRequest);
if (httpResponse.statusCode = 201) {
res.status(httpResponse.statusCode).json(httpResponse.body);
} else {
res
.status(httpResponse.statusCode)
.json({ error: httpResponse.body.message });
}
O adaptador está pronto. Agora, precisamos chamá-lo no arquivo taskRoutes.ts
. Ao acessá-lo, podemos apagar todo o trecho de código após "/tasks"
. No lugar, adicionamos vírgula e passamos o adaptador expressRouteAdapter(addTaskController)
e salvamos o arquivo.
taskRoutes.ts
import { Request, Response, Router } from "express";
import { AddTaskController } from "../../../controllers/task/addTask";
import { express RouteAdapter } from "../../../express RouteAdapter";
export default (router: Router): void => {
const addTaskController = new AddTaskController();
router.post("/tasks", expressRouteAdapter(addTaskController));
};
Feito isso, nosso servidor já está executando, caso o seu não esteja basta passar o comando npm start
no terminal. Agora, podemos testar.
Com o Insomnia aberto, faremos nossas requisições. Temos uma requisição do tipo POST
, apontando para http://localhost:3000/api/tasks
que é onde criaremos nossas rotas. Não se preocupe, pois o projeto já está configurado para apontar para essa rota.
Basta passarmos um title
, description
e um date
e enviar.
{
"title":"Title example",
"description": "one single description here!",
"date": "29/02/2024"
}
Depois, na lateral superior direita, clicamos no botão "Send" e temos o objeto sendo retornado logo abaixo e o status 201
criado.
Se analisarmos novamente o primeiro bloco do diagrama, percebemos que concluímos a parte da direita do controller
. Separamos o que antes o controller
estava ligado ao Express
e acoplamos a biblioteca a um adaptador para podermos desacoplar o controller
. Agora o controller
depende de uma interface que o implemente, não dependemos mais de uma biblioteca externa.
Se futuramente precisarmos trocar de biblioteca, como trocar o Express pelo Fastify, basta alterarmos apenas o adaptador.
Da mesma forma que conseguimos fazer com o Express, precisaremos fazer com a nossa biblioteca de validação. É isso que faremos na sequência.
Te esperamos lá!
O curso Padrões de projeto com TypeScript: aprimorando uma API com arquitetura limpa possui 114 minutos de vídeos, em um total de 49 atividades. Gostou? Conheça nossos outros cursos de Node.JS em Programação, ou leia nossos artigos de Programação.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.