Перейти к основному содержимому
Нормативная версия документации - английская

Эта страница переведена с английского языковой моделью. При любом расхождении верен английский оригинал.

Ответы и публикация в другие темы

accept отвечает на каждый заказ запросом оплаты и уведомляет покупателя через слот Out. Роутер задаёт, как публикуются оба сообщения.

Подписчик публикует сообщения двумя способами. Ответ - это значение, которое возвращает тело обработчика. Рантайм кодирует его и публикует после успешного завершения тела. Слот Out - это издатель, которого тело получает параметром и вызывает само, столько раз, сколько нужно. Адрес сообщения - свойство его типа: #[outgoing(name = ..)] задаёт тему, поэтому ни тело, ни точка монтирования её не повторяют. Слот перечисляет типы, которые через него публикуются, поэтому публикация любого другого типа не компилируется. Точка монтирования связывает каждую позицию с политикой публикации, здесь это обычная политика брокера 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 публикует ответ и подтверждает сообщение, 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>. Тип 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(..) задаёт описание, которое макросу даёт doc-комментарий. include_router монтирует роутер на брокере. Приложение, которое это делает, показывает глава 5.