Resolvendo o Desafio do PicPay de backend
PicPay Challenge: Explorando a Solução de um Desenvolvedor Backend
Este post quero mostrar como eu resolvi o desafio de desenvolvedor backend da PicPay. Seja você um profissional experiente ou esteja iniciando sua jornada, este projeto oferece insights valiosos sobre a construção de aplicações robustas e escaláveis.
O link do repositório está logo abaixo caso queira da uma olhada mais profunda no código
👉🏼 Repositório
Desvendando a Stack Tecnológica
O projeto utiliza uma combinação poderosa de tecnologias:
- JavaScript/TypeScript: A linguagem principal para construir a lógica da aplicação.
- Node.js: O ambiente de tempo de execução que executa código JavaScript fora de um navegador web.
- PostgreSQL: Um banco de dados relacional robusto para armazenar dados persistentes da aplicação.
- NestJS: Um framework simplificado para criar APIs em Node.js.
- Prisma ORM: Um Object-Relational Mapper que simplifica a comunicação entre a aplicação e o banco de dados.
- Docker: Uma plataforma de conteinerização para gerenciar o ambiente de desenvolvimento para PostgreSQL e potencialmente outros serviços.
- RabbitMQ: Um broker de mensagens que permite comunicação assíncrona entre diferentes partes da aplicação usando filas.
Padrões de Design para uma Arquitetura Bem Estruturada
A solução que eu busquei implementar foca principalmente no design e na arquiterura do projeto. O foco primeiro na arquiterua e no design vai trazer a longo prazo uma saúde melhor para o projeto visando a manutenção, escalabilidade e legibilidade.
- Clean Architecture O projeto mantém uma separação clara de responsabilidades organizando a base de código em camadas distintas (entidades, casos de uso, interfaces). Isso promove manutenabilidade e testabilidade.
- Domain Driven Design (DDD): O design prioriza o domínio, focando em regras de negócio e entidades centrais, garantindo que o software se alinhe com casos de uso do mundo real.
- Injeção de Dependência: Essa técnica promove acoplamento frouxo e testabilidade gerenciando dependências entre diferentes partes do código.
- Testes Automatizados: Testes unitários, de integração e de ponta a ponta garantem a qualidade do código e detectam problemas potenciais no início do processo de desenvolvimento.
- Comunicação Assíncrona: Ao implementar comunicação assíncrona, a aplicação permite que os componentes interajam por meio de mensagens sem a necessidade de respostas imediatas, melhorando a escalabilidade do sistema.
É claro que para um projeto desse porte não seria necessário implementar uma solução complexa dessa forma, porém ai é que está o desafio. É necessário entender os requisitos do projeto e o futuro dele, além disso como uma forma de estudo e para aplicar conceitos isso também é muito válido.
Eventos de Domínio: Um Diferencial
Um dos destaques desta solução é a utilização de Eventos de Domínio. Esse conceito garante a consistência dos dados quando um usuário realiza a transfêrencia e a uma notifação deve ser enviada para o usuário final. Ao aproveitar os eventos de domínio, a lógica de negócio relacionada à ao envio de noficação fica desacoplada do core da aplicação, que é a realização de transferências de dinheiro. No futuro é possível até mesmo abstrair essa parte da aplicação para outro serviço rodando em qualquer outra tecnologia.
Funcionalidades Essenciais
Atualmente, o projeto oferece a seguinte funcionalidade:
- Operações de Transferência de Dinheiro: Os usuários podem transferir fundos entre suas contas.
Começando com o Projeto
Para configurar e executar a aplicação, certifique-se de ter Node.js e Docker instalados em sua máquina. As instruções fornecidas detalham as etapas para criar variáveis de ambiente, iniciar serviços docker, instalar dependências, aplicar migrações de banco de dados, popular o banco de dados com dados iniciais e executar a aplicação em si. Além disso, deixo aqui a recomendação para testar usando a extensão REST Client para Visual Studio Code para interagir com as rotas HTTP da aplicação.
Verificando Funcionalidade através de Testes
O projeto incorpora testes unitários e de ponta a ponta que podem ser executados usando os comandos pnpm test
e pnpm test:e2e
, respectivamente. Esses testes desempenham um papel crucial na salvaguarda da qualidade do código e na prevenção de regressões.
Aprendizados Valiosos e Aprimoramentos Potenciais
Implementando essa solução pude tirar alguns aprendizados interessantes ao longo do processo de desenvolvimento, incluindo a importância de:
- Comunicação com Serviços Externos: Compreender como os serviços externos interagem e construir resiliência na aplicação para lidar com instabilidades potenciais.
É claro que esse projeto é apenas um case de estudos e podem haver várias melhorias potenciais:
- Serviços de Autenticação: Implementar um sistema de autenticação robusto para controle de acesso do usuário.
- Contextos Delimitados: Separar o domínio da aplicação em contextos menores e focados para aprimorar a manutenabilidade, escalabilidade e legibilidade do código.
- Utilização de Value Objects: Os values objects oferecem uma forma mais interessante de tipar os dados visto os comportamentos específico que cada dado pode ter, como por exemplo um Email, CPF ou CNPJ que possuem formatos de dados específicos e validações próprias.
- Recursos de Observabilidade: Integrar recursos para coletar métricas e logs para obter insights mais profundos sobre o comportamento do sistema.
- Histórico de Notificações: Armazenar mensagens de notificação para referência histórica.
- Abstração Melhorada de Mensagens: Aprimorar potencialmente a camada de abstração para enviar SMS ou notificações de e-mail para acomodar a flexibilidade futura.