ruststream-sqlx connects a RustStream service to SQL databases over
sqlx. Handlers, routing, codecs and middleware come from the
framework; this crate brings two components: the inbox, a broker that serves task queues from the
service's own tables, and the transactional outbox, which tracks what a service publishes on any
RustStream broker.
Features
- Task queues in the service's own tables on Postgres, MySQL 8.0.1 and later, MariaDB 10.6
and later, and SQLite, or through an
AnyPool. - Three claim forms: a row lock held in a transaction while the handler runs, a lease committed at once and extended while the handler works, or an advisory lock on the row's key. SQLite tables take the lease or the advisory lock form.
- Transactional mode:
.transactional()lets a handler write through its delivery's transaction, and the acknowledgement commits those writes together with the row. - Column roles that each turn on one behaviour: groups, FIFO keys, delayed retries, attempt caps with dead letters, a processed mark, and the database's clock in place of the host's.
- Row mode, headers layouts and batches: a handler takes the decoded payload, the row itself, a struct joined from other tables, or the rows of one claim as a slice.
- A derive or the manual API:
#[derive(Inbox)]or a trait with a typed builder, checked as strictly by the compiler and running the same statements. - SQL checked at compile time:
checkedhands the derive's statements to sqlx's macros, socargo sqlx preparechecks them with the service's own queries. - The service's own SQL for any queue event, and a dialect of its own for another database.
- Waking on publish: a publish wakes the subscriptions of its table in the same process, and
LISTEN/NOTIFYon Postgres wakes them from other processes. - The transactional outbox over any RustStream broker:
#[derive(Outbox)]describes the service's outbox table, andoutbox!registers it under the names it tracks. A publish middleware records each tracked message before it is sent, with the record's id in a header. A subscription middleware takes the record into work by that id and marks it processed when the handler acknowledges. Unprocessed records are published again at startup, so each message is delivered at least once. - Tests on the production app:
TestAppruns the service's own app against a real database; SQLite needs no server.
Inbox or outbox
The inbox fits work that belongs to the service's data: a task written in the same transaction as the data it serves, a queue in the database the service already runs. The outbox fits messages a service publishes on another broker and must not lose. A service may use both: the outbox's middlewares wrap inbox handlers as they wrap any other.
Install
[]
= { = "0.7", = ["macros", "json"] }
= { = "0.7", = ["inbox", "outbox", "postgres"] }
= { = "0.9", = ["runtime-tokio", "postgres", "derive", "chrono"] }
= "0.4"
= { = "1", = ["derive"] }
= { = "1", = ["rt-multi-thread", "macros"] }
[]
= { = "0.7", = ["testing"] }
inbox and outbox turn the components on, each independently of the other. A driver feature
picks the database: postgres, mysql (MySQL and MariaDB), sqlite, or any. Optional
features: chrono or time for time columns, json for a headers column, and asyncapi.
Write a service
A queue in a Postgres table, served by the inbox:
use Error;
use OutgoingMessage;
use *;
use ;
use ;
async
async
sqlx builds a pool only inside a Tokio runtime, so main builds it and hands it to the app.
Messages published on any broker, tracked by the outbox:
use Infallible;
use ;
use OutgoingMessage;
use *;
use ;
use ;
use ;
async
async
#[ruststream::app] generates main and builds the app before the runtime starts, so the outbox
starts without a pool and on_startup hands it one with set_pool. Both services are in
crates/ruststream-sqlx/examples, and examples/outbox
runs the outbox over Redis Pub/Sub.
Test it
TestApp runs the service's own app against a real database, because the service's SQL is part
of what the test checks.
use TestApp;
let pool = connect.await?;
let tb = start_live.await?;
tb.
.message
.to
.publish
.await?;
tb.
.subscriber
.assert_called_once
.settled;
A test build leaves the outbox off, so a test needs no outbox table;
RUSTSTREAM_SQLX_OUTBOX=on in the environment turns it on.
Documentation
- This crate: https://docs.rs/ruststream-sqlx
- The site, with the benchmarks: https://powersemmi.github.io/ruststream-sqlx/
- The framework: https://powersemmi.github.io/ruststream/latest
Minimum supported Rust version
The MSRV is 1.95, edition 2024.
Contributing
See CONTRIBUTING.md.
License
Licensed under the Apache-2.0 license.