Este foi um teste realizado a partir de um processo seletivo. O teste consiste na construção de um pequeno backend (servindo como API) para cadastro de bolos, lista de interessados e disparo de emails em massa (através das queues).
- CRUD de rotas de API para o cadastro de bolos;
- Os bolos deverão ter nome, peso (em gramas), valor, quantidade disponivel e uma lista de espera de emails de interessados;
- Após o cadastro de emails, caso haja bolo disponível, o sistema deverá enviar um email para os interessados avisando
sobre a disponibilidade do mesmo.
- Casos a avaliar: Pode ocorrer de 50.000 clientes se cadastrarem e o processo de envio de emails não deve ser um impediditivo.
- Banco de Dados: livre escolha;
- Framework: Laravel;
- Queues e Resources serão um diferencial.
No banco de dados resolvi utilizar o MySQL e para o processamento das filas, Redis. Ainda a respeito das filas, como envolve disparo de emails, aproveitei do Mailhog já configurado no LaravelSail. Usei também o NGINX como servidor HTTP e utilizei o PHP em sua versão 8.1. Como esperado, usei o Docker a partir do LaravelSail, que já trás um ambiente robusto e completamente configurado. Rodei isso no WSL2 com Ubuntu e o docker instalado nele, nativamente.
Após clonar o projeto em seu aparelho, basta rodar o comando do composer para instalar as dependências:
composer install
OBS: caso não tenha o composer em sua máquina, recomendo utilizar seu container docker para instalar as dependências do projeto.
Em seguida, faça uma cópia do arquivo .env.example
para .env
e configure seu ambiente de desenvolvimento como
banco de dados e etc.
E lembre-se também de gerar uma chave para aplicação:
sail artisan key:generate
Finalmente, para rodar a aplicação:
sail up -d
No banco de dados, criei apenas 2 tabelas: cakes e waiting_lists. Ambas mantém gravado o que foi solicitado no
enunciado. Por precaução, criei um CRUD para a lista de interessados (tabela waiting_lists
) onde, obrigatoriamente,
deverá ter algum bolo
cadastrado para servir de base para essa tabela (nela contém uma chave estrangeira cake_id
).
Após criar sua base de dados local e configura-la em seu .env
, execute o comando:
sail artisan migrate --seed
No passo anterior, sugiro rodar os seeders por conta das Queues. Crio alguns bolos e vinculo em torno de 2.000 interessados, adicionando-os nas filas.
Logo em seguida, caso queira testar os disparos das queues:
- Accesse seu servidor de email;
- Caso não tenha nenhuma configuração disponível, recomendo o uso do
Mailhog
que já vem setado por padrão no LaravelSail. Basta subir os containers (como mencionado anteriormente) e acessar a urlhttp://localhost:8025
(ou mude a porta de acesso caso tenha outra configuração).
- Caso não tenha nenhuma configuração disponível, recomendo o uso do
- Com o terminal aberto e dentro do diretório do projeto, basta usar o comando:
sail artisan queue:work
Se o projeto estiver configurado corretamente e não tiver conflito com portas, a fila será executada.
Se quiser uma interface visual para acompanhar a execução das filas, poderá instalar o horizon. Na verdade ele já está instalado.
- Instale o Horizon:
sail artisan horizon:install
- Publique seus arquivos de configuração:
sail artisan horizon:publish
- Acesse a rota
/horizon
; - Execute as queues a partir do horizon:
sail artisan horizon
Verá na tela a execução das filas e um conjunto de relatórios e métricas a respeito das mesmas.
Apesar de ter implementado, não tenho experiência com elas. Um exemplo disso é que, para nao sobrecarregar a fila
CakeAvailableAllJob
, fiz uma tentativa de incluir disparos individuais de email, onde a fila que recebe o lote
(mencionada anteriormente) cria uma outra fila CakeAvailableClientJob
e, nessa última, a notificação
de email é disparada individualmente.
Infelizmente não consegui fazer funciona-la como queria, mas deixei o código de exemplo comentado lá. Então acabei sobrecarregando a fila principal realizando o disparo a partir de um loop.
Pela falta de experiência no trabalho com as filas, também não incluí testes automatizados nela.
- Tratamento dos dados com FormRequest;
- Exibição dos dados em Json através das Resources;
- Versionamento da API com criação e configuração de novo arquivo de rotas (
routes/api/v1.php
); - Criação de Observers, tanto para os Bolos (
CakeObserver
), quanto para a lista de espera (WaitingListObserver
);CakeObserver
: Após a atualização do bolo, caso a quantidade disponível seja maior que zero e tenha uma lista de espera vinculada, incluo esse bolo na fila para disparo de sua lista.WaitingListObserver
: Após o cadastro de um novo interessado, o bolo em questão é incluso na fila de disparo de sua lista.
- Criação de um Middleware (
CheckCakeWaitingList
) para não deixar que dados da lista de espera vinculados a um bolo diferente seja modificado de alguma forma. Ele é usado nas rotas deshow
,update
edelete
; - CRUDs construídos usando a metodologia do TDD.
- Para rodar os testes:
sail test tests/Feature/Http/Controllers/Api/V1/CakeControllerTest.php
sail test tests/Feature/Http/Controllers/Api/V1/WaitingListControllerTest.php
Os separei de acordo com seu namespace porque facilita a leitura e também é mais fácil de identificar qual classe está sendo testada.
- Endpoints/rotas da API:
GET|HEAD api/v1/cakes ...................................... api.v1.cakes.index › Api\V1\CakeController@index
POST api/v1/cakes ...................................... api.v1.cakes.store › Api\V1\CakeController@store
GET|HEAD api/v1/cakes/{cake} ............................... api.v1.cakes.show › Api\V1\CakeController@show
PUT|PATCH api/v1/cakes/{cake} ............................... api.v1.cakes.update › Api\V1\CakeController@update
DELETE api/v1/cakes/{cake} ............................... api.v1.cakes.destroy › Api\V1\CakeController@destroy
GET|HEAD api/v1/cakes/{cake}/waitingLists .................. api.v1.cakes.waitingLists.index › Api\V1\WaitingListController@index
POST api/v1/cakes/{cake}/waitingLists .................. api.v1.cakes.waitingLists.store › Api\V1\WaitingListController@store
GET|HEAD api/v1/cakes/{cake}/waitingLists/{waitingList} .... api.v1.cakes.waitingLists.show › Api\V1\WaitingListController@show
PUT|PATCH api/v1/cakes/{cake}/waitingLists/{waitingList} .... api.v1.cakes.waitingLists.update › Api\V1\WaitingListController@update
DELETE api/v1/cakes/{cake}/waitingLists/{waitingList} .... api.v1.cakes.waitingLists.destroy › Api\V1\WaitingListController@destroy
Este foi um teste que me desafiou bastante em relação as queues. Espero ter conseguido passar um pouco da ideia que tive pra solucionar o problema e como me organizei, como estruturei o código para deixá-lo melhor manutenível, com fácil compreensão e rápida leitura.