Skip to main content

Crate itinera

Crate itinera 

Source
Expand description

Itinera is a workflow framework that keeps business rules separate from flow control.

Its behaviour is defined by the Itinera specification, whose vocabulary this crate uses. This release implements tier 1 of specification 0.1.0, with the capabilities sync, async and testing, and passes its conformance cases v0.1.0-rc.15.

To learn Itinera step by step, read the Itinera book for Rust, where every example compiles and runs. The Itinera site explains why Itinera exists. This documentation is the reference for each item.

§The parts of a workflow

  • A step holds one business rule. It declares the data it needs, which the workflow supplies, runs once per attempt, and reports an outcome: success, failure or skipped. Only an attempt that succeeds commits what the step contributed. A step may have a rescue step, which runs when the step fails or terminates abnormally and, once it succeeds, lets the step run again. See step.
  • A service is an object of operations a step uses to do its business, such as a payment gateway. The step requests it by key, and a service provider of the workflow makes it, for the journey or for each attempt. See service.
  • A policy holds flow control. Its hooks, such as on step failure or on workflow success, are called in response to a step’s outcome or at the end of the journey, and may end the journey early. A hook may use a role, an interface the workflow provides. See policy.
  • A workflow lists its steps in the order they run, with their retry budgets and policies, its own policies, its input adapters, its service providers, its ID generator and its reporters. It is declared once, before any journey, and a declaration that breaks a rule of the specification is refused. See workflow.
  • A workflow instance is created for each journey, with its initial data, its reporters and any initial global metadata, and an executor runs it to a result: the journey succeeded, failed or was aborted. See instance, executor and journey.
  • Everything that happens in a journey is an event, which the journey’s reporters receive, together with the journey’s global metadata: entries of a key and a value, apart from the data bag, starting with the journey ID. See event and report.

§A first workflow

A workflow of one step, which charges an order’s price, with a step policy that audits each charge. The order is given as the journey’s initial data, and the receipt is read from its output.

use itinera::error::Error;
use itinera::executor::LocalExecutor;
use itinera::journey::{Contributor, InitialData, Output};
use itinera::policy::{OnSuccess, step_policy};
use itinera::step::{Outcome, Step, step};
use itinera::workflow::workflow;

#[derive(Step)]
struct Charge {
    #[input]
    price: i64,
}

#[step]
impl Charge {
    fn run(self, #[contributor] mut contributor: Contributor<'_>) -> Result<Outcome, Error> {
        if self.price <= 0 {
            return Self::fail("nothing-to-charge");
        }
        contributor.contribute("charged", self.price);
        Self::succeed()
    }
}

#[derive(Default)]
struct Audit;

#[step_policy(name = "audit")]
impl Audit {
    #[on_step_success]
    fn audited(
        &self,
        #[from_step] charged: i64,
        #[contributor] mut contributor: Contributor<'_>,
    ) -> Result<Option<OnSuccess>, Error> {
        contributor.contribute("audit", format!("charged {charged}"));
        Self::carry_on()
    }
}

#[workflow(name = "orders")]
#[with_step(Charge, name = "charge", retry_budget = 2, policies = [Audit])]
struct Orders;

#[derive(InitialData)]
struct NewOrder {
    price: i64,
}

#[derive(Output)]
struct Receipt {
    charged: i64,
    audit: String,
}

let instance = Orders::builder()
    .initial_data(NewOrder { price: 40 })
    .create()?;
let receipt: Receipt = LocalExecutor::new().run(instance)?.output()?;
assert_eq!(receipt.charged, 40);
assert_eq!(receipt.audit, "charged 40");

The crate’s examples go further: orders adds a second step, a workflow policy using a role, an input adapter, an ID generator and a reporter; orders_async runs the same workflow on the asynchronous executor; and orders_by_hand declares it with the builder alone, without macros.

§Features

  • macros, on by default: the attributes and derives that declare steps, rescue steps, policies, workflows, roles and journey data. They expand to builder calls and implementations of public traits, which an application can write by hand.
  • async: the asynchronous executor, and asynchronous steps, rescue steps, hooks, service providers, reporters and dispatchers, with mode::async_trait for services and roles whose methods are asynchronous.
  • testing: test support. report::RecordingReporter, a reporter that keeps every event, and the latest value of each entry of global metadata, for a test to read; fakes in place of service providers; scripted runs; and recorders that lend a step, a hook or an input adapter its handles outside a journey. Enable it for tests only, under [dev-dependencies].
  • unstable: API the specification has not settled yet; empty for now.

Modules§

error
The error of custom code.
event
Events: the record of what happens in a journey, and what only events carry.
executor
Executors, which run workflow instances, and the refusals they give when a journey cannot start.
instance
Workflow instances: one per journey, holding its journey ID, its reporters and its data.
journey
Journeys, each one execution of a workflow: their identity, their data, and how they end.
mode
Execution modes: whether a workflow is synchronous or asynchronous.
policy
Policies: their descriptors, the hooks they define, what hooks request, the lifecycles they return, and the roles through which they reach a workflow.
report
Reporters, which receive the events and the global metadata of a journey, and dispatchers, which deliver them.
service
Services: objects of operations that steps use to do their business, and the service providers through which a workflow supplies them, for a journey or for each attempt.
step
Steps: the business units of a workflow, what they need, how they are built and run, their attempts, their outcomes and the reasons they give, and the rescue steps that fix the cause of a step’s failure.
value
Values: data that can be serialized, the only data a data bag, an event or a reason holds.
workflow
Workflows: their declaration, the workflow descriptor, shared by every instance.