# Bringing Itinera into existing code

## What you want to do

The shop's first checkout, [chapter 1](https://itinera-dev.org/book/rust/01-but-why-itinera.md)'s, still takes some of its orders, and it works. You want to bring it into Itinera without stopping it or rewriting it: one piece at a time, keeping the bank's client, the audit trail and the log it already has, with the checkout behaving as before after each move.

## The code

The program is [chapter 1](https://itinera-dev.org/book/rust/01-but-why-itinera.md)'s checkout. Its payment, which charged the card, renewed an expired one and audited the charge inside one function, is the piece moved first, into a workflow of its own, `payment`. The bank's client stays as it was: the steps ask for a trait, and the client implements it by calling its own methods:

Rust, from the program [`ch41_bringing_itinera_into_existing_code`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch41_bringing_itinera_into_existing_code.rs)

```rust
/// What the payment's steps ask of the bank.
pub trait Payments: Send + Sync {
    fn charge(&self, card: &str, amount: i64) -> Result<String, Declined>;
    fn renewed_card(&self, card: &str) -> Option<String>;
}

/// The bank's client, as it was, behind the steps' trait.
impl Payments for Bank {
    fn charge(&self, card: &str, amount: i64) -> Result<String, Declined> {
        Bank::charge(self, card, amount)
    }

    fn renewed_card(&self, card: &str) -> Option<String> {
        Bank::renewed_card(self, card)
    }
}
```

Rust, the same in its asynchronous form, from the program [`ch41_bringing_itinera_into_existing_code_async`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch41_bringing_itinera_into_existing_code_async.rs)

```rust
/// What the payment's steps ask of the bank.
#[async_trait]
pub trait Payments: Send + Sync {
    async fn charge(&self, card: &str, amount: i64) -> Result<String, Declined>;
    async fn renewed_card(&self, card: &str) -> Option<String>;
}

/// The bank's client, as it was, behind the steps' trait.
#[async_trait]
impl Payments for Bank {
    async fn charge(&self, card: &str, amount: i64) -> Result<String, Declined> {
        Bank::charge(self, card, amount).await
    }

    async fn renewed_card(&self, card: &str) -> Option<String> {
        Bank::renewed_card(self, card).await
    }
}
```

The charge becomes a step, and the renewal its rescue step. The charge no longer renews anything: it says why it failed.

Rust, from the program [`ch41_bringing_itinera_into_existing_code`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch41_bringing_itinera_into_existing_code.rs)

```rust
/// Charges the amount to the card, and gives back the receipt.
#[derive(Step)]
struct Charge {
    #[input]
    card: String,
    #[input]
    amount: i64,
}

#[step]
impl Charge {
    fn run(
        self,
        #[service] payments: &dyn Payments,
        #[contributor] mut contributor: Contributor<'_>,
    ) -> Result<Outcome, Error> {
        match payments.charge(&self.card, self.amount) {
            Ok(receipt) => {
                contributor.contribute("receipt", receipt);
                Self::succeed()
            }
            Err(Declined::ExpiredCard) => Self::fail("card-expired"),
            Err(Declined::Refused) => Self::fail("declined"),
        }
    }
}

/// Repairs a charge whose card had expired: it asks the bank for the card that replaced it.
#[derive(RescueStep)]
struct RenewCard {
    #[input]
    card: String,
    #[reason]
    reason: Reason,
}

#[rescue_step]
impl RenewCard {
    fn run(
        self,
        #[service] payments: &dyn Payments,
        #[contributor] mut contributor: Contributor<'_>,
    ) -> Result<RescueOutcome, Error> {
        if self.reason.code() != "card-expired" {
            return Self::fail("not-repairable");
        }
        let Some(card) = payments.renewed_card(&self.card) else {
            return Self::fail("not-renewed");
        };
        contributor.contribute("card", card);
        Self::succeed()
    }
}
```

Rust, the same in its asynchronous form, from the program [`ch41_bringing_itinera_into_existing_code_async`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch41_bringing_itinera_into_existing_code_async.rs)

```rust
/// Charges the amount to the card, and gives back the receipt.
#[derive(Step)]
struct Charge {
    #[input]
    card: String,
    #[input]
    amount: i64,
}

#[step]
impl Charge {
    async fn run(
        self,
        #[service] payments: &dyn Payments,
        #[contributor] mut contributor: Contributor<'_>,
    ) -> Result<Outcome, Error> {
        match payments.charge(&self.card, self.amount).await {
            Ok(receipt) => {
                contributor.contribute("receipt", receipt);
                Self::succeed()
            }
            Err(Declined::ExpiredCard) => Self::fail("card-expired"),
            Err(Declined::Refused) => Self::fail("declined"),
        }
    }
}

/// Repairs a charge whose card had expired: it asks the bank for the card that replaced it.
#[derive(RescueStep)]
struct RenewCard {
    #[input]
    card: String,
    #[reason]
    reason: Reason,
}

#[rescue_step]
impl RenewCard {
    async fn run(
        self,
        #[service] payments: &dyn Payments,
        #[contributor] mut contributor: Contributor<'_>,
    ) -> Result<RescueOutcome, Error> {
        if self.reason.code() != "card-expired" {
            return Self::fail("not-repairable");
        }
        let Some(card) = payments.renewed_card(&self.card).await else {
            return Self::fail("not-renewed");
        };
        contributor.contribute("card", card);
        Self::succeed()
    }
}
```

The audit becomes a policy, which writes to the audit trail with the function the checkout already used:

Rust, from the program [`ch41_bringing_itinera_into_existing_code`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch41_bringing_itinera_into_existing_code.rs)

```rust
/// Writes every successful charge to the shop's audit trail.
#[derive(Default)]
struct Audit;

#[step_policy(name = "audit")]
impl Audit {
    #[on_step_success]
    fn charged(
        &self,
        #[from_workflow] amount: i64,
        #[from_step] receipt: String,
    ) -> Result<Option<OnSuccess>, Error> {
        audit::record(&format!("charged {amount}, receipt {receipt}"));
        Self::carry_on()
    }
}
```

Rust, the same in its asynchronous form, from the program [`ch41_bringing_itinera_into_existing_code_async`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch41_bringing_itinera_into_existing_code_async.rs)

```rust
/// Writes every successful charge to the shop's audit trail.
#[derive(Default)]
struct Audit;

#[step_policy(name = "audit")]
impl Audit {
    #[on_step_success]
    async fn charged(
        &self,
        #[from_workflow] amount: i64,
        #[from_step] receipt: String,
    ) -> Result<Option<OnSuccess>, Error> {
        audit::record(&format!("charged {amount}, receipt {receipt}"));
        Self::carry_on()
    }
}
```

The log line the payment wrote becomes a reporter, which writes with the shop's existing `log::info`:

Rust, from the program [`ch41_bringing_itinera_into_existing_code`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch41_bringing_itinera_into_existing_code.rs)

```rust
/// Writes to the shop's log when a step fails, and when a rescue step repairs it.
#[derive(Default)]
struct Log;

impl Reporter for Log {
    fn report(&mut self, event: &Event) -> Result<(), Error> {
        match &event.body {
            EventBody::StepFailed { step, reason, .. } => log::info(&format!(
                "payment {}: {} failed, {}",
                event.journey_id,
                step.step,
                reason.code()
            )),
            EventBody::StepRescued { step, rescue, .. } => log::info(&format!(
                "payment {}: {} repaired by {rescue}",
                event.journey_id, step.step
            )),
            _ => {}
        }
        Ok(())
    }
}
```

The workflow has one step, and its provider makes the bank's client as the checkout did:

Rust, from the program [`ch41_bringing_itinera_into_existing_code`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch41_bringing_itinera_into_existing_code.rs)

```rust
/// Charges an order, renewing a card that has expired, and audits the charge.
#[workflow(name = "payment", reporters = [Log])]
#[with_step(Charge, name = "charge", rescue = RenewCard, policies = [Audit])]
pub struct Payment;

#[workflow]
impl Payment {
    /// Names each journey after its order.
    #[id_generator]
    fn order_number(&self, data: &DataBag) -> Result<String, Error> {
        Ok(data.required("order")?)
    }

    /// The bank, as it is configured today.
    #[provider(key = "payments")]
    fn bank(&self) -> Result<Provided<dyn Payments>, Error> {
        Ok(provide!(Bank {
            this_year: 2026,
            limit: 1000,
        }))
    }
}

/// What a payment starts with.
#[derive(InitialData)]
pub struct Due {
    pub order: String,
    pub card: String,
    pub amount: i64,
}

/// What a payment gives back.
#[derive(Output)]
pub struct Paid {
    pub receipt: String,
}
```

The rest of the checkout is chapter 1's, with three changes: `Shop` no longer holds the bank; `CheckoutError` gains a variant for a payment that did not run, which `not_run` makes from any error; and the line that took the payment now calls `pay`:

Rust, from the program [`ch41_bringing_itinera_into_existing_code`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch41_bringing_itinera_into_existing_code.rs)

```rust
/// An order, as the shop's website hands it over.
pub struct Order {
    pub order: &'static str,
    pub customer: &'static str,
    pub card: &'static str,
    pub price: i64,
    pub discount: Option<i64>,
    pub weight: i64,
}

/// Why a checkout did not complete.
#[derive(Debug)]
pub enum CheckoutError {
    OutOfStock,
    PaymentDeclined(Declined),
    NotShipped,
    PaymentNotRun(String),
}

impl fmt::Display for CheckoutError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::OutOfStock => write!(f, "out of stock"),
            Self::PaymentDeclined(Declined::ExpiredCard) => write!(f, "card expired"),
            Self::PaymentDeclined(Declined::Refused) => write!(f, "payment declined"),
            Self::NotShipped => write!(f, "no carrier could take it"),
            Self::PaymentNotRun(why) => write!(f, "the payment did not run: {why}"),
        }
    }
}

/// Why the payment workflow could not say how the payment went.
fn not_run(error: impl fmt::Display) -> CheckoutError {
    CheckoutError::PaymentNotRun(error.to_string())
}

impl From<Declined> for CheckoutError {
    fn from(declined: Declined) -> Self {
        Self::PaymentDeclined(declined)
    }
}

/// The shop, with everything it talks to.
pub struct Shop {
    pub warehouse: Warehouse,
    pub main: Carrier,
    pub backup: Carrier,
    pub messages: Messages,
}

impl Shop {
    /// Reserves, charges and ships an order, and tells the customer how it went.
    pub fn checkout(&self, order: &Order) -> Result<String, CheckoutError> {
        log::info(&format!("checkout {}", order.order));
        let shipped = self.reserve_charge_and_ship(order);
        match &shipped {
            Ok(tracking) => self.messages.tell(
                order.customer,
                &format!("order {} is on its way: {tracking}", order.order),
            ),
            Err(error) => {
                log::warn(&format!("checkout {} failed: {error}", order.order));
                self.messages.tell(
                    order.customer,
                    &format!("order {} could not be completed", order.order),
                );
            }
        }
        shipped
    }

    fn reserve_charge_and_ship(&self, order: &Order) -> Result<String, CheckoutError> {
        self.warehouse
            .reserve(order.order)
            .map_err(|_| CheckoutError::OutOfStock)?;
        let amount = order.price - order.discount.unwrap_or(0);
        self.pay(order.order, order.card, amount)?;
        shipping::ship(&self.main, &self.backup, order.order, order.weight)
            .ok_or(CheckoutError::NotShipped)
    }
```

Rust, the same in its asynchronous form, from the program [`ch41_bringing_itinera_into_existing_code_async`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch41_bringing_itinera_into_existing_code_async.rs)

```rust
/// An order, as the shop's website hands it over.
pub struct Order {
    pub order: &'static str,
    pub customer: &'static str,
    pub card: &'static str,
    pub price: i64,
    pub discount: Option<i64>,
    pub weight: i64,
}

/// Why a checkout did not complete.
#[derive(Debug)]
pub enum CheckoutError {
    OutOfStock,
    PaymentDeclined(Declined),
    NotShipped,
    PaymentNotRun(String),
}

impl fmt::Display for CheckoutError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::OutOfStock => write!(f, "out of stock"),
            Self::PaymentDeclined(Declined::ExpiredCard) => write!(f, "card expired"),
            Self::PaymentDeclined(Declined::Refused) => write!(f, "payment declined"),
            Self::NotShipped => write!(f, "no carrier could take it"),
            Self::PaymentNotRun(why) => write!(f, "the payment did not run: {why}"),
        }
    }
}

/// Why the payment workflow could not say how the payment went.
fn not_run(error: impl fmt::Display) -> CheckoutError {
    CheckoutError::PaymentNotRun(error.to_string())
}

impl From<Declined> for CheckoutError {
    fn from(declined: Declined) -> Self {
        Self::PaymentDeclined(declined)
    }
}

/// The shop, with everything it talks to.
pub struct Shop {
    pub warehouse: Warehouse,
    pub main: Carrier,
    pub backup: Carrier,
    pub messages: Messages,
}

impl Shop {
    /// Reserves, charges and ships an order, and tells the customer how it went.
    pub async fn checkout(&self, order: &Order) -> Result<String, CheckoutError> {
        log::info(&format!("checkout {}", order.order));
        let shipped = self.reserve_charge_and_ship(order).await;
        match &shipped {
            Ok(tracking) => self.messages.tell(
                order.customer,
                &format!("order {} is on its way: {tracking}", order.order),
            ),
            Err(error) => {
                log::warn(&format!("checkout {} failed: {error}", order.order));
                self.messages.tell(
                    order.customer,
                    &format!("order {} could not be completed", order.order),
                );
            }
        }
        shipped
    }

    async fn reserve_charge_and_ship(&self, order: &Order) -> Result<String, CheckoutError> {
        self.warehouse
            .reserve(order.order)
            .map_err(|_| CheckoutError::OutOfStock)?;
        let amount = order.price - order.discount.unwrap_or(0);
        self.pay(order.order, order.card, amount).await?;
        shipping::ship(&self.main, &self.backup, order.order, order.weight)
            .ok_or(CheckoutError::NotShipped)
    }
```

`pay` runs the workflow, and turns how the journey ended back into the checkout's own errors:

Rust, from the program [`ch41_bringing_itinera_into_existing_code`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch41_bringing_itinera_into_existing_code.rs)

```rust
/// Charges an order's card through the payment workflow, and gives back the receipt.
fn pay(&self, order: &str, card: &str, amount: i64) -> Result<String, CheckoutError> {
    let instance = Payment::builder()
        .initial_data(Due {
            order: order.to_owned(),
            card: card.to_owned(),
            amount,
        })
        .create()
        .map_err(not_run)?;
    let result = local_executor_for!(Payment)
        .run(instance)
        .map_err(not_run)?;
    match &result.status {
        JourneyStatus::Succeeded { .. } => {
            let paid: Paid = result.output().map_err(not_run)?;
            Ok(paid.receipt)
        }
        JourneyStatus::Failed {
            failure: Failure::Failure(reason),
            ..
        } if reason.code() == "card-expired" => Err(Declined::ExpiredCard.into()),
        JourneyStatus::Failed { .. } => Err(Declined::Refused.into()),
        _ => Err(CheckoutError::PaymentNotRun(
            result.status.kind().to_string(),
        )),
    }
}
```

Rust, the same in its asynchronous form, from the program [`ch41_bringing_itinera_into_existing_code_async`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch41_bringing_itinera_into_existing_code_async.rs)

```rust
/// Charges an order's card through the payment workflow, and gives back the receipt.
async fn pay(&self, order: &str, card: &str, amount: i64) -> Result<String, CheckoutError> {
    let instance = Payment::builder()
        .initial_data(Due {
            order: order.to_owned(),
            card: card.to_owned(),
            amount,
        })
        .create()
        .map_err(not_run)?;
    let result = local_executor_for!(Payment)
        .run(instance)
        .await
        .map_err(not_run)?;
    match &result.status {
        JourneyStatus::Succeeded { .. } => {
            let paid: Paid = result.output().map_err(not_run)?;
            Ok(paid.receipt)
        }
        JourneyStatus::Failed {
            failure: Failure::Failure(reason),
            ..
        } if reason.code() == "card-expired" => Err(Declined::ExpiredCard.into()),
        JourneyStatus::Failed { .. } => Err(Declined::Refused.into()),
        _ => Err(CheckoutError::PaymentNotRun(
            result.status.kind().to_string(),
        )),
    }
}
```

The asynchronous form starts from a checkout that was already asynchronous, its bank's client, `checkout` and `main` awaiting as they go. `Payments` and the client's implementation of it take `#[async_trait]`, the implementation awaiting the client, the steps' `run` and the audit's hook are `async fn`, and `pay` awaits the journey where the checkout awaited the payment. `main` is chapter 1's, with the same four orders, and both forms print:

What `ch41_bringing_itinera_into_existing_code` prints, recorded in [`ch41_bringing_itinera_into_existing_code.txt`](https://github.com/itinera-dev/book/blob/main/rust/expected/ch41_bringing_itinera_into_existing_code.txt)

```text
INFO checkout A-1
AUDIT charged 35, receipt R-ann-card-2028-35
to Ann: order A-1 is on its way: T-A-1
A-1: shipped, T-A-1

INFO checkout A-2
INFO payment A-2: charge failed, card-expired
INFO payment A-2: charge repaired by charge_rescue
AUDIT charged 25, receipt R-ben-card-2029-25
to Ben: order A-2 is on its way: T-A-2
A-2: shipped, T-A-2

INFO checkout A-3
AUDIT charged 30, receipt R-cat-card-2027-30
WARN Rapid cannot take A-3: 30 kg is over its 20 kg limit; trying Steady
to Cat: order A-3 is on its way: B-A-3
A-3: shipped, B-A-3

INFO checkout A-4
INFO payment A-4: charge failed, card-expired
INFO payment A-4: charge repaired by charge_rescue
INFO payment A-4: charge failed, declined
WARN checkout A-4 failed: payment declined
to Dan: order A-4 could not be completed
A-4: failed, payment declined
```

## How it works

1. **The rest of the program doesn't know.** `checkout` still returns the same result and the same errors, the shipping and the messages are untouched, and each order ends as it did in chapter 1. `pay` is the only seam: it creates an instance, runs its journey and reads how it ended.
2. **Existing code is kept, behind the workflow's parts.** The bank's client is a service through the trait it implements, the audit trail is called from a policy, and the log from a reporter. What moves into the workflow is the decisions: when to renew a card, and when to audit. The code that talks to the outside world stays where it was.
3. **The log now says what happened.** Chapter 1's payment logged that it was renewing a card, before it tried. Now the log follows the journey's events: Ben's charge failed with `card-expired`, the rescue step repaired it, and the charge succeeded. Dan's was repaired too, then failed again, `declined`, which chapter 1's log never said.
4. **One piece at a time.** The shipping, with its backup carrier, and the messages to the customer are still chapter 1's code, running beside the workflow. Each can move next: the shipping as a step with a retry budget and a provider for each attempt, the messages as a policy acting through a role. Move one piece, run the program, and compare what it prints with what it printed before.
5. **Where Itinera fits, and where it doesn't.** It fits a piece of business with decisions around it: whether to try again, how to repair, what to record and whom to tell. A single call with nothing to decide around it, such as reserving the goods here, gains nothing from a workflow of its own. It stays a call, or becomes a step of a workflow that has decisions to make.

The checkout's payment is now a workflow, and the shop has one way to charge a card. It also wants a second way to sell: customers collecting their orders at the shop's counter, paying at the counter's terminal, with nothing to ship.

## Outcomes

We have shown how to bring Itinera into code that already works, one piece at a time, keeping the code that talks to the outside world.

| The usual way                                                                              | With Itinera                                                                |
| ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------- |
| Untangling a function often means rewriting it, and its callers, in one go.                | One piece becomes a workflow behind a function the callers already call.    |
| A client, a log or an audit trail written for the old code is often rewritten for the new. | Each stays, called from a service's implementation, a policy or a reporter. |
