{"id":20982057,"url":"https://github.com/renanpalmeira/hashlab-challenge","last_synced_at":"2026-04-05T22:37:24.506Z","repository":{"id":78685936,"uuid":"199955022","full_name":"renanpalmeira/hashlab-challenge","owner":"renanpalmeira","description":"Hashlab Challenge Solution","archived":false,"fork":false,"pushed_at":"2019-08-05T13:06:13.000Z","size":113,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-01-02T03:37:52.030Z","etag":null,"topics":["clojure","coreos","docker","golang","google-cloud","grpc","microservices","mongodb","postgresql"],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/renanpalmeira.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-08-01T01:37:22.000Z","updated_at":"2019-12-10T19:38:33.000Z","dependencies_parsed_at":"2023-03-12T05:04:05.435Z","dependency_job_id":null,"html_url":"https://github.com/renanpalmeira/hashlab-challenge","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/renanpalmeira/hashlab-challenge","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/renanpalmeira%2Fhashlab-challenge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/renanpalmeira%2Fhashlab-challenge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/renanpalmeira%2Fhashlab-challenge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/renanpalmeira%2Fhashlab-challenge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/renanpalmeira","download_url":"https://codeload.github.com/renanpalmeira/hashlab-challenge/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/renanpalmeira%2Fhashlab-challenge/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31452899,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T21:22:52.476Z","status":"ssl_error","status_checked_at":"2026-04-05T21:22:51.943Z","response_time":75,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","coreos","docker","golang","google-cloud","grpc","microservices","mongodb","postgresql"],"created_at":"2024-11-19T05:42:30.655Z","updated_at":"2026-04-05T22:37:24.134Z","avatar_url":"https://github.com/renanpalmeira.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# hashlab-challenge\n\n[![Build Status](https://travis-ci.org/RenanPalmeira/hashlab-challenge.svg?branch=master)](https://travis-ci.org/RenanPalmeira/hashlab-challenge)\n\nImplementaçã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/).\n\n# Stack\n\nO 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.\n\n- Linguagens: Clojure e Go\n- Bancos de dados: PostgreSQL e MongoDB\n\n## Serviços\n\n- `user-service` é escrito em Go e usa o MongoDB como banco de dados\n- `discount-service` é escrito em Go e faz chamadas ao `user-service`\n- `product-service` é escrito em Clojure, usa o PostgreSQL como banco de dados e faz chamadas ao `discount-service`\n\n# Distribuindo a pasta `proto`\n\nRode em seu terminal o comando `sh scripts/distribute_proto.sh`, esse script vai copiar/colar a pasta `proto` nos serviços.\n\n# Executando\n\n`docker-compose up` inicia todos os serviços e os banco de dados (inserindo alguns produtos e usuários).\n\n## Usuários disponíveis \n\nEm `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.\n\n# Endpoints\n\n`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\n\nExemplo\n```\n{\n    \"data\": [\n        {\n            \"id\": \"19a06d17-b9a4-4e48-9e31-50b07c05f1d1\",\n            \"price_in_cents\": 10000,\n            \"title\": \"Product 1\",\n            \"description\": \"Description 1\",\n            \"discount\": {\n                \"prc\": 5.0,\n                \"value_in_cents\": 500\n            }\n        },\n        {\n            \"id\": \"2aa90a5e-8c31-4121-8529-751719c988fc\",\n            \"price_in_cents\": 18084,\n            \"title\": \"Product 2\",\n            \"description\": \"Description 2\",\n            \"discount\": {\n                \"prc\": 5.0,\n                \"value_in_cents\": 904\n            }\n        },\n        ...\n    ],\n    \"links\": {\n        \"self\": {\n            \"number\": 1,\n            \"href\": \"/product?page=1\"\n        },\n        \"last\": {\n            \"number\": 3,\n            \"href\": \"/product?page=3\"\n        },\n        \"first\": {\n            \"number\": 1,\n            \"href\": \"/product?page=1\"\n        },\n        \"next\": {\n            \"number\": 2,\n            \"href\": \"/product?page=2\"\n        }\n    }\n}\n```\n\n`GET /product/{product-id}` - Pegando um produto por id junto fazendo uma chamada ao `discount-service` quando for enviado o header X-USER-ID\n\nExemplo\n```\n{\n    \"id\": \"0f642355-3841-4bc7-8a3c-1985383578e3\",\n    \"price_in_cents\": 22202,\n    \"title\": \"Product 3\",\n    \"description\": \"Description 3\",\n    \"discount\": {\n        \"prc\": 5.0,\n        \"value_in_cents\": 1110\n    }\n}\n```\n\n## Tolerância a falhas\n\nCaso 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\n\nExemplo\n```\n{\n    \"data\": [\n        {\n            \"id\": \"19a06d17-b9a4-4e48-9e31-50b07c05f1d1\",\n            \"price_in_cents\": 10000,\n            \"title\": \"Product 1\",\n            \"description\": \"Description 1\",\n            \"discount\": null\n        },\n        {\n            \"id\": \"2aa90a5e-8c31-4121-8529-751719c988fc\",\n            \"price_in_cents\": 18084,\n            \"title\": \"Product 2\",\n            \"description\": \"Description 2\",\n            \"discount\": null\n        },\n        ...\n    ],\n    \"links\": {\n         \"self\": {\n             \"number\": 1,\n             \"href\": \"/product?page=1\"\n         },\n         \"last\": {\n             \"number\": 3,\n             \"href\": \"/product?page=3\"\n         },\n         \"first\": {\n             \"number\": 1,\n             \"href\": \"/product?page=1\"\n         },\n         \"next\": {\n             \"number\": 2,\n             \"href\": \"/product?page=2\"\n         }\n    }\n}\n```\n\n## Erros\n\nQuando não é encontrado nenhum produto ou o id requisitado não foi encontrado, vai ser retornado um erro similar a esse:\n\n```\n{\n    \"errors\": [\n        {\n            \"type\": \"ResourcesNotFoundError\",\n            \"message\": \"Products not found\"\n        }\n    ],\n    \"url\": \"/product?page=42\"\n}\n```\n\n# Variáveis de ambiente disponíveis\n\n- `HASHLAB_PRODUCTION_SERVICE_PER_PAGE` Configura a quantidade de produtos por página, padrão: `20` \n- `HASHLAB_DISCOUNT_SERVICE_URI` endereço do `discount-service`, padrão: `localhost:50051`\n- `HASHLAB_DISCOUNT_SERVICE_PORT` porta do `discount-service` padrão: `:50051`\n- `HASHLAB_USER_SERVICE_URI` endereço do `user-service` padrão: `localhost:50052`\n- `HASHLAB_USER_SERVICE_PORT` porta do `user-service` padrão: `:50052`\n- `HASHLAB_POSTGRES_CONNECTION_URI` URL JDBC para acessar o PostgreSQL padrão: `jdbc:postgresql://localhost:5432/hashlab?user=hashlab\u0026password=hashlab`\n- `HASHLAB_MONGODB_HOST` endereço do MongoDB padrão: `localhost:27017`\n- `HASHLAB_MONGODB_USERNAME` nome de usuário do MongoDB padrão: `hashlab`\n- `HASHLAB_MONGODB_PASSWORD` senha do MongoDB padrão: `hashlab`\n- `HASHLAB_MONGODB_DATABASE` banco de dados do MongoDB padrão: `hashlab`\n- `HASHLAB_MONGODB_AUTH_SOURCE` banco de dados de autorização do MongoDB padrão: `admin`\n\n# Bibliotecas\n\n## Clojure\n\n- `pedestal`  - web framework\n- `honeysql` - uma camada para converter mapas Clojure em SQL\n- `lein-protoc` - plugin para implementar arquivos `.proto` para Clojure\n- `environ` - acessar variáveis de ambiente\n\n## Go\n\n- `go.mongodb.org/mongo-driver/mongo` - driver oficial de MongoDB para a linguagem Go\n- `github.com/golang/protobuf/protoc-gen-go` - plugin para implementar arquivos `.proto` para Go\n- `github.com/crgimenes/goconfig` - acessar variáveis de ambiente\n\n# Estrutura\n\n## user-service\n\n```\nuser-service\n├── database\n│   └── database.go // configura a conexão com o MongoDB\n├── main.go // implementa o serviço gRPC e inicializa o servidor gRPC\n├── model\n│   └── User.go // modelo para ter acesso aos dados do MongoDB\n```\n\n## discount-service\n\n```\ndiscount-service\n├── logic\n│   ├── logic.go // regras de negócio para conceder ou não desconto \n│   └── logic_test.go // teste das regras de negócio\n├── main.go // implementa o serviço gRPC e inicializa o servidor gRPC\n└── util\n    └── util.go // utilitários para lidar com dados e a comunicação entre o user-service\n```\n\n## product-service\n\n```\nproduct-service\n├── src\n│   └── com\n│       └── hash\n│           └── product\n│               ├── client\n│               │   └── discount.clj // interface com o discount-service\n│               ├── config.clj // configurações\n│               ├── controller.clj // responsável por chamar o banco de dados e discount-service para executar as regras de negócio \n│               ├── db.clj // interface com o banco de dados PostgreSQL\n│               ├── interceptors\n│               │   └── components.clj // utilitários para criar conexões com banco de dados e discount-service\n│               ├── logic.clj // regras de negócio para lidar com a resposta do discount-service e calcular ou não o valor do desconto \n│               ├── server.clj // inicializa o servidor HTTP \n│               ├── service.clj // rotas e funções para lidar com o HTTP (headers, querystring, path)\n│               └── util.clj // utilitários para lidar com configurações, gRPC, banco de dados, paginação, valores em centavos e as respostas em JSON  \n└── test\n    └── com\n        └── hash\n            └── product\n                ├── controller_test.clj ;; testando as regras do controller\n                └── logic_test.clj ;; testando as regras de negócio\n```\n\n# Deploy\n\nO 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.\n\n# Links\n\n* https://blog.jmibanez.com/2018/07/22/grpc-with-clojure-and-leiningen.html\n* https://github.com/vrih/clojure-grpc-example","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frenanpalmeira%2Fhashlab-challenge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frenanpalmeira%2Fhashlab-challenge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frenanpalmeira%2Fhashlab-challenge/lists"}