Segurança com clean arquitecture usando o nodejs e typescript

Está gostando? Compartilhe

A arquitetura limpa (clean architecture) é um padrão de arquitetura de software que promove a separação de preocupações (SoC), o isolamento de tecnologias e a independência da camada de infraestrutura. Essa abordagem é comumente usada em aplicativos empresariais complexos, pois ajuda a garantir a escalabilidade, a manutenibilidade e a testabilidade do código.

Neste artigo, exploraremos como aplicar a arquitetura limpa ao desenvolvimento de aplicativos Node.js usando TypeScript, com ênfase na segurança. Vamos começar definindo a arquitetura limpa e, em seguida, discutiremos como aplicá-la à nossa pilha tecnológica.

Clean Architecture

A arquitetura limpa é baseada no princípio da inversão de dependência (IoC). Em vez de permitir que as camadas de alto nível dependam das camadas de baixo nível, a arquitetura limpa coloca a lógica de negócios no centro do design do software, criando um anel externo de interfaces de entrada e um anel interno de interfaces de saída.

A seguir, vamos definir as camadas da arquitetura limpa:

  1. Entidades: As entidades são objetos que representam conceitos de negócios em um domínio específico. Elas encapsulam as regras de negócio e são independentes da tecnologia.

  2. Casos de uso: Os casos de uso são a lógica de negócio da aplicação e dependem das entidades. Eles são responsáveis por orquestrar as operações necessárias para atingir os objetivos do usuário.

  3. Adaptadores: Os adaptadores são responsáveis por converter os dados em formato que possa ser usado pela camada de negócios. Eles incluem interfaces de entrada (por exemplo, API REST) e interfaces de saída (por exemplo, acesso a banco de dados).

  4. Infraestrutura: A camada de infraestrutura é responsável por lidar com as tecnologias específicas, como banco de dados, rede e sistema de arquivos.

A seguir, vamos definir algumas diretrizes para aplicar a arquitetura limpa no desenvolvimento de aplicativos Node.js usando TypeScript.

Diretrizes

  1. Separar as camadas: Como mencionado anteriormente, a arquitetura limpa enfatiza a separação de preocupações. É importante separar as camadas de negócio, adaptadores e infraestrutura em módulos distintos e independentes.

  2. Usar interfaces: A arquitetura limpa enfatiza o uso de interfaces para definir as dependências entre as camadas. Isso torna mais fácil substituir as implementações de uma camada por outra.

  3. Isolar a lógica de negócio: A camada de negócio deve estar completamente isolada da tecnologia usada na camada de infraestrutura. Isso torna mais fácil testar e manter o código.

  4. Validar entradas e saídas: Como a segurança é uma preocupação crítica em muitas aplicações, é importante validar todas as entradas e saídas do aplicativo para garantir que elas estejam de acordo com as expectativas.

  5. Tratar erros corretamente: O tratamento de erros é uma parte crucial da segurança em qualquer aplicativo. A arquitetura limpa promove a separação do tratamento de erros da lógica de negócios. Isso torna mais fácil lidar com exceções e evitar vazamentos de informações sensíveis.

  6. Usar criptografia: A arquitetura limpa não fornece uma solução específica para a criptografia de dados, mas recomenda o uso de bibliotecas criptográficas confiáveis e de padrões de segurança reconhecidos.

  7. Limitar o acesso aos recursos: A camada de infraestrutura deve ser configurada de forma a limitar o acesso aos recursos, como bancos de dados e arquivos. Isso inclui a aplicação de autenticação e autorização adequadas.

  8. Testar com cobertura completa: A arquitetura limpa promove a testabilidade do código e recomenda o uso de testes automatizados. É importante ter uma cobertura de teste completa para garantir que todos os cenários possíveis tenham sido testados.

  9. Aplicando a arquitetura limpa com Node.js e TypeScript

    A seguir, vamos mostrar um exemplo de como aplicar a arquitetura limpa ao desenvolvimento de um aplicativo Node.js usando TypeScript.

    Suponha que estamos desenvolvendo um sistema de autenticação que permita que os usuários se registrem e façam login. Vamos começar definindo as entidades do nosso domínio:

				
					// user.entity.ts

export interface User {
  id: string;
  email: string;
  password: string;
}

				
			

Em seguida, vamos definir um caso de uso que permita que os usuários se registrem:

				
					// register.usecase.ts

import { User } from './user.entity';

export interface RegisterUserRequest {
  email: string;
  password: string;
}

export interface RegisterUserResponse {
  user: User;
}

export interface RegisterUserUseCase {
  execute(request: RegisterUserRequest): Promise<RegisterUserResponse>;
}

				
			

A seguir, vamos definir um adaptador de interface de entrada que permite que os usuários se registrem por meio de uma API REST:

				
					// register.controller.ts

import { Request, Response } from 'express';
import { RegisterUserUseCase, RegisterUserRequest } from '../usecases/register.usecase';

export class RegisterUserController {
  constructor(private readonly registerUserUseCase: RegisterUserUseCase) {}

  async handle(request: Request, response: Response) {
    const registerUserRequest: RegisterUserRequest = {
      email: request.body.email,
      password: request.body.password,
    };

    const registerUserResponse = await this.registerUserUseCase.execute(registerUserRequest);

    response.json(registerUserResponse);
  }
}

				
			

Em seguida, vamos definir um adaptador de interface de saída que permite que os usuários sejam armazenados em um banco de dados MongoDB:

				
					// user.repository.ts

import { User } from './user.entity';

export interface UserRepository {
  save(user: User): Promise<void>;
  findByEmail(email: string): Promise<User | null>;
}

				
			

A seguir, vamos implementar o caso de uso e os adaptadores:

				
					// register.usecase.impl.ts

import { User } from '../entities/user.entity';
import { RegisterUserRequest, RegisterUserResponse, RegisterUserUseCase } from './register.usecase';
import { UserRepository } from '../entities/user.repository';

export class RegisterUserUseCaseImpl implements RegisterUser  {
constructor(private readonly userRepository: UserRepository) {}

async execute(request: RegisterUserRequest): Promise<RegisterUserResponse> {
const existingUser = await this.userRepository.findByEmail(request.email);
}
				
			

Conclusão

aqui está uma versão mais clara e simplificada do caso de uso usando Clean Architecture, Node.js e TypeScript:

				
					// register.usecase.ts

import { v4 as uuidv4 } from 'uuid';
import { User } from '../entities/user.entity';
import { UserRepository } from '../repositories/user.repository';

export interface RegisterUserRequest {
  email: string;
  password: string;
}

export interface RegisterUserResponse {
  user: User;
}

export class RegisterUserUseCase {
  constructor(private readonly userRepository: UserRepository) {}

  async execute(request: RegisterUserRequest): Promise<RegisterUserResponse> {
    const existingUser = await this.userRepository.findByEmail(request.email);

    if (existingUser) {
      throw new Error('User already exists');
    }

    const user: User = {
      id: uuidv4(),
      email: request.email,
      password: request.password,
    };

    await this.userRepository.save(user);

    return { user };
  }
}

				
			

Neste caso de uso, temos uma classe RegisterUserUseCase que recebe um objeto UserRepository em seu construtor. A classe RegisterUserUseCase possui um método execute que recebe um objeto RegisterUserRequest contendo o email e a senha do usuário a ser registrado.

Dentro do método execute, a primeira coisa que fazemos é verificar se o usuário já existe no repositório, usando o método findByEmail do UserRepository. Se o usuário já existir, lançamos um erro.

Caso contrário, criamos um novo objeto User, gerando um novo ID usando a biblioteca uuid, e salvamos o novo usuário no repositório, usando o método save do UserRepository.

Por fim, retornamos um objeto RegisterUserResponse contendo o usuário recém-criado.

Essa implementação segue os princípios do Clean Architecture, separando as camadas da aplicação e definindo interfaces claras para as dependências externas, permitindo uma maior flexibilidade e manutenibilidade do código.

Quer ficar atualizado sobre marketing ?

Assine a nossa newsletter.

Aproveite e Veja Também

DESCUBRA NESSE E-BOOK 5 ESTRATÉGIAS PARA VENDER MUITO NA SUA LOJA DROPSHIPPING.

Você Quer Impulsionar Seu Negócio?

Mande-nos uma mensagem que entramos em contato.

Desenvolvimento de Backend com Node.js, TypeScript, MongoDB e Docker: Práticas Avançadas com TDD, DDD, Clean Architecture e SOLID

DESCUBRA como desenvolver um Backend com Node.js, TypeScript, MongoDB e Docker utilizando práticas avançadas com TDD, DDD, Clean Architecture e SOLID.