Construa uma API, que responda JSON, para conversão monetária. Ela deve ter uma moeda de lastro (USD) e fazer conversões entre diferentes moedas com cotações de verdade e atuais.
A API deve converter entre as seguintes moedas:
- USD
- BRL
- EUR
- BTC
- ETH
Ex: USD para BRL, USD para BTC, ETH para BRL, etc...
A requisição deve receber como parâmetros: A moeda de origem, o valor a ser convertido e a moeda final.
Ex: ?from=BTC&to=EUR&amount=123.45
A arquitetura foi dividida em três componentes:
- Redis, banco chave/valor in-memory;
- Nó worker em Golang, que periodicamente recebe as cotações baseado em uma estratégia qualquer e salva os dados no Redis;
- Nó api em Golang, que levanta o servidor HTTP e serve rota de conversão, calculando o resultado baseado nas cotações atualmente no Redis.
Escolhi o Redis nessa arquitetura por alguns motivos:
- É um banco com consultas extremamente rápidas, o que ajuda a escalar em cenários de muitas requisições;
- Chave/valor, mantendo o código simples, natural e direto;
- Pela flexibilidade no caso de escalar a arquitetura, podendo utilizar as facilidades do Redis Cluster.
- Para evitar qualquer tratamento de concorrência no caso de uma abordagem de armazenamento in-memory dentro da aplicação.
Pelos requisitos de performance e simplicidade da linguagem, optei por escolher Go como linguagem de ambos os componentes API e Worker. Frameworks/Libs/Ferramentas:
- Echo, para simplificar a API e aumentar a segurança via middlewares incluídos (Anti-CSRF, XSS, etc);
- Go Redis para conexão com o banco;
- dep para gerenciamento de dependências.
Cogitei utilizar uma lib para gerenciamento de configuração (Ex. Configor) para facilitar a passagem de configurações via variáveis de ambiente ao rodar as containers, mas para manter a simplicidade e evitar qualquer bug relacionado à ferramentas de terceiros, utilizei o padrão da linguagem.
Tendo em vista que o próprio routing mesh interno do Docker Swarm já realiza roteamento round-robin, optei por não incluir qualquer load balancer. No entanto, seria possível incluir facilmente na arquitetura. Não entendo que o caching das requisições seja tão crucial nesse use case, dado que tanto os valores convertidos quanto as cotações são dados extremamente mutáveis.
A API serve um método GET /currency/convert
para as requisições, recebendo os parâmetros:
from
, a moeda base.to
, a moeda alvo.amount
, a quantidade de unidades monetárias da moeda base.
O cálculo é feito a partir das taxas de conversão encontradas no Redis.
Exemplo:
GET http://localhost:8080/currency/convert?amount=1&from=USD&to=BRL
{
"amount":1,
"from":"USD",
"to":"BRL",
"resultingAmount":3.9041
}
Para casos de erro foi respeitada a semântica http, com os status code corretos sendo enviados em cada caso.
O Worker roda periodicamente como configurado via propriedade UpdateInterval
nas configurações da aplicação. Seu funcionamento é resumido a:
- Requisitar novas taxas de cambio, via
RequestQuotasStrategy
implementada. - Salvar taxas de cambio no Redis.
- Aguardar por
UpdateInterval
milissegundos e executar novamente.
- Docker-CLI/Engine (versão 18.0.9)
- Ferramenta docker-compose (versão 1.23.x)
$ git clone https://github.com/schonmann/challenge-bravo.git
$ cd challenge-bravo
-
Levantando aplicação
$ docker-compose up -d
-
Descendo aplicação
$ docker-compose down
-
Manager
-
Levantando stack
$ docker swarm init $ docker stack deploy -c docker-compose.yml currency-conversion
-
Escalando nós da API (Ex: 3)
$ docker service scale currency-conversion_api=3
-
Descendo stack
$ docker stack rm currency-conversion-api
-
-
Worker
$ docker swarm join --token <SWARM_TOKEN>
Para 2500 reqs/s e nível de concorrência 100, a latência média das requisições foi de 9.7 ms:
Aumentando o nível de concorrência para 1000, a latência média sobe para 31.9 ms, o que parece não comprometer os resultados:
Para estes testes, foi utilizada uma máquina Inspiron-7572 (i7 U + 16GB), rodando Ubuntu 18.04.
- As API keys foram commitadas no controle de versão apenas por conveniência, e considerando o fato de serem chaves gratuitas. Entendo que não é uma boa prática e nem faria em caso de desenvolvimento produtivo.
- Tive alguns problemas pra executar em uma máquina windows utilizando swarm mode (não conseguia fazer requisições outbound para as API's de cotação de dentro das containers). Por tanto, sugiro que seja utilizado o docker-compose na avaliação.