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

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

Разделяемое состояние и контекст

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

Подписчики сервиса совместно используют клиенты, пулы и кеши. Приложение создаёт их в on_startup до первой подписки, а ошибка в этом хуке прерывает запуск. Значение, которое возвращает хук, - это состояние приложения, одно на весь сервис. Подписчик получает одно поле состояния через параметр State<T>. Контекст - часть, своя для каждой доставки: имя подписки, заголовки, поля брокера для этой доставки. Подписчик получает его через параметр &mut Context сразу после сообщения.

Состояние​

Shop содержит платёжный клиент и почтовый клиент, каждый за Arc, поэтому копирование состояния обходится дёшево. FromRef извлекает из состояния каждое поле: через него разрешается State<Arc<Gateway>>. open - это хук, который приложение передаёт в on_startup в главе 5.

state.rs
/// The app's state: each field reaches a subscriber as `State<FieldType>`.
#[derive(Clone, FromRef)]
pub(crate) struct Shop {
pub(crate) gateway: Arc<Gateway>,
pub(crate) mailer: Arc<Mailer>,
}

impl Shop {
/// Opens the clients. The app runs it once, before the first subscription opens; an error
/// stops the startup.
pub(crate) async fn open() -> Result<Self, MailError> {
Ok(Self {
gateway: Arc::new(Gateway::new(CARD_LIMIT_CENTS, CHARGES_PER_SECOND)),
mailer: Arc::new(Mailer::connect("smtp://mail.internal:25").await?),
})
}
}

Ручная форма содержит две реализации, которые генерирует derive-макрос, по одной на поле.

Состояние и контекст в одном подписчике​

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

notifications.rs
/// E-mails a notice to the customer.
// The mailer comes from the app's state; the context names the subscription in the warning.
#[subscriber("notices")]
async fn send(
notice: &Notice,
ctx: &mut Context<'_, (), Shop>,
State(mailer): State<Arc<Mailer>>,
) -> HandlerOutcome {
match mailer.send(&notice.customer, &notice.text).await {
Ok(()) => HandlerOutcome::ack(),
Err(error) => {
// A refused mailbox stays refused; another try would only repeat the warning.
tracing::warn!(subscription = ctx.name(), %error, "notice not sent");
HandlerOutcome::drop()
}
}
}

В ручной форме тип состояния - последний параметр Handle, и монтирование сверяет его с типом состояния приложения. Тело читает поле через from_context. Макрос делает то же самое для параметра State. Собственные экстракторы - это ваши типы, которые читаются тем же способом.