跳到主要内容
英文版本为准

本页译自英文,由模型翻译。若与英文原文存在差异,以英文原文为准。

回复与发布到其他主题

accept 用一个支付请求回复每个订单,并通过 Out 槽位通知顾客。路由器决定两者如何发出。

订阅者有两种发布方式。 回复是主体返回的值:主体成功之后,运行时对它编码并发布。 Out 槽位是一个发布者。主体把它作为参数接收,并自己调用,需要多少次就调用多少次。 消息发往哪里是消息类型的属性:#[outgoing(name = ..)] 固定主题,主体和挂载点都不必重复它。 槽位列出可以经由它发出的类型,发布其他任何类型都无法编译。 挂载点把每个发布位置绑定到一个发布策略,这里是 Broker 的普通 Publish。

消息​

支付模块接收 PaymentRequest,Paid 是它在下一章给出的回复。

payments.rs
/// What the orders side asks payments to charge.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema, Outgoing)]
#[outgoing(name = "payments.requested")]
pub(crate) struct PaymentRequest {
pub(crate) order: u64,
pub(crate) customer: String,
pub(crate) lines: Vec<Line>,
pub(crate) amount_cents: u64,
pub(crate) request_id: String,
}

/// A charged order, on its way to shipping.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema, Outgoing)]
#[outgoing(name = "payments.settled")]
pub(crate) struct Paid {
pub(crate) order: u64,
pub(crate) customer: String,
pub(crate) lines: Vec<Line>,
}

Notice 是商店发给顾客的通知,OutSlot 标记 Notices 是它发出所经由的槽位。 手写形式写出 #[derive(OutSlot)] 生成的内容:槽位的名字、它发布的类型,以及这些类型在 AsyncAPI 文档中的条目。

notifications.rs
/// What the shop tells a customer.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema, Outgoing)]
#[outgoing(name = "notices")]
pub(crate) struct Notice {
pub(crate) customer: String,
pub(crate) text: String,
}

/// The slot a subscriber tells customers through, and what may leave through it.
#[derive(OutSlot)]
#[publishes(Notice)]
pub(crate) struct Notices;

订阅者​

没有订单行的订单,accept 直接丢弃,因为没有可收款的内容;超出商店上限的订单也会丢弃,因为它没有总额。 它通过槽位告诉顾客订单已收到,如果这次发布失败,就请求重试。 然后它返回支付请求,运行时把它作为回复发布。 Ok 发布回复并 ack,Err 不发布任何内容,按结果结算。

orders.rs
/// Accepts an order: asks payments to charge it, and tells the customer it arrived.
#[subscriber("orders.placed", reply)]
async fn accept(
order: &Order,
Headers(meta): Headers<RequestMeta>,
Out(notices): Out<impl Publisher, Notices>,
) -> Result<PaymentRequest, HandlerOutcome> {
if order.lines.is_empty() {
tracing::warn!(order = order.id, "an order with no lines, dropped");
return Err(HandlerOutcome::drop());
}
let Some(amount_cents) = total_cents(&order.lines) else {
tracing::warn!(order = order.id, "an order past the shop's bounds, dropped");
return Err(HandlerOutcome::drop());
};
let notice = Notice {
customer: order.customer.clone(),
text: format!("We have your order {}.", order.id),
};
if notices.message(&notice).publish().await.is_err() {
return Err(HandlerOutcome::retry());
}
Ok(PaymentRequest {
order: order.id,
customer: order.customer.clone(),
lines: order.lines.clone(),
amount_cents,
request_id: meta.request_id,
})
}

在手写形式中,槽位是 Outs 中的一个条目,outs.get(Notices) 交出它的发布者。

路由器​

Router 汇集一个模块的订阅者,以及它们各个发布位置的设置。 out_reply 把回复绑定到一个策略,.codec(INTERNAL) 用 CBOR 对它编码。 out 绑定槽位。槽位沿用路由器的编解码器,这里是 JSON。 路由器在 RouterDef<MemoryBroker, Shop> 中指明 Broker 和应用的状态类型,Shop 在第 7 章介绍。

orders.rs
/// The orders side's subscribers, with where their messages go.
pub(crate) fn routes() -> impl RouterDef<MemoryBroker, Shop> {
Router::new()
.include(audit)
.include(accept)
// The payment request travels as CBOR, the codec of the shop's own topics.
.out_reply(Publish)
.codec(INTERNAL)
.out(Notices, Publish)
.build()
}

手写形式用 subscriber(..) 把每个订阅者绑定到它的主题,.describe(..) 给它描述,相当于宏从文档注释得到的描述。 include_router 把路由器挂载到 Broker 上,第 5 章展示完成这一步的应用。