A equipe de engenharia da RD Station tem alguns princípios nos quais baseamos nosso trabalho diário. Um deles é: projete seu código para ser mais fácil de entender, não mais fácil de escrever.
Portanto, para nós, é mais importante um código de fácil leitura do que um que utilize recursos complexos e/ou desnecessários.
O que gostaríamos de ver:
- O código deve ser fácil de ler. Clean Code pode te ajudar.
- Notas gerais e informações sobre a versão da linguagem e outras informações importantes para executar seu código.
- Código que se preocupa com a performance (complexidade de algoritmo).
- O seu código deve cobrir todos os casos de uso presentes no README, mesmo que não haja um teste implementado para tal.
- A adição de novos testes é sempre bem-vinda.
- Você deve enviar para nós o link do repositório público com a aplicação desenvolvida (GitHub, BitBucket, etc.).
O desafio consiste em uma API para gerenciamento de um carrinho de compras de e-commerce.
Você deve desenvolver utilizando a linguagem Ruby e framework Rails, uma API Rest que terá 3 endpoints que deverão implementar as seguintes funcionalidades:
Criar um endpoint para inserção de produtos no carrinho.
Se não existir um carrinho para a sessão, criar o carrinho e salvar o ID do carrinho na sessão.
Adicionar o produto no carrinho e devolver o payload com a lista de produtos do carrinho atual.
ROTA: /cart
Payload:
{
"product_id": 345, // id do produto sendo adicionado
"quantity": 2, // quantidade de produto a ser adicionado
}Response
{
"id": 789, // id do carrinho
"products": [
{
"id": 645,
"name": "Nome do produto",
"quantity": 2,
"unit_price": 1.99, // valor unitário do produto
"total_price": 3.98, // valor total do produto
},
{
"id": 646,
"name": "Nome do produto 2",
"quantity": 2,
"unit_price": 1.99,
"total_price": 3.98,
},
],
"total_price": 7.96 // valor total no carrinho
}Criar um endpoint para listar os produtos no carrinho atual.
ROTA: /cart
Response:
{
"id": 789, // id do carrinho
"products": [
{
"id": 645,
"name": "Nome do produto",
"quantity": 2,
"unit_price": 1.99, // valor unitário do produto
"total_price": 3.98, // valor total do produto
},
{
"id": 646,
"name": "Nome do produto 2",
"quantity": 2,
"unit_price": 1.99,
"total_price": 3.98,
},
],
"total_price": 7.96 // valor total no carrinho
}Um carrinho pode ter N produtos, se o produto já existir no carrinho, apenas a quantidade dele deve ser alterada
ROTA: /cart/add_item
Payload
{
"product_id": 1230,
"quantity": 1
}Response:
{
"id": 1,
"products": [
{
"id": 1230,
"name": "Nome do produto X",
"quantity": 2, // considerando que esse produto já estava no carrinho
"unit_price": 7.00,
"total_price": 14.00,
},
{
"id": 01020,
"name": "Nome do produto Y",
"quantity": 1,
"unit_price": 9.90,
"total_price": 9.90,
},
],
"total_price": 23.9
}Criar um endpoint para excluir um produto do carrinho.
ROTA: /cart/:product_id
- Verifique se o produto existe no carrinho antes de tentar removê-lo.
- Se o produto não estiver no carrinho, retorne uma mensagem de erro apropriada.
- Após remover o produto, retorne o payload com a lista atualizada de produtos no carrinho.
- Certifique-se de que o endpoint lida corretamente com casos em que o carrinho está vazio após a remoção do produto.
Um carrinho é considerado abandonado quando estiver sem interação (adição ou remoção de produtos) há mais de 3 horas.
- Quando este cenário ocorrer, o carrinho deve ser marcado como abandonado.
- Se o carrinho estiver abandonado há mais de 7 dias, remover o carrinho.
- Utilize um Job para gerenciar (marcar como abandonado e remover) carrinhos sem interação.
- Configure a aplicação para executar este Job nos períodos especificados acima.
- O Job deve ser executado regularmente para verificar e marcar carrinhos como abandonados após 3 horas de inatividade.
- O Job também deve verificar periodicamente e excluir carrinhos que foram marcados como abandonados por mais de 7 dias.
Você deve usar como base o código disponível nesse repositório e expandi-lo para que atenda as funcionalidade descritas acima.
Há trechos parcialmente implementados e também sugestões de locais para algumas das funcionalidades sinalizados com um # TODO. Você pode segui-los ou fazer da maneira que julgar ser a melhor a ser feita, desde que atenda os contratos de API e funcionalidades descritas.
Existem testes pendentes, eles estão marcados como Pending, e devem ser implementados para garantir a cobertura dos trechos de código implementados por você. Alguns testes já estão passando e outros estão com erro. Com a sua implementação os testes com erro devem passar a funcionar. A adição de novos testes é sempre bem-vinda, mas sem alterar os já implementados.
- Implementação dos testes faltantes e de novos testes para os métodos/serviços/entidades criados
- Construção das 4 rotas solicitadas
- Implementação de um job para controle dos carrinhos abandonados
- Utilização de factory na construção dos testes
- Desenvolvimento do docker compose / dockerização da app
A aplicação já possui um Dockerfile, que define como a aplicação deve ser configurada dentro de um contêiner Docker. No entanto, para completar a dockerização da aplicação, é necessário criar um arquivo docker-compose.yml. O arquivo irá definir como os vários serviços da aplicação (por exemplo, aplicação web, banco de dados, etc.) interagem e se comunicam.
-
Adicione tratamento de erros para situações excepcionais válidas, por exemplo: garantir que um produto não possa ter quantidade negativa.
-
Se desejar você pode adicionar a configuração faltante no arquivo
docker-compose.ymle garantir que a aplicação rode de forma correta utilizando Docker.
- ruby 3.3.1
- rails 7.1.3.2
- postgres 16
- redis 7.0.15
⚠️ Observação Essa forma de execução do projeto ainda irá funcionar mas está depreciada neste projeto, porém, como ela faz parte do que é mostrado no escopo do desafio a mantive aqui para não alterar o escopo. Concluindo, ela foi depreciada quando foi adicionada a execução por Docker, que é a forma oficial e que pode ser acessada por aqui.
Dado que todas as ferramentas estão instaladas e configuradas:
Instalar as dependências do:
bundle installExecutar o sidekiq:
bundle exec sidekiqExecutar projeto:
bundle exec rails serverExecutar os testes:
bundle exec rspecSalve seu código em um versionador de código (GitHub, GitLab, Bitbucket) e nos envie o link público. Se achar necessário, informe no README as instruções para execução ou qualquer outra informação relevante para correção/entendimento da sua solução.
O projeto pode ser executado utilizando Docker e Docker Compose, facilitando a configuração do ambiente e evitando dependências locais.
- Docker
- Docker Compose
docker compose build
Esse comando irá realizar o build da imagem da aplicação.
docker compose up
Esse comando irá subir os seguintes serviços:
- API Rails
- PostgreSQL
- Redis
- Sidekiq
Após a inicialização, a aplicação estará disponível em:
http://localhost:3000
docker compose exec web bin/rails db:create db:migrate
Esse comando irá criar e migrar o banco de dados em ambiente de desenvolvimento.
docker compose run --rm test bin/rails db:create db:migrate
Esse comando irá criar e migrar o banco de dados em ambiente de teste.
A aplicação conta com testes automatizados desenvolvidos com RSpec e podem ser executados das seguintes maneiras:
docker compose run --rm -e RAILS_ENV=test web bundle exec rspec
docker compose run --rm -e RAILS_ENV=test web bundle exec rspec PATH_DO_ARQUIVO_DE_TESTE
docker compose run --rm -e RAILS_ENV=test web bundle exec rspec PATH_DO_ARQUIVO_DE_TESTE:LINHA_INICIAL_DO_TESTE
Durante a implementação, priorizei legibilidade, simplicidade e separação de responsabilidades, conforme o escopo do desafio.
As principais decisões foram:
- Controllers enxutos, responsáveis apenas por orquestrar request/response
- Regras de negócio isoladas em Services, facilitando testes e manutenção
- Serializers responsáveis por montar o payload exatamente como definido no contrato da API
- Sessão do carrinho via cookie, permitindo que o carrinho persista entre requisições sem necessidade de autenticação
- Uso de transações para garantir consistência ao adicionar/remover itens do carrinho
- Evitei abstrações desnecessárias para manter o código simples e direto
O código foi organizado com foco em clareza e facilidade de leitura, para ser mais fácil de ler e entender do que de escrever:
- Controllers → Apenas fluxo de entrada e saída
- Services → Regras de negócio do carrinho (adição, remoção, atualização)
- Models → Regras de domínio e associações
- Jobs → Processamento assíncrono de carrinhos abandonados, de maneira clara e otimizada, sem varredura
- Serializers → Formatação de resposta da API
- Spec → Testes organizados por camadas (request, model, service, job)
Essa estrutura facilita a manutenção e permite que novas funcionalidades sejam adicionadas sem impacto excessivo no código existente, respeitando uma das principais premissas do desafio, que é para ser mais fácil de ler e entender do que de escrever.
Foram implementados os testes pendentes e adicionados novos testes para cobrir cenários importantes, como:
- Adição de produtos novos e existentes no carrinho
- Atualização de quantidade
- Remoção de produtos inexistentes
- Cálculo correto de valores totais
- Criação automática de carrinho via sessão
- Execução do job de carrinho abandonado
Utilizei factories para simplificar a criação de dados e manter os testes mais legíveis e objetivos. O foco foi garantir confiança no comportamento da API, não apenas cobertura. Teste existentes foram mantidos, adicionando apenas o controle de sessão com cookies que faz parte da regra que planejei e desenvolvi.
Foi implementado um job responsável por gerenciar carrinhos sem interação:
- Carrinhos sem atualização por mais de 3 horas são marcados como abandonados
- Carrinhos abandonados há mais de 7 dias são removidos do banco
- O job é executado periodicamente via Sidekiq
Essa abordagem evita o acúmulo de dados desnecessários e mantém a base limpa ao longo do tempo, sem impacto no fluxo principal da aplicação, seguindo o que foi pedido no escopo do desafio.
Caso houvesse mais tempo para evoluir a solução, alguns pontos que seriam priorizados:
- Padronização de erros e exceções, centralizando tratamento de erros da API para garantir respostas ainda mais consistentes
- Versionamento da API, introduzindo versionamento (
/v1) para permitir evolução sem quebra de contrato - Documentação da API, adicionando Swagger/OpenAPI para facilitar testes e integração com outros serviços
- Otimização de banco de dados, criando índices para
cart_id,product_ideupdated_ate melhorando ainda mais a performance em consultas frequentes - Padronização de código, adicionando Rubocop com regras customizadas para manter consistência e qualidade do código ao longo do tempo
- GitHub Actions, para garantir qualidade e facilidade na verificação de possíveis erros