Jeeves v0.1.3 Jeeves.Service
Implement a service consisting of a pool of workers, all running in their own application.
Prerequisites
You’ll need to add poolboy to your project dependencies.
Usage
To create the service:
Create a module that implements the API you want. This API will be expressed as a set of public functions. Each function will automatically receive the current state in a variable (by default named
state
). There is no need to declare this as a parameter.why?. If a function wants to change the state, it must end with a call to theJeeves.Common.update_state/2
function (which will have been imported into your module automatically).Add the line
use Jeeves.Service
to the top of this module.
Options
You can pass a keyword list to use Jeeves.Service:
state_name:
atomThe default name for the state variable is (unimaginatively)
state
. Usestate_name
to override this. For example, the previous example named the stateoptions
, and inside therecognize
function your could writeoptions.algorithm
to look up the algorithm to use.pool: [
options]
Set options for the service pool. One or more of:
min: n
The minimum number of workers that should be active, and by extension the number of workers started when the pool is run. Default is 2.
max: n
The maximum number of workers. If all workers are busy and a new request arrives, a new worker will be started to handle it if the current worker count is less than
max
. Excess idle workers will be quietly killed off in the background. Default value is(min+1)*2
.showcode:
booleanIf truthy, dump a representation of the generated code to STDOUT during compilation.
timeout:
integer or floatSpecify the timeout to be used when the client calls workers in the pool. If all workers are busy, and none becomes free in that time, an OTP exception is raised. An integer specifies the timeout in milliseconds, and a float in seconds (so 1.5 is the same as 1500).
Consuming the Service
Each service runs in an independent application. These applications are referenced by the main application.
The main application lists the services it uses in its mix.exs
file.
«todo: finish this»
State
Each worker has independent state. This state is initialized in two stages.
First, the main application maintains a list of services it uses in its
mix.exs
file:
@services [
prime_factors: [
args: [ max_calc_time: 10_000 ]
]
]
When the main application starts, it starts each service application in turn.
As each starts, it passes the arguments in the args
list to the function
setup_worker_state
in the service. This function does what is required to
create a state that can be passed to each worker when it is started.
For example, our PrimeFactors service might want to maintain a cache
of previously calculated results, shared between all the workers. It
could dothis by creating an agent in the
setup_worker_state
function and adding its pid to the state it
returns.
def setup_worker_state(initial_state) do
{ :ok, pid } = Agent.start_link(fn -> %{} end)
initial_state
|> Enum.info(%{ cache: pid })
end
Each worker would be able to access that agent via the state it receives:
def factor(n) do
# yes, two workers could calculate the same value in parallel... :)
case Agent.get(state.cache, fn map -> map[n] end) do
nil ->
result = complex_calculation(n)
Agent.update(state.cache, fn map -> Map.put(map, n, result) end)
result
result ->
result
end
end