MongoDB and cache
Use MongoDB collections and cache services through DI, and explicitly connect their operation contexts to message cancellation.
MongoDB and cache services use the shared DI infrastructure. The WebSocket-specific part connects their operation contexts to the message execution signal. Do not assume a connection-long transaction or an unbounded operation.
MongoDB setup#
Update the first application's lilyrs feature list and add the other dependencies:
lilyrs = { version = "0.1.0", features = ["websocket", "mongodb"] }
tokio-util = "0.7"mongodb selects single mode; for named databases, use mongodb-factory instead. lilyrs::mongodb re-exports MongoFilter and MongoRepositoryError, so no separate repository crate dependency is needed. Keep Serde and Tokio as in the first application. Macro helpers are resolved through the facade. This example also imports BSON types from lilyrs::mongodb::bson; no separate mongodb, async-trait, or futures dependency is required solely for generated code. tokio-util stays in the manifest because the application uses it directly.
[database]
mode = "single"
database_type = "mongodb"
connection_string = "mongodb://127.0.0.1:27017"
database_name = "lily"
query_timeout_secs = 10The MongoDB section is [database], not [mongodb]. [postgresql] and [cache] are separate sections. Use your own connection, TLS, and secret settings in a live environment.
Typed collection and cancellation bridge#
use std::sync::Arc;
use lilyrs::websocket::Injectable;
use lilyrs::websocket::ServiceTrait;
use lilyrs::websocket::ExecutionCancellation;
use lilyrs::mongodb::{Collection, DatabaseService, MongoCollection};
use lilyrs::mongodb::{MongoFilter, MongoRepositoryError};
use lilyrs::mongodb::bson::{doc, oid::ObjectId};
use serde::{Deserialize, Serialize};
use tokio_util::sync::CancellationToken;
#[derive(Clone, Serialize, Deserialize)]
struct User {
#[serde(skip_serializing_if = "Option::is_none")]
_id: Option<ObjectId>,
name: String,
}
#[derive(Default, Injectable, MongoCollection)]
#[service(lifetime = "Singleton")]
#[collection("users")]
#[collection_type(User)]
struct UserCollection {
#[inject]
db: Arc<DatabaseService>,
collection: Option<Collection<User>>,
}
// MongoCollection generates the ServiceTrait implementation.
#[derive(Injectable)]
#[service(lifetime = "Singleton")]
struct UserService {
#[inject]
database: Arc<DatabaseService>,
#[inject]
users: Arc<UserCollection>,
}
impl ServiceTrait for UserService {}
impl UserService {
async fn find_by_name(&self, name: &str, stopping: ExecutionCancellation)
-> Result<Option<User>, MongoRepositoryError>
{
let token = CancellationToken::new();
let _cancel_on_drop = token.clone().drop_guard();
let operation = self.database.operation_context(token.clone())?;
let filter = MongoFilter::new(doc! { "name": name })?;
let work = self.users.find_one(filter, &operation);
tokio::pin!(work);
tokio::select! {
biased;
_ = stopping.cancelled() => { token.cancel(); work.await }
result = &mut work => result,
}
}
}Download the complete example.
MongoCollection generates ServiceTrait; do not implement it again for the same type. The struct also derives Default because the collection runtime field is not injected. Call the example's private service method from an action in the same module using Service<UserService> and ExecutionCancellation; deliberately adjust visibility if exposing it to another module.
Operation ownership#
database.operation_context(token) uses the configured query timeout. The example creates an application-owned CancellationToken, observes the WS signal, cancels that token, and awaits the operation within the same invocation. A token drop guard handles early drop; no raw WS token is extracted and no detached worker is started.
Use with_deadline when needed to select a shorter remaining message duration. Account for repeated calls changing the timeout on the same context. A DI scope alone does not start a Mongo transaction; explicit begin, context attachment, and commit/abort are required. The transaction/migration guide explains the shared data layer.
Cache usage#
[cache]
mode = "single"
provider = "redis"
redis_url = "redis://127.0.0.1:6379/0"
key_namespace = "chat-api"
operation_timeout_secs = 2The rate limiter page has a complete example of the redis feature on lilyrs, lilyrs::redis imports, and CacheOperationContext bridge. redis selects single mode for one service; use redis-factory instead for named connections. When combining it with Mongo, add the selected Redis profile to the same lilyrs feature list. The [cache] configuration name remains unchanged. Cache Redis and backplane Redis have separate clients and lifecycle owners; connecting to the same server does not mean they share a pool.
Compilation of these examples was verified. Live Mongo replica sets, transactions, migrations, and Redis data operations must be tested separately in your integration environment.
Sources for this page v0.1.0
This guide is based on the reviewed framework commit.