use std::convert::Infallible;
use std::error::Error;
use std::future::{Future, ready};
use ruststream::memory::prelude::*;
use serde::Deserialize;
#[derive(Debug, Deserialize, schemars::JsonSchema)]
struct Order {
id: u64,
}
struct Receive;
impl Handle<Order> for Receive {
fn handle(
&self,
order: &Order,
_outs: &(),
_ctx: &mut Context<'_>,
) -> impl Future<Output = Result<(), HandlerOutcome>> {
println!("got order {}", order.id);
ready(Ok(()))
}
}
struct WithContext;
impl Handle<Order> for WithContext {
fn handle(
&self,
order: &Order,
_outs: &(),
ctx: &mut Context<'_>,
) -> impl Future<Output = Result<(), HandlerOutcome>> {
if let Some(id) = ctx.headers().correlation_id() {
println!("order {} correlates to {id}", order.id);
}
ready(Ok(()))
}
}
struct SettleBatch;
impl Handle<[Order]> for SettleBatch {
fn handle(
&self,
orders: &[Order],
_outs: &(),
_ctx: &mut Context<'_>,
) -> impl Future<Output = Result<(), Vec<HandlerOutcome>>> {
println!("settling {} orders", orders.len());
ready(Ok(()))
}
}
struct Reconcile;
impl Handle<[Order]> for Reconcile {
fn handle(
&self,
orders: &[Order],
_outs: &(),
_ctx: &mut Context<'_>,
) -> impl Future<Output = Result<(), Vec<HandlerOutcome>>> {
ready(Err(orders
.iter()
.map(|order| {
if order.id == 0 {
HandlerOutcome::retry()
} else {
HandlerOutcome::ack()
}
})
.collect()))
}
}
fn batch_routes() -> impl RouterDef<MemoryBroker> {
Router::<MemoryBroker>::new()
.include(
subscriber("orders", SettleBatch)
.batch(nonzero!(64))
.build(),
)
.include(subscriber("orders", Reconcile).batch(nonzero!(64)).build())
}
struct Frame<'a>(&'a [u8]);
impl Deserialized for Frame<'_> {
type Output<'a> = Frame<'a>;
type Error = Infallible;
fn from_payload(payload: &[u8]) -> Result<Frame<'_>, Self::Error> {
Ok(Frame(payload))
}
}
impl Input for Frame<'_> {
type Axis = SoloDeserialized<Frame<'static>>;
}
struct Ingest;
impl<'p> Handle<[Frame<'p>]> for Ingest {
fn handle(
&self,
frames: &[Frame<'p>],
_outs: &(),
_ctx: &mut Context<'_>,
) -> impl Future<Output = Result<(), Vec<HandlerOutcome>>> {
let bytes: usize = frames.iter().map(|frame| frame.0.len()).sum();
println!("ingesting {} frames ({bytes} bytes)", frames.len());
ready(Ok(()))
}
}
struct FanOut;
impl Handle<Order> for FanOut {
fn handle(
&self,
order: &Order,
_outs: &(),
_ctx: &mut Context<'_>,
) -> impl Future<Output = Result<(), HandlerOutcome>> {
println!("processing order {}", order.id);
ready(Ok(()))
}
}
fn fan_out_routes() -> impl RouterDef<MemoryBroker> {
Router::<MemoryBroker>::new()
.include(subscriber("orders", FanOut).workers(nonzero!(16)).build())
}
struct PerCustomer;
impl Handle<Order> for PerCustomer {
fn handle(
&self,
order: &Order,
_outs: &(),
_ctx: &mut Context<'_>,
) -> impl Future<Output = Result<(), HandlerOutcome>> {
println!("processing order {}", order.id);
ready(Ok(()))
}
}
fn per_customer_routes() -> impl RouterDef<MemoryBroker> {
Router::<MemoryBroker>::new().include(
subscriber("orders", PerCustomer)
.workers_by_key(nonzero!(16))
.build(),
)
}
struct Audit;
impl Handle<Order> for Audit {
fn handle(
&self,
order: &Order,
_outs: &(),
_ctx: &mut Context<'_>,
) -> impl Future<Output = Result<(), HandlerOutcome>> {
println!("auditing order {}", order.id);
ready(Ok(()))
}
}
struct Archive;
impl Handle<Order> for Archive {
fn handle(
&self,
order: &Order,
_outs: &(),
_ctx: &mut Context<'_>,
) -> impl Future<Output = Result<(), HandlerOutcome>> {
println!("archiving order {}", order.id);
ready(Ok(()))
}
}
struct Drain;
impl Handle<[Order]> for Drain {
fn handle(
&self,
orders: &[Order],
_outs: &(),
_ctx: &mut Context<'_>,
) -> impl Future<Output = Result<(), Vec<HandlerOutcome>>> {
println!("draining {} orders", orders.len());
ready(Ok(()))
}
}
struct Bill;
impl Handle<Order> for Bill {
fn handle(
&self,
order: &Order,
_outs: &(),
_ctx: &mut Context<'_>,
) -> impl Future<Output = Result<(), HandlerOutcome>> {
println!("billing order {}", order.id);
ready(Ok(()))
}
}
fn app() -> RustStream {
let shard = 7;
RustStream::new(AppInfo::new("subscribers", "0.1.0")).with_broker(MemoryBroker::new(), |b| {
b.include(subscriber("orders", Receive).build());
b.include(subscriber("orders", WithContext).build());
b.include(
subscriber(Unnamed::<Name>::new(), Audit)
.name(format!("audit-{shard}"))
.build(),
);
b.include(
subscriber(Unnamed::<MemorySource>::new(), Archive)
.name("archive")
.build(),
);
b.include(subscriber("frames", Ingest).batch(nonzero!(32)).build());
b.include(subscriber("orders", Drain).batch(nonzero!(128)).build());
b.include(
subscriber("orders", Bill)
.workers(nonzero!(4))
.on_failure(FailurePolicies::default().with_decode(FailurePolicy::Skip))
.build(),
);
b.include_router(fan_out_routes());
b.include_router(per_customer_routes());
b.include_router(batch_routes());
})
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
app().run().await?;
Ok(())
}