Skip to content

E-commerce cart API challenge developed as part of the RD Station technical interview process. This project implements a RESTful API for shopping cart management, including product insertion, update, removal, session-based carts, background processing for abandoned carts, automated tests, and Dockerized execution.

Notifications You must be signed in to change notification settings

tzanarde/cart-api

Repository files navigation

Desafio técnico e-commerce

Índice


Escopo do Desafio

Nossas expectativas

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 - Carrinho de compras

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:

1. Registrar um produto no carrinho

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
}

2. Listar itens do carrinho atual

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
}

3. Alterar a quantidade de produtos 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
}

4. Remover um produto do carrinho

Criar um endpoint para excluir um produto do carrinho.

ROTA: /cart/:product_id

Detalhes adicionais:
  • 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.

5. Excluir carrinhos abandonados

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.

Detalhes adicionais:

  • 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.

Como resolver

Implementação

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.

Testes

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.

O que esperamos

  • 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

Itens adicionais / Legais de ter

  • 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.yml e garantir que a aplicação rode de forma correta utilizando Docker.

Informações técnicas

Dependências

  • ruby 3.3.1
  • rails 7.1.3.2
  • postgres 16
  • redis 7.0.15

Como executar o projeto

Executando a app sem o docker

⚠️ 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 install

Executar o sidekiq:

bundle exec sidekiq

Executar projeto:

bundle exec rails server

Executar os testes:

bundle exec rspec

Como enviar seu projeto

Salve 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.

Resolução do Desafio

Como Executar o Projeto com o Docker

O projeto pode ser executado utilizando Docker e Docker Compose, facilitando a configuração do ambiente e evitando dependências locais.

Pré-requisitos

  • Docker
  • Docker Compose

Subindo a Aplicação

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

Criando Banco de Dados

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.

Executando os Testes

A aplicação conta com testes automatizados desenvolvidos com RSpec e podem ser executados das seguintes maneiras:

Todos os testes
docker compose run --rm -e RAILS_ENV=test web bundle exec rspec
Um arquivo de teste específico
docker compose run --rm -e RAILS_ENV=test web bundle exec rspec PATH_DO_ARQUIVO_DE_TESTE
Um teste específico
docker compose run --rm -e RAILS_ENV=test web bundle exec rspec PATH_DO_ARQUIVO_DE_TESTE:LINHA_INICIAL_DO_TESTE

Decisões técnicas

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

Organização do código

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.

Desenvolvimento de Testes

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.

Job de carrinho abandonado

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.

Pontos de melhoria

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_id e updated_at e 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

About

E-commerce cart API challenge developed as part of the RD Station technical interview process. This project implements a RESTful API for shopping cart management, including product insertion, update, removal, session-based carts, background processing for abandoned carts, automated tests, and Dockerized execution.

Topics

Resources

Stars

Watchers

Forks

Languages