#![allow(unused_variables)]
use serde::Deserialize;
use serde::Serialize;
use slog::Drain;
use std::sync::Arc;
use steno::ActionContext;
use steno::ActionError;
use steno::ActionRegistry;
use steno::DagBuilder;
use steno::Node;
use steno::SagaDag;
use steno::SagaId;
use steno::SagaName;
use steno::SagaResultErr;
use steno::SagaType;
use steno::SecClient;
use uuid::Uuid;
#[tokio::main]
async fn main() {
let log = {
let decorator = slog_term::TermDecorator::new().build();
let drain = slog_term::FullFormat::new(decorator).build().fuse();
let drain = slog::LevelFilter(drain, slog::Level::Warning).fuse();
let drain = slog_async::Async::new(drain).build().fuse();
slog::Logger::root(drain, slog::o!())
};
let sec = steno::sec(
log.new(slog::o!()),
Arc::new(steno::InMemorySecStore::new()),
);
let trip_context = Arc::new(TripContext {});
let params = TripParams {
hotel_name: String::from("Springfield Palace Hotel"),
flight_info: String::from("any flight"),
car_info: String::from("1998 Canyonero"),
charge_details: String::from("Moneybank Charge Card"),
};
book_trip(sec, trip_context, params).await;
}
async fn book_trip(
sec: SecClient,
trip_context: Arc<TripContext>,
params: TripParams,
) {
let registry = {
let mut registry = ActionRegistry::new();
load_trip_actions(&mut registry);
Arc::new(registry)
};
let dag = make_trip_dag(params);
let saga_id = SagaId(Uuid::new_v4());
let saga_future = sec
.saga_create(saga_id, Arc::new(trip_context), dag, registry)
.await
.expect("failed to create saga");
sec.saga_start(saga_id).await.expect("failed to start saga running");
let result = saga_future.await;
match result.kind {
Ok(success) => {
println!(
"hotel: {:?}",
success.lookup_node_output::<HotelReservation>("hotel")
);
println!(
"flight: {:?}",
success.lookup_node_output::<FlightReservation>("flight")
);
println!(
"car: {:?}",
success.lookup_node_output::<CarReservation>("car")
);
println!(
"payment: {:?}",
success.lookup_node_output::<PaymentConfirmation>("payment")
);
println!("\nraw summary:\n{:?}", success.saga_output::<Summary>());
}
Err(SagaResultErr { error_node_name, error_source, undo_failure }) => {
println!("action failed: {}", error_node_name.as_ref());
println!("error: {}", error_source);
if let Some((undo_node_name, undo_error_source)) = undo_failure {
println!("additionally:");
println!("undo action failed: {}", undo_node_name.as_ref());
println!("error: {}", undo_error_source);
}
}
}
}
mod actions {
use super::TripSaga;
use lazy_static::lazy_static;
use std::sync::Arc;
use steno::new_action_noop_undo;
use steno::Action;
use steno::ActionFunc;
lazy_static! {
pub(super) static ref PAYMENT: Arc<dyn Action<TripSaga>> =
ActionFunc::new_action(
"payment",
super::saga_charge_card,
super::saga_refund_card
);
pub(super) static ref HOTEL: Arc<dyn Action<TripSaga>> =
ActionFunc::new_action(
"hotel",
super::saga_book_hotel,
super::saga_cancel_hotel
);
pub(super) static ref FLIGHT: Arc<dyn Action<TripSaga>> =
ActionFunc::new_action(
"flight",
super::saga_book_flight,
super::saga_cancel_flight
);
pub(super) static ref CAR: Arc<dyn Action<TripSaga>> =
ActionFunc::new_action(
"car",
super::saga_book_car,
super::saga_cancel_car
);
pub(super) static ref PRINT: Arc<dyn Action<TripSaga>> =
new_action_noop_undo("print", super::saga_print);
}
}
fn load_trip_actions(registry: &mut ActionRegistry<TripSaga>) {
registry.register(actions::PAYMENT.clone());
registry.register(actions::HOTEL.clone());
registry.register(actions::FLIGHT.clone());
registry.register(actions::CAR.clone());
registry.register(actions::PRINT.clone());
}
fn make_trip_dag(params: TripParams) -> Arc<SagaDag> {
let name = SagaName::new("book-trip");
let mut builder = DagBuilder::new(name);
builder.append(Node::action(
"payment",
"ChargeCreditCard",
actions::PAYMENT.as_ref(),
));
builder.append_parallel(vec![
Node::action("hotel", "BookHotel", actions::HOTEL.as_ref()),
Node::action("flight", "BookFlight", actions::FLIGHT.as_ref()),
Node::action("car", "BookCar", actions::CAR.as_ref()),
]);
builder.append(Node::action("output", "Print", actions::PRINT.as_ref()));
Arc::new(SagaDag::new(
builder.build().expect("DAG was unexpectedly invalid"),
serde_json::to_value(params).unwrap(),
))
}
#[derive(Debug, Deserialize, Serialize)]
struct TripParams {
hotel_name: String,
flight_info: String,
car_info: String,
charge_details: String,
}
#[derive(Debug)]
struct TripContext;
#[derive(Debug)]
struct TripSaga;
impl SagaType for TripSaga {
type ExecContextType = Arc<TripContext>;
}
#[derive(Debug, Deserialize, Serialize)]
struct HotelReservation(String);
#[derive(Debug, Deserialize, Serialize)]
struct FlightReservation(String);
#[derive(Debug, Deserialize, Serialize)]
struct CarReservation(String);
#[derive(Debug, Deserialize, Serialize)]
struct PaymentConfirmation(String);
#[derive(Debug, Deserialize, Serialize)]
struct Summary {
car: CarReservation,
flight: FlightReservation,
hotel: HotelReservation,
payment: PaymentConfirmation,
}
async fn saga_charge_card(
action_context: ActionContext<TripSaga>,
) -> Result<PaymentConfirmation, ActionError> {
let trip_context = action_context.user_data();
let params = action_context.saga_params::<TripParams>()?;
let charge_details = ¶ms.charge_details;
Ok(PaymentConfirmation(String::from("123")))
}
async fn saga_refund_card(
action_context: ActionContext<TripSaga>,
) -> Result<(), anyhow::Error> {
let trip_context = action_context.user_data();
let p: PaymentConfirmation = action_context.lookup("payment")?;
Ok(())
}
async fn saga_book_hotel(
action_context: ActionContext<TripSaga>,
) -> Result<HotelReservation, ActionError> {
let trip_context = action_context.user_data();
let params = action_context.saga_params::<TripParams>()?;
let hotel_name = ¶ms.hotel_name;
Ok(HotelReservation(String::from("123")))
}
async fn saga_cancel_hotel(
action_context: ActionContext<TripSaga>,
) -> Result<(), anyhow::Error> {
let trip_context = action_context.user_data();
let confirmation: HotelReservation = action_context.lookup("hotel")?;
Ok(())
}
async fn saga_book_flight(
action_context: ActionContext<TripSaga>,
) -> Result<FlightReservation, ActionError> {
let trip_context = action_context.user_data();
let params = action_context.saga_params::<TripParams>()?;
let flight_info = ¶ms.flight_info;
Ok(FlightReservation(String::from("123")))
}
async fn saga_cancel_flight(
action_context: ActionContext<TripSaga>,
) -> Result<(), anyhow::Error> {
let trip_context = action_context.user_data();
let confirmation: FlightReservation = action_context.lookup("flight")?;
Ok(())
}
async fn saga_book_car(
action_context: ActionContext<TripSaga>,
) -> Result<CarReservation, ActionError> {
let trip_context = action_context.user_data();
let params = action_context.saga_params::<TripParams>()?;
let car_info = ¶ms.car_info;
Ok(CarReservation(String::from("123")))
}
async fn saga_cancel_car(
action_context: ActionContext<TripSaga>,
) -> Result<(), anyhow::Error> {
let trip_context = action_context.user_data();
let confirmation: CarReservation = action_context.lookup("car")?;
Ok(())
}
async fn saga_print(
action_context: ActionContext<TripSaga>,
) -> Result<Summary, ActionError> {
Ok(Summary {
car: action_context.lookup("car")?,
flight: action_context.lookup("flight")?,
hotel: action_context.lookup("hotel")?,
payment: action_context.lookup("payment")?,
})
}