Naming a journey
What you want to do
Every event says who decided what, and when, and which journey it belongs to, by the journey's ID. You want that ID to be one the shop's people already know, the order's number, so that every decision in the log and at the desk is tied to its order.
The code
The workflow names each journey with a method of its #[workflow] block marked #[id_generator]. It reads the order's number from the journey's data, and returns it as the journey's ID. The providers are chapter 8's, and Payments and Carrier are the traits chapter 7 shows.
#[workflow]
impl Orders {
/// Names each journey after its order.
#[id_generator]
fn order_number(&self, data: &DataBag) -> Result<String, Error> {
Ok(data.required("order")?)
}
/// The bank, as the payment client of the journey.
#[provider(key = "payments")]
fn bank(&self) -> Result<Provided<dyn Payments>, Error> {
Ok(provide!(Bank))
}
/// The main carrier, as the carrier of the journey.
#[provider(key = "carrier")]
fn main_carrier(&self) -> Result<Provided<dyn Carrier>, Error> {
Ok(provide!(MainCarrier { max_weight: 20 }))
}
}The log now writes the workflow's name and the journey ID at the start of every line:
/// The shop's log: it writes a line for every event of a journey, starting with the workflow's
/// name and the journey's ID.
#[derive(Default)]
struct Log;
impl Reporter for Log {
fn report(&mut self, event: &Event) -> Result<(), Error> {
match detail(&event.body) {
Some(detail) => println!(
"log {} {} #{} {} {detail}",
event.workflow,
event.journey_id,
event.sequence,
event.kind()
),
None => println!(
"log {} {} #{} {}",
event.workflow,
event.journey_id,
event.sequence,
event.kind()
),
}
Ok(())
}
}detail is the function chapter 8 shows, with the arm for the step's message that chapter 9 added.
And the desk says which order failed:
/// The customer service desk: of all the events of a journey, it keeps only a failed journey's.
#[derive(Default)]
struct Desk;
impl Reporter for Desk {
fn report(&mut self, event: &Event) -> Result<(), Error> {
if let EventBody::JourneyFailed { step, .. } = &event.body {
println!("desk: order {} failed at {step}", event.journey_id);
}
Ok(())
}
}The rest of the workflow is chapter 10's, the shipping is chapter 9's and the charge chapter 7's, and the shop runs Ann's and Gil's orders again. It prints:
Ann's order:
log orders A-1 #1 journey_started order, price, weight
log orders A-1 #2 attempt_started charge
log orders A-1 #3 service_provided charge gets payments from bank, a ch11_naming_a_journey::Bank
the bank charges 40 for order A-1
log orders A-1 #4 step_succeeded charge
log orders A-1 #5 contribution_committed receipt
log orders A-1 #6 attempt_started ship
log orders A-1 #7 service_provided ship gets carrier from main_carrier, a ch11_naming_a_journey::MainCarrier
the carrier books order A-1
log orders A-1 #8 step_info ship: the parcel leaves with receipt R-A-1
log orders A-1 #9 step_succeeded ship
log orders A-1 #10 contribution_committed tracking
log orders A-1 #11 journey_succeeded
the journey succeeded, with receipt R-A-1 and tracking T-A-1
Gil's order:
log orders A-7 #1 journey_started order, price, weight
log orders A-7 #2 attempt_started charge
log orders A-7 #3 service_provided charge gets payments from bank, a ch11_naming_a_journey::Bank
the bank charges 45 for order A-7
log orders A-7 #4 step_succeeded charge
log orders A-7 #5 contribution_committed receipt
log orders A-7 #6 attempt_started ship
log orders A-7 #7 service_provided ship gets carrier from main_carrier, a ch11_naming_a_journey::MainCarrier
the carrier books order A-7
log orders A-7 #8 step_failed ship: too-heavy
log orders A-7 #9 step_given_up ship
log orders A-7 #10 journey_failed ship
desk: order A-7 failed at ship
the journey failed, with receipt R-A-7 and tracking noneHow it works
- Every journey has a journey ID. It is made once, when the instance is created, and every event of the journey carries it, as
event.journey_id. The executor never makes one of its own, and you never hand one to it. - By default, it is random. Unless the workflow names its journeys, each journey's ID is a new random UUID, different on every run and meaningless to people, which is why the log of chapter 8 left it out.
- Naming journeys is the workflow's decision. An ID generator is part of the workflow, like its steps and its providers: a method marked
#[id_generator]in the#[workflow]block, which takes&selfand the journey's data, a&DataBag, and returnsResult<String, Error>. A workflow has at most one. No step and no reporter decides what a journey is called. - It reads the initial data. The generator runs when the instance is created, before any step, so the data bag holds only the initial data.
data.required("order")reads the value underorderas the type the method returns, aString, and returns an error if it is missing or of another type. Ann's journey becomesA-1, and Gil'sA-7. - The workflow's name says what it does; the ID says which journey. Every event also carries the name of its workflow, as
event.workflow: the one in#[workflow(name = "orders")], the same for every journey of the workflow. The name should say what the workflow does, asorderssays it handles the shop's orders, and the ID should name one journey of it, as an order's number names one order, so the events of each journey can be told apart from every other's.
Now the desk says "order A-7 failed at ship", and the log shows why: the main carrier won't take Gil's parcel of 60 kg. The shop has a backup carrier too, which takes heavier parcels than the main one. But the shipping gets one attempt, with the one carrier its provider makes for the journey, so a parcel the main carrier refuses is never offered to the other.
Outcomes
We have shown how a workflow names its journeys, so every event says which order it is about.
| The usual way | With Itinera |
|---|---|
| A log line usually carries an order's number only where the code that writes it remembers to add it. | Every event, and so every decision, carries the journey's ID, made once by the workflow from the order's data. |