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:
Mais de 1500 cursos completamente atualizados, com novos lançamentos todas as semanas, emProgramação, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
Desafios temáticos para você turbinar seu portfólio. Você aprende na prática, com exercícios e projetos que simulam o dia a dia profissional.
Webséries exclusivas com discussões avançadas sobre arquitetura de sistemas com profissionais de grandes corporações e startups.
Emitimos certificados para atestar que você finalizou nossos cursos e formações.
Mais de 1500 cursos completamente atualizados, com novos lançamentos todas as semanas, emProgramação, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
Desafios temáticos para você turbinar seu portfólio. Você aprende na prática, com exercícios e projetos que simulam o dia a dia profissional.
Webséries exclusivas com discussões avançadas sobre arquitetura de sistemas com profissionais de grandes corporações e startups.
Emitimos certificados para atestar que você finalizou nossos cursos e formações.
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com Luri até 100 mensagens por semana.
Estude a língua inglesa com um curso 100% focado em tecnologia e expanda seus horizontes profissionais.
Mais de 1500 cursos completamente atualizados, com novos lançamentos todas as semanas, emProgramação, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
Desafios temáticos para você turbinar seu portfólio. Você aprende na prática, com exercícios e projetos que simulam o dia a dia profissional.
Webséries exclusivas com discussões avançadas sobre arquitetura de sistemas com profissionais de grandes corporações e startups.
Emitimos certificados para atestar que você finalizou nossos cursos e formações.
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com Luri até 100 mensagens por semana.
Estude a língua inglesa com um curso 100% focado em tecnologia e expanda seus horizontes profissionais.
Acesso completo
durante 1 ano
Estude 24h/dia
onde e quando quiser
Novos cursos
todas as semanas