/desafio-liven

Desafio do processo seletivo da Liven.

Primary LanguageTypeScript

Treinamento Liven

Enunciado

O sistema lida com duas entidades: usuários e endereços, que mantém um relacionamento do tipo 1:N, ou seja, um usuário pode ter vários endereços, mas os endereços estão relacionados à apenas um usuário.

As operações requeridas são CRUDs para ambas as entidades:

  • Criação de usuários
  • Listagem de usuários e seus endereços
  • Alteração de usuários
  • Remoção de usuários
  • Criação de endereços
  • Listagem endereços do usuário
  • Alteração de endereços
  • Remoção de endereços

Como especificado, as operações devem ser fornecidas através de um servidor HTTP, portanto optou-se por expor endpoints seguindo os princípios REST (embora a API não seja Restful!). Isso envolve o uso semântico dos verbos HTTP e a definição de endpoints orientados a recursos.

Além disso, as rotas de listagem devem permitir a especificação de parâmetros de filtragem através de querystrings nas rotas.

Processo de desenvolvimento

Ambiente

Foram utilizadas as ferramentas descritas no enunciado, com destaque para as seguintes:

  • Typescript
  • Node.js
  • Express
  • Mysql
  • Knex.js ♥️ Objection.js

Além dessas, foram utilizadas algumas outras ferramentas para auxiliar o desenvolvimento ou adicionar funcionalidades extras do sistema.

Arquitetura

Buscou-se por implementar o sistema seguindo o padrão da clean architecture (porém não aderindo 100% a ele). Isso tem algumas implicações:

  • Definição de camadas do sistema e suas responsabilidades.
  • Desacoplamento entre as camadas.
  • Desacoplamento entre regras de negócio e implementações externas.

presentation/clean.png

Testes

Ao longo do desenvolvimento foram realizados testes unitários usando a biblioteca Jest. Os testes foram aplicados principalmente na camada de domínio e casos de uso, auxiliando tanto na especificação quanto na validação das funcionalidades mais importantes da aplicação.

Para cada entidade e caso de uso há ao menos um conjunto de testes que validam suas funcionalidades.

Extras

Validação de requisições

Para validar os dados enviados nas requisições à API, foram utilizadas duas bibliotecas:

  • JOI para a definição e validação de schemas.
  • Celebrate para a criação de middlewares de validação.

Error handling

O tratamento de erros é feito de forma centralizada na aplicação, fazendo uso da função next() do express em conjunto com um error handler customizado implementado como um middleware.

Definição de erros

Os erros são definidos na camada de domínio, portanto não contém nenhuma informação externa às regras de negócio da aplicação.

export abstract class BaseError extends Error {
  public readonly details?: string

  constructor(message: string, details?: string) {
    super(message)
    this.details = details
  }
}

Dessa classe base são derivadas as classes de erro específicas:

export class EntityNotFound extends BaseError {
  constructor(public readonly entity: string, public readonly details?: string) {
    super(`${entity} not found.`, details)
  }
}

Error handler

O papel do error handler é identificar os errors gerados em outras camadas da aplicação, atribuir a eles um status HTTP e então enviar uma resposta com o máximo de informação possível sobre o erro.

type StatusAssoc = {
  [key: string]: number
}

const httpStatus: StatusAssoc = {
  UserAlreadyExists: 400,
  EntityNotFound: 404,
  InvalidFieldError: 400,
}

export async function handler(error: BaseError, _req: Request, res: Response, _next: NextFunction): Promise<Response> {
  const { message, details } = error
  const name = error.constructor.name
  const status = httpStatus[name] || 500

  if (message) {
    return res.status(status).json({ error: message, details: details })
  }

  return res.status(status).json({ 'Internal error': error.message })
}

Envio de e-mails

Foi implementado também o envio de e-mails no momento do cadastro do usuário. A implementação atual faz uso da biblioteca nodemailer para o envio dos e-mails.

Documentação

A documentação da API foi feita seguindo os padrões da OpenAPI Specification, utilizando a ferramenta Swagger como interface. A documentação é extraída das rotas através de JSDocs pela biblioteca swagger-jsdoc e servida em um endpoint /docs através da biblioteca swagger-ui-express.

Fim

Muito obrigado!