Перейти к основному содержимому
Эта страница ещё не переведена

Ниже показан английский оригинал.

Repository

A publish writes a row through the row's Publish, the service's own INSERT; Repository names the table at compile time, a static call per publish.

Publish<DB> is the statement of a queue table that takes a published message. Its publish(conn, message) receives the message's name, its encoded bytes and its headers, and lays them out in the table's columns. In a table with a group column, the name usually goes there, so a subscription of that group reads the row. The trait has no default: a row without it is not a publisher, and a mount that names it does not compile.

Repository::<Row>::default() is the publish policy of Row's table. A mount site names it wherever a registration publishes: .out_reply(Repository::<Job>::default()) for a reply, .out(Slot, Repository::<Job>::default()) for an Out slot. The runtime pairs it with the connected broker at startup into a RepositoryPublisher. A publisher outside a handler, such as an HTTP endpoint's, comes from SqlxBroker::new(pool).bindable(), bind(Repository::<Job>::default()) and running.publisher(..).

Each publish takes a connection of the pool, runs the row's Publish on it and commits on its own. A handler's transaction does not cover it: a row that must commit with the handler's writes goes in through Insert on the handler's Tx. A message whose header the table cannot hold byte for byte is refused before the statement runs, with SqlxBrokerError::Header; a headers column holds them. A failed statement returns SqlxBrokerError::Publish, which names the message's name, the table and the row type. After shutdown a publisher answers SqlxBrokerError::Closed.

A publish through a repository wakes the subscriptions of its table and group in the same process at once. A name the service does not know at compile time goes through a route instead: the broker's default publisher looks the name up.

Example​

An order's confirmation becomes a row of jobs in the group emails, and the second subscription of the service reads it.

use ruststream::OutgoingMessage;
use sqlx::{Sqlite, SqliteConnection};

/// One row of `jobs`: the group column holds the name a message was published to.
#[derive(Inbox, sqlx::FromRow)]
#[inbox(table = "jobs")]
struct Job {
#[field(id, generated)]
id: i64,
#[field(group)]
name: String,
#[field(locked_until)]
locked_until: Option<DateTime<Utc>>,
#[field(payload)]
payload: Vec<u8>,
}

/// The service's own insert: the name goes to the group column, the bytes to the payload.
impl Publish<Sqlite> for Job {
async fn publish(
conn: &mut SqliteConnection,
message: &OutgoingMessage<'_>,
) -> Result<(), sqlx::Error> {
sqlx::query("INSERT INTO jobs (name, payload) VALUES (?, ?)")
.bind(message.name())
.bind(message.payload())
.execute(conn)
.await?;
Ok(())
}
}

#[subscriber(InboxQueue::<Job>::new("orders"), reply)]
async fn confirm(order: &Order) -> Confirmation {
Confirmation { order: order.id }
}

#[subscriber(InboxQueue::<Job>::new("emails"))]
async fn email(confirmation: &Confirmation) -> HandlerOutcome {
tracing::info!(order = confirmation.order, "emailing the confirmation");
HandlerOutcome::ack()
}

/// The service on the database `url` names.
fn service(url: &str) -> RustStream {
let schema_url = url.to_owned();
// Nothing connects here: the broker builds its pool when the service starts.
let broker = SqlxBroker::from_url(url, SqlitePoolOptions::new());
RustStream::new(AppInfo::new("orders", "0.1.0"))
// The table, created before the subscription checks it.
.on_startup(async move |()| {
let mut conn = SqliteConnection::connect(&schema_url).await?;
sqlx::raw_sql(SCHEMA).execute(&mut conn).await?;
conn.close().await
})
.with_broker(broker.route::<Job>("orders"), |b| {
// The confirmation becomes a row of `jobs` in the group `emails`.
b.include(confirm).out_reply(Repository::<Job>::default());
b.include(email);
})
}