Escolha de uma Stack para Back-End
Objetivo
Este estudo foi utilizado para auxiliar a equipe de tecnologia do Grupo Sozo a decidir uma stack de desenvolvimento back-end web. A área de back-end estava pouco estruturada e com múltiplas stacks diferentes para várias aplicações, portanto era necessário padronizar uma ferramenta e migrar assim que possível, visando minimizar a curva de aprendizado de novos membros e a agilidade no desenvolvimento.
Definição de Requisitos e Métricas
Para o momento da empresa o foco era entregar APIs de forma rápida, porém com bastante poder de customização. Além disso é necessário que o framework responda rápido ao cliente, que seja possível trabalhar com as tecnologias mais modernas e que seja fácil de dar manutenção. Com isso em mente foram levantados os seguintes requisitos e métricas.
Requisitos
- Requisitos de Desenvolvimento
- Criar APIs rapidamente (obrigatório)
- define a estrutura do dado e a API é criada de forma automática
- Customizabilidade de APIs (obrigatório)
- você pode alterar o comportamento de uma API ou criar uma específica
- ORM (obrigatório)
- para acessar o banco de dados e manipular os dados com uma interface consistente e simplificada
- Algum padrão claro na ferramenta
- todos que desenvolverem nesse framework irão desenvolver de forma igual por conta de padrões impostos pelos criadores
- facilita pesquisa de bugs, revisão de código, onboarding e manutenção do código
- Geração automática de documentação dos endpoints
- gerar ou integrar com uma ferramenta que documente automaticamente a forma de uso do endpoint.
- exemplo: Swagger
- Ser clean/fácil para codificar
- Usar linguagem de programação conhecida pela equipe de desenvolvimento
- Python e Javascript são as linguagens de maior proficiência da equipe
- Ser fácil de configurar
- Ser fácil de contratar mão de obra
- Criar APIs rapidamente (obrigatório)
- Requisitos de Tecnologia
- Entregar opcionalmente dados dos relacionamentos das tabelas nos endpoints
(obrigatório)
- evita múltiplos acessos à API e evita cruzar dados no front-end
- Filtros para API restful (obrigatório)
- permitir filtrar a coleção pelos campos
- permitir filtros diversos, como por exemplo: like, or, and, ...
- Permitir limitar quais campos serão retornados
- Nem sempre queremos todas as colunas da tabela
- Suporte a websockets (obrigatório)
- conseguir abrir websockets para manter o front-end altamente responsivo a notificações
- Ser capaz de suportar diferentes modelos de serialização
- Autenticação fácil (obrigatório)
- suporte a JWT, SSO e OAuth de forma fácil
- Versionamento de banco de dados com migrations (obrigatório)
- suporte a mexer no banco de dados de forma incremental, permitindo reverter mudanças antigas
- Se conectar com o banco PostgreSQL (obrigatório)
- Possuir formas de conexão agnóstica a base de dados
- Poder integrar com mais de um banco (obrigatório)
- exemplo: acessar duas bases de dados e fazer algum cruzamento, acessar o pipedrive e fazer algum cruzamento
- Possuir fácil configuração para ambientes de homologação e teste
- Entregar opcionalmente dados dos relacionamentos das tabelas nos endpoints
(obrigatório)
- Requisitos de Performance
- Cache, mesmo com a camada de autenticação (obrigatório)
- aumenta a eficiência do back-end reduzindo custo de processamento e acelerando a resposta
- exemplo: express inserindo o plugin do redis
- Ter baixo tempo de resposta (obrigatório)
- Cache, mesmo com a camada de autenticação (obrigatório)
Métricas
- Comunidade (atividade de comunidade)
- Bibliotecas/plugins que integram
- Frequência de atualizações
- Velocidade de resposta de issues (atividade de comunidade)
- Issues abertas (atividade de comunidade)
- Popularidade
- Número/qualidade das empresas que utilizam
Candidatos
Para escolher os candidatos optamos por ferramentas populares para back-end que nossa equipe conhecia.
Lista de frameworks candidatos:
- Django Rest API
- Django
- Express JS
- FastAPI
- koa
- NestJS
- Next.js
- adonisjs
- Nuxt.js
- Phoenix Framework
- Postgrest
- Ruby on Rails
- Sails.js
- Strapi
- Svelte Kit
- starlette
Bibliotecas que podem ser utilizadas com esses frameworks:
- ORM:
- API:
- graphql
- Descartamos o uso dele, pois é muito pesado e verboso, podendo trazer problemas de performance.
Análise
Primeiramente foi criada uma matriz de decisão avaliando se atende ou não os requisitos listados acima e também atribuindo uma pontuação para cada métrica listada acima. Após atribuir uma pontuação para cada um deles, os 3 melhores foram analisados profundamente (tratado mais abaixo).
Matriz de decisão para os requisitos.
Matriz de decisão para as métricas.
Anotações de pesquisa de cada stack
Após estudo de alguns framework, foi dado peso para cada tópico estudo e o resultado foi o seguinte:
Onde a coluna "Avaliação" foi dada por meio das métricas, dando pesos diferentes para cada métrica.
O Ruby on Rails, apesar de ter sido o melhor classificado, foi descartado pelo fato de ser naturalmente pesado, o que torna-o complicado de ser utilizado em projetos de pequeno porte, e por ser uma linguagem que tem mercado menor que javascript. Desta forma, os melhores candidatos foram:
Obs.: O Strapi consta como 4º escolhido, porém não será feito estudo do mesmo. Este consta na tabela apenas por ser utilizado atualmente, e será avaliado pelo conhecimento já existente da equipe.
Estudo aprofundado
Para essa análise profunda, foi criada uma API para um chat em tempo real, contendo múltiplos canais, e upload de arquivo na mensagem. Essa criação será cronometrada. A aplicação irá focar em avaliar alguns requisitos que são difíceis de avaliar sem programar:
- Autenticação
- Websockets
- Endpoints com filtro
- Endpoints selecionando colunas
- Endpoints resolvendo relacionamentos
- Migrations
- Documentação
- Facilidade de encontrar coisas na comunidade
- Sensação ao codificar
O estudo foi executado por 3 pessoas atuando no projeto por 8 horas, e foi dividido em 6 etapas:
- Questionário:
- Nesta etapa colhemos como cada membro se sente confiante com relação ao seu próprio conhecimento na stack, com base em estudos anteriores/experiências anteriores.
- Planejamento:
- Foi definido como seria o CRUD, cada pedaço de feature, o MER e outros.
- Esse planejamento foi feito somente uma vez, e replicado para cada execução do projeto em cada stack.
- Separação:
- Dado o planejamento feito anteriormente, é feito a separação entre os membros de quem irá executar cada feature, podendo ser pessoas diferentes em cada stack.
- Inicialização do repositório:
- Ainda com o cronometro parado, é feito a criação do repositório, e adicionado as imagens que serão utilizadas em um arquivo docker compose.
- Execução:
- Aqui inicializamos o cronometro, e damos inicio a execução conforme planejado (2).
- Impressões finais:
- Afim de facilitar discussões futuras, todos anotam o que acharam da stack estudada (não será adicionado a este documento estas notas).
Após o planejamento executado, ficou a seguinte divisão:
Features:
- CRUD: users, channels, messages, favorites;
- sistema de Login (sem reset de senha);
- sistema de Media para upload de arquivos;
- mensagens multi canais com Websocket;
- acrescentar aos CRUDS, filtros, seleção de colunas, resolução de relacionamentos.
Como tivemos somente 1 dia para o projeto quebramos eles em pedaços para facilitar a marcação do tempo, e para facilitar a divisão de tarefas. Ficou da seguinte forma:
Cada bloco apresentado a cima é uma atividade que um membro executou. As etapas em coding pair foram criadas para que todos estivessem alinhados quanto aos padrões do projeto e como fazer o básico.
Resultado NestJS
- (1) Questionário:
- José - Estudei 10% da documentação logo antes, e tenho 0 de experiências antes do projeto.
- Josué - Estudei 30% da documentação previamente.
- Ericson - Estudei 10% da documentação previamente.
- (3) Separação:
- José: mensagens com websocket
- Josué: Sistema de login
- Ericson: CRUDs
- (5) Execução:
- Etapa 1: 1h32m
- Etapa 2: 55m
- Etapa 3: 6h17m (ficou incompleto):
- feito mensagens com websocket incluindo documentação
- parcialmente feito os CRUDs, faltando os filtros e seeds
- não feito o login
- Etapa 4: não iniciada
Resultado FastAPI
- (1) Questionário:
- José - Estudei 5% da documentação logo antes, e tenho 0 de experiências antes do projeto.
- Josué - Estudei 15% da documentação previamente.
- Ericson - Estudei 30% da documentação previamente.
- (3) Separação:
- José: mensagens com websocket
- Josué: CRUDs
- Ericson: Sistema de login
- (5) Execução:
- Etapa 1: 4h
- Etapa 2: 1h
- Etapa 3: 3h (ficou incompleto):
- feito parcialmente mensagens com websocket, faltando os eventos de cada canal (enviar, alguem entrou, ...) e documentação
- parcialmente feito os CRUDs, faltando algumas coleções, e seeds
- parcialmente feito o sistema de login
- Etapa 4: não iniciada
Resultado ExpressJS
- (1) Questionário:
- José - Estudei 0 da documentação logo antes, e tenho 30% de experiências antes do projeto.
- Josué - 40% de experiência antes do projeto.
- Ericson - 30% de experiência antes do projeto.
- Não pre estudamos a documentação do express para este projeto, devido a nossa experiência com ele anteriormente.
- (3) Separação:
- José: mensagens com websocket
- Josué: CRUDs
- Ericson: Sistema de login
- (5) Execução:
- Etapa 1: 6h40m
- Tivemos problemas com a estrutura que escolhemos e mudamos ela algumas vezes.
- Etapa 2: 1h (ficou incompleto)
- Faltou 1 endpoint e a documentação
- Etapa 3: não iniciada
- Etapa 4: não iniciada
- Etapa 1: 6h40m
Conclusão
Após uma reunião onde foi apresentado os pontos testados, e os resultados, concluímos que faltou testar a parte de login, uso de recursos da máquina e tempo de resposta das APIs testadas. Entretanto devido ao tempo, e a oportunidade/necessidade de refatorar uma API atual para garantir mais segurança, resolvemos testar o NestJs em um projeto real.
Sendo assim nossa stack escolhida, temporariamente, é o NestJS. Ele foi o mais bem votado durante a reunião, tendo um padrão e arquitetura mais bem definidos, tornando mais fácil o onboarding de novos membros, sem perder a flexibilidade que o expressjs entrega. Os pontos que foram negativados, foi a parte de autenticação que não foi devidamente testada, a falta de um gerador de CRUDs, e a falta de filtros e resolução de relacionamentos para o cliente direto na api.
Será necessário a criação dos itens que sentimos falta, e isso pode quebrar o padrão do nestJs, e torná-lo menos legível, uma vez que teremos de fazer nossas escolhas arquiteturais para ele, o que é um ótimo jeito de testar a stack escolhida, quanto aos padrões, facilidade de uso e flexibilidade.
TL;DR
A equipe de tecnologia do Grupo Sozo precisa melhorar o back-end e escolheu NestJS, porque ele tem um padrão mais bem definido, e uma boa documentação, além de ter toda a flexibilidade que o expressJs dá, sendo feito em cima do expressJs.