Hourglass.Client.Backend behaviour (hourglass v0.1.0)

Copy Markdown View Source

Behaviour describing the cluster-facing operations Hourglass code issues through Hourglass.Client.

Two implementations:

  • Hourglass.Client.Real — production. Wraps the Bridge NIFs and round-trips real Temporal Server.
  • Hourglass.Client.Mock — Mox-defined in test/support/client_mock.ex. Returns canned responses for behavioral tests so the default suite runs in-process with no cluster.

The active backend is resolved at call time via Application.fetch_env!(:hourglass, :client_backend). Production config sets it to Real; config/test.exs sets it to Mock.

Callback shape

No client ref leaks through the contract — each backend manages its own connection internally. Production opens the bridge client on demand inside each callback (matching the per-call connect pattern the codebase already uses); the mock has nothing to connect to.

Callbacks return Hourglass-typed values + decoded proto messages, never raw bytes — the Bridge's bytes-in/bytes-out contract is a NIF implementation detail, not part of this surface. That makes the mock cheap (no protobuf round-trip) and the production-impl boundary explicit (decode once at the boundary).

What the backend does NOT expose

  • connect — connection management is per-impl, not part of the contract.
  • await_workflowHourglass.await/2 is a polling loop on status/2; it doesn't need a separate cluster hook. Mock- backed describe_workflow_execution covers it.
  • query_workflow, terminate_workflow — not used by Hourglass today. Add when needed.

Summary

Callbacks

Requests cancellation of the identified workflow execution.

Returns a snapshot of the workflow's current execution state. Maps to Temporal's DescribeWorkflowExecution RPC; the caller (Hourglass.status/2) decodes the response into a WorkflowStatus struct.

Ensures the named namespace exists in the cluster. Idempotent: if the namespace already exists, returns :ok without re-registering.

Returns the workflow's full event history. Maps to GetWorkflowExecutionHistory (with wait_new_event: false semantics). Used by Hourglass.status/2 (when failures: :include) and by Hourglass.await/2's test-only completed-result extraction.

Sends a signal to the identified workflow execution.

Starts a new workflow execution. Returns a WorkflowHandle containing the cluster-assigned run_id.

Functions

Resolves the active backend module.

Sets a per-process backend override. Returns the previous value (or nil if there was none) so callers can restore it.

Callbacks

cancel_workflow(handle, reason)

@callback cancel_workflow(handle :: Hourglass.WorkflowHandle.t(), reason :: String.t()) ::
  :ok | {:error, Hourglass.Error.t()}

Requests cancellation of the identified workflow execution.

describe_workflow_execution(handle)

@callback describe_workflow_execution(handle :: Hourglass.WorkflowHandle.t()) ::
  {:ok, Temporal.Api.Workflowservice.V1.DescribeWorkflowExecutionResponse.t()}
  | {:error, Hourglass.Error.t()}

Returns a snapshot of the workflow's current execution state. Maps to Temporal's DescribeWorkflowExecution RPC; the caller (Hourglass.status/2) decodes the response into a WorkflowStatus struct.

ensure_namespace(name)

@callback ensure_namespace(name :: String.t()) :: :ok | {:error, Hourglass.Error.t()}

Ensures the named namespace exists in the cluster. Idempotent: if the namespace already exists, returns :ok without re-registering.

fetch_history(handle)

@callback fetch_history(handle :: Hourglass.WorkflowHandle.t()) ::
  {:ok, Temporal.Api.History.V1.History.t()} | {:error, Hourglass.Error.t()}

Returns the workflow's full event history. Maps to GetWorkflowExecutionHistory (with wait_new_event: false semantics). Used by Hourglass.status/2 (when failures: :include) and by Hourglass.await/2's test-only completed-result extraction.

signal_workflow(handle, name, payload)

@callback signal_workflow(
  handle :: Hourglass.WorkflowHandle.t(),
  name :: String.t(),
  payload :: term()
) ::
  :ok | {:error, Hourglass.Error.t()}

Sends a signal to the identified workflow execution.

start_workflow(workflow_module, args, opts)

@callback start_workflow(workflow_module :: module(), args :: term(), opts :: keyword()) ::
  {:ok, Hourglass.WorkflowHandle.t()} | {:error, Hourglass.Error.t()}

Starts a new workflow execution. Returns a WorkflowHandle containing the cluster-assigned run_id.

Required opts:

  • :workflow_id — caller-supplied stable id.

Optional:

Functions

impl()

@spec impl() :: module()

Resolves the active backend module.

Lookup order:

  1. Process dictionary (set_impl/1) — per-process override, used by test setup to flip to the Mox mock for a single test without affecting other concurrent tests.
  2. Application config (:client_backend) — process-wide default. Production sets it to Real; tests use Mock.

Production callers call this on every cluster operation. The cost is two map lookups — negligible compared to the cluster round-trip the call performs.

set_impl(module)

@spec set_impl(module()) :: module() | nil

Sets a per-process backend override. Returns the previous value (or nil if there was none) so callers can restore it.

Used by the test case template; production code should not call this.