mod common;
use std::{
sync::{
Arc,
atomic::{AtomicUsize, Ordering},
},
time::Duration,
};
use common::RunningWorker;
use serde::{Deserialize, Serialize};
use steda::{Error, Result, RetryStrategy, Step, Task, TaskContext};
#[derive(Debug, Deserialize, Serialize)]
struct ShipOrderInput {
order_id: String,
amount_cents: u64,
}
#[derive(Debug, Deserialize, Serialize)]
struct Shipment {
order_id: String,
reservation_id: String,
payment_id: String,
}
const SHIP_ORDER: Task<ShipOrderInput, Shipment> = Task::new("ship-order");
const RESERVE_INVENTORY: Step<String> = Step::new("reserve-inventory");
const CHARGE_PAYMENT: Step<String> = Step::new("charge-payment");
async fn ship_order(
input: ShipOrderInput,
ctx: TaskContext,
reservation_calls: Arc<AtomicUsize>,
charge_calls: Arc<AtomicUsize>,
) -> Result<Shipment> {
let reservation_id = ctx
.step(RESERVE_INVENTORY, async || {
reservation_calls.fetch_add(1, Ordering::SeqCst);
println!("reserving inventory for {}", input.order_id);
Ok("RES-1001".to_owned())
})
.await?;
let payment_id = ctx
.step(CHARGE_PAYMENT, async || {
charge_calls.fetch_add(1, Ordering::SeqCst);
println!(
"capturing payment of €{}.{:02}",
input.amount_cents / 100,
input.amount_cents % 100
);
Ok("PAY-1001".to_owned())
})
.await?;
if ctx.attempt() == 1 {
println!("shipping attempt 1 failed after both checkpoints committed");
return Err(Error::Other("label printer temporarily unavailable".to_owned()));
}
println!("shipping attempt 2 replayed reservation and payment checkpoints");
Ok(Shipment { order_id: input.order_id, reservation_id, payment_id })
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
let steda = common::connect().await?;
let queue = steda.queue("example-checkpoints")?;
queue.create().await?;
let reservations = Arc::new(AtomicUsize::new(0));
let charges = Arc::new(AtomicUsize::new(0));
let reservation_calls = Arc::clone(&reservations);
let charge_calls = Arc::clone(&charges);
let worker = queue
.worker()
.task(SHIP_ORDER, move |input, ctx| {
ship_order(input, ctx, Arc::clone(&reservation_calls), Arc::clone(&charge_calls))
})
.build()?;
let worker = RunningWorker::start(worker);
let task = queue
.spawn(SHIP_ORDER, ShipOrderInput { order_id: "ORD-1001".to_owned(), amount_cents: 14_950 })
.max_attempts(2)
.retry_strategy(RetryStrategy::fixed(Duration::from_millis(250)))
.await?;
let shipment = task.result_with_timeout(Duration::from_secs(10)).await?;
assert_eq!(reservations.load(Ordering::SeqCst), 1);
assert_eq!(charges.load(Ordering::SeqCst), 1);
println!("order {} shipped", shipment.order_id);
println!(" reservation: {}", shipment.reservation_id);
println!(" payment: {}", shipment.payment_id);
worker.stop().await?;
Ok(())
}