{"id":30420753,"url":"https://github.com/macabeus/hashlab-challenge","last_synced_at":"2025-08-22T08:20:05.899Z","repository":{"id":44168608,"uuid":"164047318","full_name":"macabeus/hashlab-challenge","owner":"macabeus","description":"💯 gRPC + TS + Python + DynamoDB = [Hashlab Challenge Solution]","archived":false,"fork":false,"pushed_at":"2022-09-23T22:17:46.000Z","size":49,"stargazers_count":6,"open_issues_count":3,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2023-03-02T04:15:44.409Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/macabeus.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}},"created_at":"2019-01-04T02:28:38.000Z","updated_at":"2022-06-20T17:05:34.000Z","dependencies_parsed_at":"2023-01-18T15:01:34.917Z","dependency_job_id":null,"html_url":"https://github.com/macabeus/hashlab-challenge","commit_stats":null,"previous_names":[],"tags_count":null,"template":null,"template_full_name":null,"purl":"pkg:github/macabeus/hashlab-challenge","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/macabeus%2Fhashlab-challenge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/macabeus%2Fhashlab-challenge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/macabeus%2Fhashlab-challenge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/macabeus%2Fhashlab-challenge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/macabeus","download_url":"https://codeload.github.com/macabeus/hashlab-challenge/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/macabeus%2Fhashlab-challenge/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271606605,"owners_count":24788981,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-22T02:00:08.480Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":[],"created_at":"2025-08-22T08:20:04.892Z","updated_at":"2025-08-22T08:20:05.870Z","avatar_url":"https://github.com/macabeus.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg src=\"https://avatars2.githubusercontent.com/u/24793433?s=200\u0026v=4\" width=\"127px\" height=\"127px\" align=\"left\"/\u003e\n\n# hashlab-challenge\n\nImplementação do [desafio de backend](https://github.com/hashlab/hiring/tree/5d9c767101f2fa7021155930839fb790e0451f7e) para a [Hashlab](https://www.hash.com.br/). Resumidamente, deve-se construir dois microservices que se comunicam por gRPC, da qual um serviço é responsável pela listagem de produtos (Products) e o outro pelo desconto customizado por produto (Discount).\n\nO serviço Products precisa ser resilitente, agindo do seguinte modo em caso de falha no Discount:\n- se o Discount estiver indisponível, o Products deve continuar podendo listar os produtos, mas sem aplicar os descontos;\n- se o Products for listar os produtos A, B e C e, ao tentar obter o desconto customizado desses produtos, o do B falhar, ainda deve listar todos os produtos, com o A e C tendo desconto e o B não.\n\nAlém disso, deve-se armazenar os produtos e usuário em um banco de dados. Para facilitar, eu optei em usar um banco de dados compartilhado entre os dois microservices.\n\nAs regras de descontos são:\n- se for aniversário do usuário, deve-se conceder 5% de desconto\n- se for blackfriday (nesse exemplo, fixada em 25/11/2019), deve-se conseder 10% de desconto\n- desconto deve ser limitado em até 10%\n\n# Executando\n\n## Como rodar o projeto\n\nPode usar o seguinte comando do Makefile para subir todos os serviços:\n\n```\nmake up\n```\n\nEm seguida, para popular o banco de dados, use:\n\n```\ndocker-compose run --rm database_seed\n```\n\nDesse modo, a listagem de produtos estará acessível a partir da url `http://localhost:3000/product`. Nessa rota, opcionalmente, pode-se passar um id de usuário definindo no header `X-USER-ID`. Outro parâmetro optional no header é o `FORCE-DISCOUNT-DEBUG`, da qual deve-se passar um número que será o desconto retornado pelo Discount, o que é útil para fins de teste.\n\nDe forma análoga, para parar todos os serviços pode usar o seguinte comando:\n\n```\nmake stop\n```\n\n## Simulando falhas\n\nTal como descrito nos requisitos do projeto, o Products precisa ser resilitente. Mesmo em cenários de falhas no Discount, ele ainda precisa listar os produtos. No caso, temos dois cenários para simular:\n\n### Discount fora do ar\n\nPode-se apenas deixar de subir o service `discount_server` ou derrubá-lo, para assim simular um cenário em que o Discount caiu. Para derrubar esse service, use o seguinte comando:\n\n```\ndocker-compose stop discount_server\n```\n\n### Intermitência\n\nCaso queria simular um cenário em que falha ao tentar o desconto de alguns produtos, basta setar a flag na envrionment variable `DISCOUNT_SERVER_INTERMITTENT` e subir novamente o service `discount_server`:\n\n```\nexport DISCOUNT_SERVER_INTERMITTENT=1\ndocker-compose up discount_server\n```\n\nDesse modo, produtos com ID par retornarão erro ao tentar obter o desconto, e assim não terão o desconto aplicado.\n\n## Como rodar os testes e linter\n\nTestes do Products:\n\n```\ndocker-compose run products_test_unit\ndocker-compose run products_test_functional\ndocker-compose run products_test_e2e\n```\n\nLint no Products:\n\n```\ndocker-compose run products_lint\n```\n\nTest do Discount:\n\n```\ndocker-compose run discount_test\n```\n\nLint no Discount:\n\n```\ndocker-compose run discount_lint\n```\n\n## Como compilar os .proto\n\nCaso precise mudar a interface de comunicação entre os dois serviços, será necessário atualizar o arquivo `proto/discount.proto` e, sem seguida, compilá-lo novamente.\n\nPara compilar, precisa ter instalado em seu sistema o [`protobuf-compiler`](https://packages.debian.org/sid/protobuf-compiler), pacote do Python [`grpc_tools`](https://pypi.org/project/grpcio-tools/), e o pacote do NPM [`grpc-tools`](https://www.npmjs.com/package/grpc-tools) ([ver issue #5](https://github.com/macabeus/hashlab-challenge/issues/5)).\n\nInstalar essas dependências (usando como exemplo Ubuntu):\n\n```\nsudo apt-get install protobuf-compiler\npython -m pip install grpcio-tools\nnpm install -g grpc-tools\n```\n\nInstale as dependências do `proto`:\n\n```\ncd proto\nnpm i\n```\n\nE por fim, para compilar execute na raiz do projeto:\n\n```\nmake compile-proto\n```\n\nPronto! Agora já pode utilizar as novas definições do `.proto`!\n\n# Escolha da Stack\n\n## Linguagens\n\nUm dos requisitos do projeto é que os microservices usem linguagens diferentes. Assim, optei em usar TypeScript no Products e Python no Discount. Como os serviços são simples e tem um escopo bem reduzido, não vejo necessidade de usar uma linguagem que enforce muito em como deve-se programar, me dando mais liberdade no desenvolvimento e foco em implementar a regra de negócio da aplicação.\n\nAlém disso, há boas implementações de gRPC e integração com o DynamoDB tanto para TypeScript como para Python, o que facilita o desenvolvimento. E com TypeScript consigo obter uns dos benefícios do gRPC, que são os parâmetros tipificados.\n\n## Infra\n\nComo esse projeto é bem simples, a primeira ideia que me veio em mente para o deploy seria utilizar um serviço igualmente simples de se usar, como o Now ou Heroku, porem, tanto o [Now](https://spectrum.chat/zeit/general/grpc-service-returning-520-origin-error~c3a28953-c7f1-43b1-af4e-b4327a6d7d3e) como [Heroku](https://devcenter.heroku.com/articles/http-routing#http-versions-supported) não apresentam suporte para gRPC.\n\nAssim me restou utilizar um IaaS para se deployar a aplicacao, que no caso seria a AWS, por ter mais experiência. Como toda a aplicação estar dockerizada, se fosse realizar o deploy, a ideia mais simples que me vem em mente seria utilizar o Fargate.\n\n## Banco de dados\n\nAs consultas a serem feitas nessa aplicação são simples: obter um usuário com base no ID e listar todos os produtos. Além disso, não há um relacionamento entre essas duas entidades. Desse modo, um banco orientado a documentos me pareceu ser uma boa abordagem para essa aplicação. E já que estava em planos usar a AWS, o DynamoDB se encaixa bem, me abstraindo mais a respeito de como escalar e subir esse banco, e ele consegue performar bem nas consultas que essa aplicação visa realizar.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmacabeus%2Fhashlab-challenge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmacabeus%2Fhashlab-challenge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmacabeus%2Fhashlab-challenge/lists"}