Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/renanpalmeira/hashlab-challenge

Hashlab Challenge Solution
https://github.com/renanpalmeira/hashlab-challenge

clojure coreos docker golang google-cloud grpc microservices mongodb postgresql

Last synced: 10 days ago
JSON representation

Hashlab Challenge Solution

Awesome Lists containing this project

README

        

# hashlab-challenge

[![Build Status](https://travis-ci.org/RenanPalmeira/hashlab-challenge.svg?branch=master)](https://travis-ci.org/RenanPalmeira/hashlab-challenge)

Implementação do [teste de back-end](https://github.com/hashlab/hiring) da [hashlab](https://www.hash.com.br/), o teste consiste em escrever 2 serviços (serviço de desconto, serviço de produto) e fazer uma comunicação tolerante a falhas entre eles, utilizando [gRPC](https://grpc.io/).

# Stack

O projeto está dividida em 3 (três) serviços `user-service`, `discount-service` e `product-service` utilizando os banco de dados e linguagens mais próximas da stack da hash.

- Linguagens: Clojure e Go
- Bancos de dados: PostgreSQL e MongoDB

## Serviços

- `user-service` é escrito em Go e usa o MongoDB como banco de dados
- `discount-service` é escrito em Go e faz chamadas ao `user-service`
- `product-service` é escrito em Clojure, usa o PostgreSQL como banco de dados e faz chamadas ao `discount-service`

# Distribuindo a pasta `proto`

Rode em seu terminal o comando `sh scripts/distribute_proto.sh`, esse script vai copiar/colar a pasta `proto` nos serviços.

# Executando

`docker-compose up` inicia todos os serviços e os banco de dados (inserindo alguns produtos e usuários).

## Usuários disponíveis

Em `scripts/mongo/users.json` temos 29 usuários (com `birth_date` em UTC e com dias do mês de agosto de 2019) para testar o envio do header X-USER-ID.

# Endpoints

`GET /product` - Listagem de todos os produtos fazendo chamadas ao `discount-service` para cada produto quando for enviado o header X-USER-ID, por padrão a paginação está em 20 itens por página

Exemplo
```
{
"data": [
{
"id": "19a06d17-b9a4-4e48-9e31-50b07c05f1d1",
"price_in_cents": 10000,
"title": "Product 1",
"description": "Description 1",
"discount": {
"prc": 5.0,
"value_in_cents": 500
}
},
{
"id": "2aa90a5e-8c31-4121-8529-751719c988fc",
"price_in_cents": 18084,
"title": "Product 2",
"description": "Description 2",
"discount": {
"prc": 5.0,
"value_in_cents": 904
}
},
...
],
"links": {
"self": {
"number": 1,
"href": "/product?page=1"
},
"last": {
"number": 3,
"href": "/product?page=3"
},
"first": {
"number": 1,
"href": "/product?page=1"
},
"next": {
"number": 2,
"href": "/product?page=2"
}
}
}
```

`GET /product/{product-id}` - Pegando um produto por id junto fazendo uma chamada ao `discount-service` quando for enviado o header X-USER-ID

Exemplo
```
{
"id": "0f642355-3841-4bc7-8a3c-1985383578e3",
"price_in_cents": 22202,
"title": "Product 3",
"description": "Description 3",
"discount": {
"prc": 5.0,
"value_in_cents": 1110
}
}
```

## Tolerância a falhas

Caso o `discount-service` ou `user-service` parar de executar/retornar algum erro, o `product-service` continua listando os produtos, a diferença é que o sub-resource de `discount` vai retornar como nulo

Exemplo
```
{
"data": [
{
"id": "19a06d17-b9a4-4e48-9e31-50b07c05f1d1",
"price_in_cents": 10000,
"title": "Product 1",
"description": "Description 1",
"discount": null
},
{
"id": "2aa90a5e-8c31-4121-8529-751719c988fc",
"price_in_cents": 18084,
"title": "Product 2",
"description": "Description 2",
"discount": null
},
...
],
"links": {
"self": {
"number": 1,
"href": "/product?page=1"
},
"last": {
"number": 3,
"href": "/product?page=3"
},
"first": {
"number": 1,
"href": "/product?page=1"
},
"next": {
"number": 2,
"href": "/product?page=2"
}
}
}
```

## Erros

Quando não é encontrado nenhum produto ou o id requisitado não foi encontrado, vai ser retornado um erro similar a esse:

```
{
"errors": [
{
"type": "ResourcesNotFoundError",
"message": "Products not found"
}
],
"url": "/product?page=42"
}
```

# Variáveis de ambiente disponíveis

- `HASHLAB_PRODUCTION_SERVICE_PER_PAGE` Configura a quantidade de produtos por página, padrão: `20`
- `HASHLAB_DISCOUNT_SERVICE_URI` endereço do `discount-service`, padrão: `localhost:50051`
- `HASHLAB_DISCOUNT_SERVICE_PORT` porta do `discount-service` padrão: `:50051`
- `HASHLAB_USER_SERVICE_URI` endereço do `user-service` padrão: `localhost:50052`
- `HASHLAB_USER_SERVICE_PORT` porta do `user-service` padrão: `:50052`
- `HASHLAB_POSTGRES_CONNECTION_URI` URL JDBC para acessar o PostgreSQL padrão: `jdbc:postgresql://localhost:5432/hashlab?user=hashlab&password=hashlab`
- `HASHLAB_MONGODB_HOST` endereço do MongoDB padrão: `localhost:27017`
- `HASHLAB_MONGODB_USERNAME` nome de usuário do MongoDB padrão: `hashlab`
- `HASHLAB_MONGODB_PASSWORD` senha do MongoDB padrão: `hashlab`
- `HASHLAB_MONGODB_DATABASE` banco de dados do MongoDB padrão: `hashlab`
- `HASHLAB_MONGODB_AUTH_SOURCE` banco de dados de autorização do MongoDB padrão: `admin`

# Bibliotecas

## Clojure

- `pedestal` - web framework
- `honeysql` - uma camada para converter mapas Clojure em SQL
- `lein-protoc` - plugin para implementar arquivos `.proto` para Clojure
- `environ` - acessar variáveis de ambiente

## Go

- `go.mongodb.org/mongo-driver/mongo` - driver oficial de MongoDB para a linguagem Go
- `github.com/golang/protobuf/protoc-gen-go` - plugin para implementar arquivos `.proto` para Go
- `github.com/crgimenes/goconfig` - acessar variáveis de ambiente

# Estrutura

## user-service

```
user-service
├── database
│   └── database.go // configura a conexão com o MongoDB
├── main.go // implementa o serviço gRPC e inicializa o servidor gRPC
├── model
│   └── User.go // modelo para ter acesso aos dados do MongoDB
```

## discount-service

```
discount-service
├── logic
│   ├── logic.go // regras de negócio para conceder ou não desconto
│   └── logic_test.go // teste das regras de negócio
├── main.go // implementa o serviço gRPC e inicializa o servidor gRPC
└── util
└── util.go // utilitários para lidar com dados e a comunicação entre o user-service
```

## product-service

```
product-service
├── src
│   └── com
│   └── hash
│   └── product
│   ├── client
│   │   └── discount.clj // interface com o discount-service
│   ├── config.clj // configurações
│   ├── controller.clj // responsável por chamar o banco de dados e discount-service para executar as regras de negócio
│   ├── db.clj // interface com o banco de dados PostgreSQL
│   ├── interceptors
│   │   └── components.clj // utilitários para criar conexões com banco de dados e discount-service
│   ├── logic.clj // regras de negócio para lidar com a resposta do discount-service e calcular ou não o valor do desconto
│   ├── server.clj // inicializa o servidor HTTP
│   ├── service.clj // rotas e funções para lidar com o HTTP (headers, querystring, path)
│   └── util.clj // utilitários para lidar com configurações, gRPC, banco de dados, paginação, valores em centavos e as respostas em JSON
└── test
└── com
└── hash
└── product
├── controller_test.clj ;; testando as regras do controller
└── logic_test.clj ;; testando as regras de negócio
```

# Deploy

O deploy foi feito utilizando o [Google Cloud Platform](https://cloud.google.com/) com Docker e [CoreOS](https://coreos.com/) para se aproximar ao máximo da stack da Hash.

# Links

* https://blog.jmibanez.com/2018/07/22/grpc-with-clojure-and-leiningen.html
* https://github.com/vrih/clojure-grpc-example