Urania.ex

Travis Hex.pm

Efficient and elegant data access for Elixir.

NOTE: This is an experimental library until some issues with its underlying
execution model ([Pinky](https://github.com/codegram/pinky) promises) are fleshed out.
However, the public API is considered mostly stable, as the execution model is
completely separate from the semantics of constructing muses.

This is a one-to-one port of funcool/urania for Elixir.

A brief explanation blatantly stolen from Urania for Clojure’s original guide ensues:

Oftentimes, your business logic relies on remote data that you need to fetch
from different sources: databases, caches, web services, or third party APIs,
and you can’t mess things up. Urania helps you to keep your business logic clear
of low-level details while performing efficiently:

* batch multiple requests to the same data source

* request data from multiple data sources concurrently

* cache previous requests

Having all this gives you the ability to access remote data sources in a concise
and consistent way, while the library handles batching and overlapping requests
to multiple data sources behind the scenes.

Usage

First define your own data source:

defmodule MyHttpSource do
  defstruct [:url, :params]
end

defimpl Urania.DataSource, for: MyHttpSource do
  def identity(this) do
    [this.url, this.params]
  end
  
  def fetch(this, env) do
    # ... perform an actual HTTP request and return the result
  end
end

Now you’re ready to construct muses, which are sort of like composable request plans that will be carried out when you actually run the muses with Urania.run!.

muse = [%MyHttpSource { url: "www.google.com" },
        %MyHttpSource { url: "www.something.com"}]
       |> Urania.collect # lay them out in parallel
       |> Urania.flat_map(fn ([response_from_google response_from_something]) ->
          if response_from_google[:foo] do
            %MyHttpSource { url: "www.foo.com", params: response_from_google[:foo_params] }
          else
            Urania.value(Map.put(response_from_something, :good, :job))
          end
        end)
       |> Urania.map(&validate_final_response/1)

# Whenever you actually want to run the request plan:

Urania.run!(muse)
{:ok, <response>}

Urania will take care of deduplicating requests and caching them within a single run. If you implement the BatchedSource protocol for your data source in addition to DataSource, Urania will batch all your parallel requests into a single one by calling the fetch_multi function you need to satisfy:

defimpl Urania.BatchedSource, for: MyHttpSource do
  def fetch_multi(request, more_requests, env) do
    # batch together request and more_requests into a single one.
    # this function MUST return a map from request `identity`s to responses.
  end
end

Make sure to check out Urania for Clojure’s original guide for deeper understanding. The only difference is that flat_map in Urania.ex is mapcat in Clojure Urania. Everything else should have the same names and semantics.

Installation

  1. Add urania to your list of dependencies in mix.exs:

    def deps do
      [{:urania, "~> 0.1.0"}]
    end

Documentation

Check out the API documentation for detailed examples of each of the Urania primitives.

Acknowledgements

Urania for Clojure is an awesome project: kudos to its authors and maintainers.