Current section
Files
Jump to
Current section
Files
lib/hephaestus.ex
defmodule Hephaestus do
@moduledoc """
Entry-point macro for consumer applications.
Use this module in your application to configure and start the Hephaestus
workflow engine with your chosen storage and runner adapters.
## Usage
defmodule MyApp.Hephaestus do
use Hephaestus,
storage: Hephaestus.Runtime.Storage.ETS,
runner: Hephaestus.Runtime.Runner.Local
end
Then add `MyApp.Hephaestus` to your application's supervision tree:
children = [
MyApp.Hephaestus
]
This starts a supervision tree with Registry, DynamicSupervisor,
TaskSupervisor, and the configured storage adapter.
## API
The generated module exposes:
* `start_instance/2` - starts a workflow instance
* `start_instance/3` - starts a workflow instance with options (e.g. telemetry metadata)
* `resume/2` - resumes a waiting workflow instance
## Example
{:ok, id} = MyApp.Hephaestus.start_instance(MyApp.Workflows.OrderFlow, %{order_id: 123})
{:ok, id} = MyApp.Hephaestus.start_instance(MyApp.Workflows.OrderFlow, %{order_id: 123}, telemetry_metadata: %{request_id: "req-123"})
:ok = MyApp.Hephaestus.resume(id, :payment_confirmed)
## Options
* `:storage` - the storage adapter module (default: `Hephaestus.Runtime.Storage.ETS`)
* `:runner` - the runner adapter module (default: `Hephaestus.Runtime.Runner.Local`)
"""
@default_storage Hephaestus.Runtime.Storage.ETS
@default_runner Hephaestus.Runtime.Runner.Local
defp normalize_adapter({module, opts}) when is_list(opts), do: {module, opts}
defp normalize_adapter(module), do: {module, []}
defmacro __using__(opts) do
{storage_module, storage_opts} =
opts
|> Keyword.get(:storage, @default_storage)
|> normalize_adapter()
{runner_module, runner_opts} =
opts
|> Keyword.get(:runner, @default_runner)
|> normalize_adapter()
quote bind_quoted: [
storage_module: storage_module,
storage_opts: storage_opts,
runner_module: runner_module,
runner_opts: runner_opts
] do
@hephaestus_storage_module storage_module
@hephaestus_storage_opts storage_opts
@hephaestus_runner_module runner_module
@hephaestus_runner_opts runner_opts
@doc false
def child_spec(init_arg) do
registry = Module.concat(__MODULE__, Registry)
dynamic_supervisor = Module.concat(__MODULE__, DynamicSupervisor)
task_supervisor = Module.concat(__MODULE__, TaskSupervisor)
storage_name = Module.concat(__MODULE__, Storage)
children = [
{Registry, keys: :unique, name: registry},
{DynamicSupervisor, name: dynamic_supervisor, strategy: :one_for_one},
{Task.Supervisor, name: task_supervisor},
{@hephaestus_storage_module,
Keyword.merge(@hephaestus_storage_opts, name: storage_name)}
]
hephaestus_name = __MODULE__
hephaestus_runner = @hephaestus_runner_module
hephaestus_storage = @hephaestus_storage_module
%{
id: __MODULE__,
start:
{__MODULE__, :start_link_with_telemetry,
[
children,
[name: __MODULE__, strategy: :one_for_one] ++ List.wrap(init_arg),
%{name: hephaestus_name, runner: hephaestus_runner, storage: hephaestus_storage}
]},
type: :supervisor
}
end
@doc false
def start_link_with_telemetry(children, opts, telemetry_info) do
result = Supervisor.start_link(children, opts)
case result do
{:ok, pid} ->
Hephaestus.Telemetry.runner_init(Map.put(telemetry_info, :pid, pid))
{:ok, pid}
other ->
other
end
end
@doc """
Starts a workflow instance through the configured runner.
## Options
* `:telemetry_metadata` - a map of custom metadata to attach to all
telemetry events emitted for this instance (default: `%{}`)
## Examples
{:ok, instance_id} = MyApp.Hephaestus.start_instance(MyApp.Workflows.OrderFlow, %{order_id: 123})
{:ok, instance_id} = MyApp.Hephaestus.start_instance(MyApp.Workflows.OrderFlow, %{order_id: 123}, telemetry_metadata: %{request_id: "req-456"})
"""
def start_instance(workflow, context, opts \\ [])
when is_atom(workflow) and is_map(context) do
telemetry_metadata = Keyword.get(opts, :telemetry_metadata, %{})
@hephaestus_runner_module.start_instance(
workflow,
context,
Keyword.merge(runner_opts(), telemetry_metadata: telemetry_metadata)
)
end
@doc """
Resumes a waiting workflow instance through the configured runner.
## Examples
:ok = MyApp.Hephaestus.resume(instance_id, :payment_confirmed)
"""
def resume(instance_id, event) when is_binary(instance_id) and is_atom(event) do
@hephaestus_runner_module.resume(instance_id, event)
end
defp runner_opts do
[
storage: {@hephaestus_storage_module, Module.concat(__MODULE__, Storage)},
registry: Module.concat(__MODULE__, Registry),
dynamic_supervisor: Module.concat(__MODULE__, DynamicSupervisor),
task_supervisor: Module.concat(__MODULE__, TaskSupervisor)
] ++ @hephaestus_runner_opts
end
end
end
end