Primeiro iremos criar uma nova requisição. Utilizaremos o mesmo recurso para fins de estudo, mas a estratégia de execução será diferente.
Precisamos da atualização dos cafés quentes;
Temos a limitação de 50 requisições por dia no Rate Limit;
Não precisamos ter uma resposta imediata, isso pode ser atualizado quando der.
Tendo isso em mente, criaremos uma requisição que bate no mesmo endpoint dos cafés quentes, porém, não iremos esperar uma resposta imediata e sim, um :ok, nos avisando que o agendamento esta pronto.
usei o mesmo nome da função, com adição do sufixo _assync, indicando assincronicidade. Isso quer dizer, não esperamos que isso seja feito agora e sim, de forma assincrona.
Diferente da primeira função, na segunda precisamos agendar um Job para ser executado. Para isso, precisamos primeiro do Worker que irá processar nosso Job. Vamos criar o worker com o nome HotCoffeesWorker que fara a sincronia de forma assíncrona para nos.
A estrutura do nosso modulo é bem simples, utilizaremos a macro use Oban.Worker e precisaremos implementar a função perform/1 .
defmoduleCoffeeShop.Integrations.Coffee.HotCoffeesWorkerdouseOban.Worker @impl Oban.Workerdefperform(%Oban.Job{args: _args}) do :okendend
A função perform/1 será executada quando um job rodar. Precisamos que ela realize a chamada para o serviço externo. Para isso, vamos reaproveitar a função sincrona criada no cliente, Client.all_hot_coffees/1.
O Oban salva os parâmetros como map, devido a não suportar uma lista. Por isso que ao receber o opts convertemos ele novamente para lista e o processamento continua igual o original utilizanod o Keyword.
O case/1 controla o que esperamos. Nesse caso, sera apenas um sucesso quando recebermos um status 200. Fora isso, queremos que seja um erro.
Nosso worker está pronto, agora precisamos agendar a execução. Para isso utilizaremos a função Oban.insert/1 que espera um Job. Para conseguirmos o Job de forma fácil, utilizaremos a macro em nosso modulo worker e executaremos a função HotCoffeesWorker.new/1 que retorna um Job já configurado para nosso Worker. Vamos replicar isso em nosso cliente.
lib/coffee_shop/integrations/coffee/client.ex
defmoduleCoffeeShop.Integrations.Coffee.ClientdoaliasCoffeeShop.Integrations.Coffee.ResponsealiasCoffeeShop.Integrations.Coffee.HotCoffeesWorker# ...defall_hot_coffees(opts \\ []) do base_url =Keyword.get(opts, :base_url,base_url()) opts|>new_client()|>Tesla.get("#{base_url}/coffee/hot")|>Response.build()enddefall_hot_coffees_async(opts \\ []) do opts|>Map.new(fn option -> option end)|>HotCoffeesWorker.new()|>Oban.insert()enddefpbase_url(), do: "https://api.sampleapis.com"end
Na linha 18, convertemos a lista em Map. Como informamos que aconteceria na criação do worker.
Ao rodar a função all_hot_coffees_async/1 receberemos um {:ok, _job} de resposta e não mais a estrutura %Response{}, isso acontece porque estamos criando um novo Job e não mais realizando uma requisição para o serviço. Vamos criar um teste comprovando nossa ideia.
defmoduleCoffeeShop.Integrations.Coffee.ClientTestdouseExUnit.CaseuseOban.Testing, repo: CoffeeShop.RepoaliasCoffeeShop.Integrations.Coffee.ClientaliasCoffeeShop.Integrations.Coffee.HotCoffeesWorkeraliasCoffeeShop.Integrations.Coffee.ResponsealiasCoffeeShop.Integrations.Counter# ... describe "all_hot_coffees_async/0"do test "schedule a job"do opts = [ base_url: "http://localhost:3000", retry_delay: 1 ] assert {:ok,_job} =Client.all_hot_coffees_async(opts)# Verificando se foi agendado com sucessoassert_enqueued( worker: HotCoffeesWorker, args: %{"base_url"=>"http://localhost:3000","retry_delay"=>1} )endendend
Você já pode rodar os testes do nosso cliente
mix test test/coffee_shop/integrations/coffee/client_test.exs
> mix test test/coffee_shop/integrations/coffee/client_test.exs
....
Finished in 1.0 seconds (0.00s async, 1.0s sync)
4 tests, 0 failures
Agendamento realizado com sucesso. Mas precisamos de mais garantias. Nosso Rate Limit é de 24h. Isso quer dizer que precisamos garantir que essa execução só irá ser executada em 24h certo? No teste, nao temos essa garantia ainda, temos que adiciona-la.
O próprio Oban nos disponibiliza na função assert_enqueued o scheduled_at, para confirmarmos para quando foi agendado. Com isso, criamos a variavel in_a_day com a utilização do DateTime para adicionarmos o tempo a partir de agora + 24h e adicionamos na assertion. Vamos rodar e ver o que acontece.
mix test test/coffee_shop/integrations/coffee/client_test.exs
mix test test/coffee_shop/integrations/coffee/client_test.exs
.
1) test all_hot_coffees_async/0 schedule a job (CoffeeShop.Integrations.Coffee.ClientTest)
test/coffee_shop/integrations/coffee/client_test.exs:73
Expected a job matching:
%{
args: %{"base_url" => "http://localhost:3000", "retry_delay" => 1},
worker: CoffeeShop.Integrations.Coffee.HotCoffeesWorker,
scheduled_at: ~U[2024-04-17 13:44:40.309948Z]
}
to be enqueued. Instead found:
[
%{
args: %{"base_url" => "http://localhost:3000", "retry_delay" => 1},
worker: "CoffeeShop.Integrations.Coffee.HotCoffeesWorker",
scheduled_at: ~U[2024-04-16 13:44:40.294598Z]
}
]
code: assert_enqueued(
stacktrace:
test/coffee_shop/integrations/coffee/client_test.exs:83: (test)
..
Finished in 1.2 seconds (0.00s async, 1.2s sync)
4 tests, 1 failure
A há, é um ótimo teste para se ter, não é? Esperamos é rodar o job em 2024-04-17, mas está sendo executado um dia antes 2024-04-16. Isso quer dizer, esta rodando logo quando é agendado.
Não é o comportamento que esperamos. Isso está acontecendo porque realmente não configuramos essa etapa. Devemos configurar isso quando o job. Em nosso cliente é inserido o job e lá vamos adicionar essa opção:
Criado a regra na linha 9 e adicionado a opção na linha 12. Agora rodaremos o teste novamente.
mix test test/coffee_shop/integrations/coffee/client_test.exs
mix test test/coffee_shop/integrations/coffee/client_test.exs
Compiling 1 file (.ex)
....
Finished in 1.1 seconds (0.00s async, 1.1s sync)
4 tests, 0 failures
Estamos atendendo a regra que precisamos seguir com o rate limit de longa duração.
Em nosso teste, não temos garantia de que a execução do perform funciona, apenas garantimos o agendamento. Precisamos saber se conseguimos rodar o que foi agendado. Para isso usaremos a função perform_job/2 que executará o job agendado.