Hi all, currently i am in the middle of optimizing a resolution of a particular field in graphql using absinthe, which is particularly interesting because it has elements that should be batched, and element that should be asynced. I have a working implementation, but it’s somewhat hacky and i wonder if it’s possible to do this another way? (maybe there’s a way to compose batching & async together)
the original code looks somewhat like this
# Absinthe Type
object :order do
#....
field :server_company, :company do
resolve(fn order, _input, _ ->
# here's SQL call, it should be batched for optimization
order_request = Repo.preload(order, :order_request).order_request
server_company_id =
if is_nil(order.server_company_id) and
order_request.request_type == "PREORDER" do
# here's also SQL call, it should be batched for optimization
order_request = Repo.preload(order_request, :preorder_data)
case order_request.preorder_data do
%{server_company_id: server_company_id} ->
server_company_id
_ ->
nil
end
else
order.server_company_id
end
# Here's API call to other service, should be asynced for optimization
{:ok, Servers.get_company_data(server_company_id)}
end)
end
end
above code have part which should be optimized by batching, and part which should be optimized by async. currently the hacky implementation is based on moving async calling inside the batching code, it looks like:
# Absinthe Type
object :order do
#....
field :server_company, :company do
resolve(fn order, _input, _ ->
batch({__MODULE__, :batch_order_server_company}, order, fn batch_results ->
{:ok, Map.get(batch_results, order.id)}
end)
end)
end
end
def batch_order_server_company(_, orders) do
orders = Repo.preload(orders, [order_request: :preorder_data])
tasks = for order <- orders do
order_request = order.order_request
server_company_id =
if is_nil(order.server_company_id) and
order_request.request_type == "PREORDER" do
case order_request.preorder_data do
%{server_company_id: server_company_id} ->
server_company_id
_ ->
nil
end
else
order.server_company_id
end
Task.async(fn -> {order.id, Servers.get_company_data(server_company_id)} end)
end
Task.await_many(tasks) |> Enum.into(%{})
end
I was using somewhat customized async resolver, which have separate supervisor for async task and implement latency cutting by backup requests, so if possible i like to reuse functionality inside the async resolver (though if i need i could still refactor important logic part from async resolver if needed).