Эта страница переведена с английского языковой моделью. При любом расхождении верен английский оригинал.
Разделяемое состояние и контекст
Платёжный и почтовый клиенты создаются один раз при запуске и доступны подписчикам через State. Контекст сообщает подписчику, какую доставку он обрабатывает.
Подписчики сервиса совместно используют клиенты, пулы и кеши.
Приложение создаёт их в on_startup до первой подписки, а ошибка в этом хуке прерывает запуск.
Значение, которое возвращает хук, - это состояние приложения, одно на весь сервис.
Подписчик получает одно поле состояния через параметр State<T>.
Контекст - часть, своя для каждой доставки: имя подписки, заголовки, поля брокера для этой доставки.
Подписчик получает его через параметр &mut Context сразу после сообщения.
Состояние
Shop содержит платёжный клиент и почтовый клиент, каждый за Arc, поэтому копирование состояния обходится дёшево.
FromRef извлекает из состояния каждое поле: через него разрешается State<Arc<Gateway>>.
open - это хук, который приложение передаёт в on_startup в главе 5.
- Макросы
- Вручную
/// 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?),
})
}
}
/// The app's state: each field reaches a subscriber as `State<FieldType>`.
#[derive(Clone)]
pub(crate) struct Shop {
pub(crate) gateway: Arc<Gateway>,
pub(crate) mailer: Arc<Mailer>,
}
// What `#[derive(FromRef)]` writes for each field: `State<Arc<Gateway>>` resolves through it.
impl FromRef<Shop> for Arc<Gateway> {
fn from_ref(shop: &Shop) -> Self {
shop.gateway.clone()
}
}
impl FromRef<Shop> for Arc<Mailer> {
fn from_ref(shop: &Shop) -> Self {
shop.mailer.clone()
}
}
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 отправляет уведомление по электронной почте через почтовый клиент из состояния.
Несуществующий почтовый ящик не появится при повторной попытке, поэтому отклонённое уведомление отбрасывается, а предупреждение называет подписку из контекста.
- Макросы
- Вручную
/// 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(¬ice.customer, ¬ice.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()
}
}
}
/// E-mails a notice to the customer.
struct Mail;
// The mailer comes from the app's state; the context names the subscription in the warning.
impl Handle<Notice, (), (), (), Shop> for Mail {
async fn handle(
&self,
notice: &Notice,
_outs: &(),
ctx: &mut Context<'_, (), Shop>,
) -> Result<(), HandlerOutcome> {
let State(mailer) = State::<Arc<Mailer>>::from_context(ctx).await?;
if let Err(error) = mailer.send(¬ice.customer, ¬ice.text).await {
// A refused mailbox stays refused; another try would only repeat the warning.
tracing::warn!(subscription = ctx.name(), %error, "notice not sent");
return Err(HandlerOutcome::drop());
}
Ok(())
}
}
В ручной форме тип состояния - последний параметр Handle, и монтирование сверяет его с типом состояния приложения.
Тело читает поле через from_context. Макрос делает то же самое для параметра State.
Собственные экстракторы - это ваши типы, которые читаются тем же способом.