README.md

_tl;dr_

* create anonymous, named, and pooled services by just specifying
  their functions
* have them automatically wrapped in standard OTP GenServers and
  Supervisors.
* simplify testing with automatically generated nonservice
  implementations.
  

Here's a pool of between 2 and 5 Fibonacci number services, each
supervised and run in a separate, parallel process:

~~~ elixir
defmodule Fib do
  use Jeeves.Pooled

  def fib(n), do: _fib(n)

  defp _fib(0), do: 0
  defp _fib(1), do: 1
  defp _fib(n), do: _fib(n-1) + _fib(n-2)    # terribly inefficient
end
~~~

You'd start the pool using

~~~ elixir
Fib.run
~~~

And invoke one of the pool of workers using

~~~ elixir
Fib.fib(20)   # => 6765
~~~

We can use GenServer state to cache already calculated values, making the
process O(n) rather than O(1.6ⁿ).

~~~ elixir
defmodule Fib do
  use Jeeves.Pooled, 
      state:       %{ 0 => 0, 1 => 1}, 
      state_name: :cache

  def fib(n) do
    case cache[n] do
    nil ->
      fib_n = fib(n-2, cache) + fib(n-1, cache)
      update_state(Map.put(cache, n, fib_n)) do
        fib_n
        end
    cached_result ->
      cached_result      
    end 
  end
end
~~~

In the previous example each worker maintains its own cache. In the
Fibonacci example, that's fine, as the cost of loading the cache is
small. But if we _wanted_ to share a single cache between all workers,
we can add it as a named service:

~~~ elixir
defmodule FibCache do
  use Jeeves.Named, state: %{ 0 => 0, 1 => 1 }

  def get(n), do: state[n]
  def put(n, fib_n) do
    state
    |> Map.put(n, fib_n)
    |> update_state(do: fib_n)
  end
end

defmodule Fib do
  use Jeeves.Pooled, 

  def fib(n) do
    case FibCache.get(n) do
    nil ->
      with fib_n = fib(n-2) + fib(n-1),
      do: FibCache.put(n, fib_n)   # => returns result
    cached_result ->
      cached_result
    end 
  end
end
~~~


end _tl;dr_
----

# Jeeves—at your service

Erlang encourages us to write our code as self-contained servers and
applications. Elixir makes it even easier by removing much of the
boilerplate needed to create an Erlang GenServer.

However, creating these servers is often more work than it needs to
be. And, unfortunately, following good design practices adds even more
work, in the form of duplication.

The Jeeves library aims to make it easier for newcomers to craft
well designed services. It doesn't replace GenServer. It is simply a
layer on top that handles the most common GenServer use cases. The
intent is to remove any excuse that people might have for not writing
their Elixir code using a ridiculously large number of trivial
services.

# Basic Example

You can think of an Erlang process as a remarkably pure implementation
of an object. It is self contained, with private state, and with an
interface that is accessed by sending messages. This harks straight
back to the early days of Smalltalk.

Jeeves draws on that idea. When you include it in a module, that
module's public functions become the interface to the service. You
write the functions, and Jeeves rewrites them into a GenServer.

Here's a simple service that implements a key-value store.

~~~ elixir
defmodule KVStore do

  use Jeeves.Anonymous, state: %{}

  def put(store, key, value) do
    set_state(Map.put(store, key, value)) do
      value
    end
  end
  
  def get(store, key) do
    store[key]
  end
end
~~~

The first parameter to `put` and `get` is the current state, and the second is
the value being passed in.

You'd call it using

~~~ elixir
rt = KVStore.run()

KVStore.put(rt, :name, "Elixir")
KVStore.get(rt, :name)   # => "Elixir"
~~~

Behind the scenes, Jeeves has created a pure implementation of our
totaller, along with a GenServer that delegates to that
implementation. What does that code look like? Add the `:show_code`
option to our original source.

~~~ elixir
defmodule KVStore do

  use Jeeves.Anonymous, 
      state: %{},
      show_code: true

  def put(store, key, value) do
    # . . .
~~~

During compilation, you'll see the code that will actually be run:


~~~ elixir

# defmodule RunningTotal do

  import(Kernel, except: [def: 2])
  import(Jeeves.Common, only: [def: 2, set_state: 2])
  @before_compile({Jeeves.Anonymous, :generate_code_callback})
  def run() do
    run(%{})
  end
  def run(state) do
    {:ok, pid} = GenServer.start_link(__MODULE__, state, server_opts())
    pid
  end
  def init(state) do
    {:ok, state}
  end
  def initial_state(default_state, _your_state) do
    default_state
  end
  def server_opts() do
    []
  end
  defoverridable(initial_state: 2)

  use(GenServer)

  def put(store, key, value) do
    GenServer.call(store, {:put, key, value})
  end
  def get(store, key) do
    GenServer.call(store, {:get, key})
  end
  def handle_call({:put, key, value}, _, store) do
    __MODULE__.Implementation.put(store, key, value)
    |> Jeeves.Common.create_genserver_response(store)                                                                  
  end
  def handle_call({:get, key}, _, store) do
    __MODULE__.Implementation.get(store, key)
    |> Jeeves.Common.create_genserver_response(store)                                                                         
  end
  
  defmodule Implementation do
    def put(store, key, value) do
      set_state(Map.put(store, key, value)) do
        value
      end
    end
    def get(store, key) do
      store[key]
    end
  end

# end
~~~

### Testing

We can test this implementation without starting a separate
process by simply calling functions in `KVStore.Implementation`. You
have to supply the state, and allow for the fact that the responses
will include the updated state if `set_state` is called.

~~~ elixir 
alias KVI KVStore.Implementation

@state %{}

test "we can put a KV pair, then get it, retrieves the correct value" do
  { :reply, Elixir, state } = KVI.put(@state, :name, Elixir)
  assert KVI.get(state, name) == Elixir
end
~~~

## Creating a Named (Singleton) Service

It's sometimes convenient to create a global, named, service. Logging
is a good example of this, as are registry services, global caches, and
the like. 

We can make out KV store a global _named service_ with some trivial changes:

~~~ elixir
defmodule NamedKVStore do

  use Jeeves.Named, 
      state_name:   :kvs, 
      state:        %{}, 

  def put(key, value) do
    set_state(Map.put(kvs, key, value)) do
      value
    end
  end
  
  def get(key) do
    kvs[key]
  end
end
~~~

Notice there's a big of magic here. A named service can be called from
anywhere in your code. It doesn't require you to remember a PID or any
other handle, as the service's API invokes the service process by
name. However, the service process itself contains state (the map in
the KVStore example). The client doesn't need to know about this
internal state, so it is never exposed via the API. Instead, it is
automatically made available inside the service's functions in a
variable. By default, this variable is called `state`, but the
NamedKVStore example changes this to something more meaningful, `kvs`.

You'd call the named KV store service using

~~~ elixir
NamedKVStore.put(:name, "Elixir")
NamedKVStore.put(:engine, "BEAM")  
NamedKVStore.get(:name)            # => "Elixir"
~~~

## Pooled Services

Named services can be turned into pools of workers by changing to `Jeeves.Pooled`.

~~~ elixir
defmodule TwitterFeed do

  use Jeeves.Pooled,
      pool: [ min: 5, max: 20 ]

  def fetch(name) do
    # ...
  end
end
~~~

Calls to `TwitterFeed.fetch` would run in parallel, up to a maximum of
20 processes.


## Inline code

Finally, we can tell Jeeves not to generate a server at all.

~~~ elixir
defmodule RunningTotal do

  use Jeeves.Inline, state: 0

  def add(total, value) do
    set_state(value+total)
  end
end
~~~

The cool thing is we can switch between not running a process, running
a single server, or running a pool of servers by changing a single
declaration in the module.

# More Information

* Anonymous services: 
    * documentation: `Jeeves.Anonymous`
    * [example](./examples/apps/anon_worker)
  
* Named services: 
    * documentation: `Jeeves.Named`
    * [example](./examples/apps/named_worker)
  
* Pooled services: 
    * documentation: `Jeeves.Pooled`
    * [example](./examples/apps/pooled_named_worker)
  
* [Some background](./background.html)

# To do

* [ ] Implement anonymous pools
* [ ] Add declarative supervision (when child_spec becomes available)
* [ ] Tests!

# Author

Dave Thomas  (dave@pragdave.me, @pragdave)

License: see the file [LICENSE.md](./LICENSE.html)