O desafio, encontrado no GitHub da Goomer, é de uma API de Restaurantes, que permite listar, cadastrar, alterar e atualizar restaurantes e seus respectivos produtos.
Como um diferencial para este desafio, eu utilizei o OAuth2 e JWT para a Autenticação, com um Authorization Server próprio, utilizando o Password Grant Type. Além disso, inspirado neste desafio da Zé Delivery (que eu também já resolvi, e o repositório está aqui), eu decidi implementar a funcionalidade de realizar consultas geográficas, utilizando as GeoSpatial/GeoJson Queries do banco de dados MongoDB. Dada uma coordenada(longitude e latitude), o banco de dados procura registros próximos deste ponto, sendo possível específicar a distância mínima e/ou máxima em metros.
E em consequência disto, eu também decidi utilizar APIs externas relacionadas a dados geográficos. Eu utilizei a API da ViaCep para converter um dado CEP para um endereço, e uma API da OpenStreetView, o Nominatim, para converter um endereço em um ponto geográfico. O objetivo disto é para que um usuário, ao se cadastrar, não precise digitar todo o seu endereço, somento o CEP e o seu número, e quando ele for consultar restaurantes geograficamente próximos, ele não precise especificar as suas próprias coordenadas(longitude e latitude).
Outra funcionalidade implementada é o GitHub Actions, para realizar o CI/CD desta aplicação. A integração contínua (CI) garante que o código seja testado automaticamente a cada nova alteração, detectando erros rapidamente. A entrega e o deploy contínuos (CD) permitem que essas alterações sejam entregues automaticamente para produção ou ambientes de testes, garantindo entregas rápidas e frequentes. Neste caso, eu fiz com que este projeto fosse publicado no Docker Hub a cada novo commit.
O desafio pode ser encontrado aqui: https://github.com/goomerdev/job-dev-backend-interview
Esses foram os requisitos definidos no enunciado original:
A sua API deverá ser capaz de:
- Listar todos os restaurantes
- Cadastrar novos restaurantes
- Listar os dados de um restaurante
- Alterar os dados um restaurante
- Excluir um restaurante
- Listar todos os produtos de um restautante
- Criar um produto de um restaurante
- Alterar um produto de um restaurante
- Excluir um produto de um restaurante
O cadastro do restaurante precisa ter os seguintes campos:
- Foto do restaurante
- Nome do restaurante
- Endereço do restaurante
- Horários de funcionamento do restaurante (ex.: De Segunda à Sexta das 09h as 18h e de Sabado à Domingo das 11h as 20h).
O cadastro de produtos do restaurante precisa ter os seguintes campos:
- Foto do produto
- Nome do produto
- Preço do produto
- Categoria do produto (ex.: Doce, Salgados, Sucos...)
Quando o Produto for colocado em promoção, precisa ter os seguintes campos:
- Descrição para a promoção do produto (ex.: Chopp pela metade do preço)
- Preço promocional
- Dias da semana e o horário em que o produto deve estar em promoção
- Desenvolvimento de um serviço RESTful para toda a aplicação.
- Spring Boot
- Spring MVC
- MongoDB
- Mongo Express
- ViaCep API
- Nominatim API
- Spring Security
- JWT
- OAuth2
- SpringDoc OpenAPI 3
- Docker
- JUnit5
- Mockito
- Jacoco
- Bean Validation
- HATEOAS
- SOLID, DRY, YAGNI, KISS
- API REST
- TDD
- Consultas com Spring Data JPA
- Injeção de Dependências
- Testes Automatizados
- Geração automática do Swagger com a OpenAPI 3
- Autenticação e Autorização com OAuth2 e JWT
Alguns diferenciais que não foram solicitados no desafio:
- Utilização de Docker
- TDD - Test Driven Development
- Tratamento de exceções
- Validações com Constraints Customizados
- Testes Unitários e de Integração
- Cobertura de Testes com Jacoco
- Documentação Swagger
- Consumo da API da ViaCep com o RestTemplate, para consultar o CEP do Usuário
- Consumo da API Nominatim, para obter as Coordenadas de um usuário a partir do CEP
- Implementação de HATEOAS
1.Clonar repositório git:
git clone https://github.com/FernandoCanabarroAhnert/desafio-goomer.git
2.Instalar dependências.
mvn clean install
3.Executar a aplicação Spring Boot.
4.Testar endpoints através do Postman ou da url http://localhost:8080/swagger-ui/index.html#/
- Clonar repositório git
- Construir o projeto:
./mvnw clean package
- Construir a imagem:
./mvnw spring-boot:build-image
- Executar o container:
docker run --name desafio-goomer -p 8080:8080 -d desafio-goomer:0.0.1-SNAPSHOT
Para fazer as requisições HTTP abaixo, foi utilizada a ferramenta Postman:
-
Collection do Postman completa: Postman-Collection
-
Environment do Postman: Postman Environment
-
Consultar Restaurantes próximos das coordenadas do Usuário logado
$ http GET http://localhost:8080/restaurants/geo/nearMe?maxDistance=8000
[
{
"id": "67015fcaf90ce176dccb9761",
"name": "Hamburgueria 1903",
"imageUrl": "https://exemplo.com/hamburgueria-1903.jpg",
"address": {
"logradouro": "Av. Carlos Gomes",
"numero": "100",
"complemento": "Loja 2",
"bairro": "Três Figueiras",
"cep": "90480-003",
"cidade": "Porto Alegre",
"estado": "RS"
},
"point": {
"longitude": -51.171539,
"latitude": -30.031834
},
"openingHours": {
"Segunda-Sexta": "11h-23h",
"Sábado-Domingo": "12h-22h"
},
"tags": [
"Hamburguer",
"Restaurante",
"Fast Food"
],
"links": [
{
"rel": "Consultar Restaurante por Id",
"href": "http://localhost:8080/restaurants/67015fcaf90ce176dccb9761"
}
]
},
{
"id": "67015f9af90ce176dccb975b",
"name": "Restaurante Sushi Seninha",
"imageUrl": "https://exemplo.com/sushi-seninha.jpg",
"address": {
"logradouro": "Av. Protásio Alves",
"numero": "3136",
"complemento": "",
"bairro": "Petrópolis",
"cep": "90410-006",
"cidade": "Porto Alegre",
"estado": "RS"
},
"point": {
"longitude": -51.186074,
"latitude": -30.032024
},
"openingHours": {
"Segunda-Sexta": "12h-23h",
"Sábado": "12h-00h",
"Domingo": "12h-22h"
},
"tags": [
"Sushi",
"Comida Japonesa",
"Restaurante"
],
"links": [
{
"rel": "Consultar Restaurante por Id",
"href": "http://localhost:8080/restaurants/67015f9af90ce176dccb975b"
}
]
}
]
- Consultar Restaurante por Id
$ http GET http://localhost:8080/restaurants/{id}
{
"id": "67015ecef90ce176dccb9753",
"name": "Churrascaria Galpão Crioulo",
"imageUrl": "https://exemplo.com/galpao-crioulo.jpg",
"address": {
"logradouro": "Av. Loureiro da Silva",
"numero": "255",
"complemento": "Parque Maurício Sirotsky Sobrinho",
"bairro": "Centro Histórico",
"cep": "90050-240",
"cidade": "Porto Alegre",
"estado": "RS"
},
"point": {
"longitude": -51.225755,
"latitude": -30.028448
},
"openingHours": {
"Segunda-Sexta": "11h-23h",
"Sábado-Domingo": "11h-22h"
},
"tags": [
"Churrascaria",
"Comida Gaúcha",
"Restaurante"
],
"_links": {
"Consultar Produtos do Restaurante Churrascaria Galpão Crioulo": {
"href": "http://localhost:8080/restaurants/67015ecef90ce176dccb9753/products"
}
}
}
- Consultar Restaurantes por Coordenadas
$ http GET http://localhost:8080/restaurants/geo?longitude=-51.225755&latitude=-30.028448&maxDistance=7500
[
{
"id": "67015ecef90ce176dccb9753",
"name": "Churrascaria Galpão Crioulo",
"imageUrl": "https://exemplo.com/galpao-crioulo.jpg",
"address": {
"logradouro": "Av. Loureiro da Silva",
"numero": "255",
"complemento": "Parque Maurício Sirotsky Sobrinho",
"bairro": "Centro Histórico",
"cep": "90050-240",
"cidade": "Porto Alegre",
"estado": "RS"
},
"point": {
"longitude": -51.225755,
"latitude": -30.028448
},
"openingHours": {
"Segunda-Sexta": "11h-23h",
"Sábado-Domingo": "11h-22h"
},
"tags": [
"Churrascaria",
"Comida Gaúcha",
"Restaurante"
],
"links": [
{
"rel": "Consultar Restaurante por Id",
"href": "http://localhost:8080/restaurants/67015ecef90ce176dccb9753"
}
]
}
]