Skip to content

Instantly share code, notes, and snippets.

@mcelaney
Last active May 27, 2017 05:06
Show Gist options
  • Save mcelaney/f47b5105944bab6845f191bd67540af1 to your computer and use it in GitHub Desktop.
Save mcelaney/f47b5105944bab6845f191bd67540af1 to your computer and use it in GitHub Desktop.
With Example
module WebStore.Catalog.Product
@moduledoc """
This is a module that might sit inside of a Phoenix application in our
umbrella. We'll need to make calls to several contexts and merge the results
together if successful.
"""
alias Revzilla.CoreModel.{Assets, Images, Merchandizing, Skus, Videos}
alias Revzilla.Recommendation.SimilarProduct
alias Revzilla.WebStore.{AssetMapper, ImageMapper, ProductMapper}
alias Revzilla.WebStore.{ReplacementProductMapper, SkuMapper, VideoMapper}
alias Revzilla.WebStore.ViewModel.Product
@doc """
Given a product_id returns a Product ViewModel
"""
@spec fetch_detail(pos_integer()) ::
{:ok, Product.t()} | {:error, :not_found}
def fetch_detail(product_id) do
tasks =
%{
product: Task.async(fn -> Merchandizing.costly_operation(id) end),
skus: Task.async(fn -> Skus.costly_operation(id) end),
video: Task.async(fn -> Videos.costly_operation(id) end),
asset: Task.async(fn -> Assets.costly_operation(id) end),
images: Task.async(fn -> Images.costly_operation(id) end),
related: Task.async(fn -> SimilarProduct.costly_operation(id) end)
}
with {:ok, {:ok, replacement_data}} <- unwrap_task(tasks[:replacement], 1000),
{:ok, {:ok, product_data}} <- unwrap_task(tasks[:product]),
{:ok, {:ok, asset_data}} <- unwrap_task(tasks[:asset]),
{:ok, {:ok, sku_data}} <- unwrap_task(tasks[:skus], 100),
{:ok, {:ok, video_data}} <- unwrap_task(tasks[:video], 50),
{:ok, {:ok, images_data}} <- unwrap_task(tasks[:images], 50) do
product_data
|> ProductMapper.to_struct
|> Map.merge(SkuMapper.build_params(sku_data))
|> Map.merge(ReplacementProductMapper.build_params(replacement_data))
|> Map.merge(VideoMapper.build_params(video_data))
|> Map.merge(AssetMapper.build_params(asset_data))
|> Map.merge(ImageMapper.build_params(images_data))
|> Map.merge(tasks[:related] |> unwrap_task |> build_recommended)
|> (&({:ok, &1})).()
else
{:ok, {:error, message}} -> {:error, message}
{:ok, {:replaced, replacement}} -> {:replaced, replacement}
_ -> kill_all_for_timeout(tasks)
end
end
def build_recommended({:ok, {:ok, products}}), do: %{recommended: products}
def build_recommended(_), do: %{recommended: []}
# Note: These would probably live in a macro but I include them here for clarity
defmodule TimeoutError do
defexception message: "A task timed out"
end
@spec unwrap_task(Task.t()) :: {:ok, term} | {:exit, term} | nil
defp unwrap_task(task, timeout \\ 500) do
Task.yield(task, timeout) || Task.shutdown(task)
end
@spec kill_all_for_timeout(list(Task.t())) :: none()
def kill_all_for_timeout(tasks) do
Enum.each(tasks, &Task.shutdown/1)
raise TimeoutError
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment