A first step
What you want to do
The shop charges an order its price. You want that business rule written with Itinera: the charge, and nothing else.
The code
In Itinera, a business rule is a step. In Rust, a step is a struct holding the data it needs, with a run method that does the work. The shop's bank is faked, so that it would print what it charges, once something runs the step.
/// Charges an order's price.
#[derive(Step)]
struct Charge {
#[input]
price: i64,
}
#[step]
impl Charge {
fn run(self) -> Result<Outcome, Error> {
if self.price <= 0 {
return Self::fail("nothing-to-charge");
}
bank_charges(self.price);
Self::succeed()
}
}That is the whole step. The program declares it and does nothing else: nothing builds a Charge, and nothing calls its run, so it prints nothing. Rust notices too, and would warn that Charge is never constructed and run is never used; the program turns that warning off, with #![allow(dead_code)] at its top.
How it works
- A step is a struct with a
run, in animplblock marked#[step].#[derive(Step)]lets Itinera build the struct from its fields. - A step asks for the data it needs. Each field marked
#[input]is an input, named after its field:price: i64asks for a number under the nameprice. The step says what it needs, and nothing about where it comes from. - A step does its work in
run.runtakes the step by value,self, so aChargeis used up by running it. Here it charges the bank, unless there is nothing to charge. - A step says how it went, and nothing more.
runreturns the step's outcome inside aResult:Self::succeed()when the charge went through, orSelf::fail("nothing-to-charge"), a failure with its reason, when the price is 0 or less. Both giveOkwith the outcome, and this step never returns anErr. A step reports what happened, never what to do next. The charge doesn't know which order it is charging for, what the shop does before it or after it, or what happens to an order whose charge failed.
So the charge is written, and it can't charge anything. Nothing builds it with an order's price, nothing calls run, and nothing does anything with its outcome. A step on its own is a business rule waiting to be used. The shop still needs a way to run it for an order.
Outcomes
We have shown a business rule written as a step, which asks for its data and says how it went, and nothing else.
| The usual way | With Itinera |
|---|---|
| A charge function usually signals a failure with an error type of its own, so each caller learns to read it its own way. | Every step says how it went the same way: success, or a failure with a reason. |
| A function usually gets its data as parameters, from whichever caller calls it. | The step names each piece of data it needs as an input, in its own declaration, so what it reads is listed in one place. |