Last active
May 27, 2017 05:06
-
-
Save mcelaney/f47b5105944bab6845f191bd67540af1 to your computer and use it in GitHub Desktop.
With Example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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