MongoDB
Connect typed collections to DI and use operation contexts that preserve database timeouts.
In the default single mode, lily_mongodb registers Singleton DatabaseService. It connects and performs a readiness ping at application startup. The main access flow is controller → domain service → typed collection.
Dependencies and connection#
A running MongoDB instance is required. Add the mongodb feature to the lilyrs dependency from the first application; it selects single mode. The example uses these dependencies:
lilyrs = { version = "0.1.0", features = ["http-api", "mongodb"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread", "time"] }The serde derive dependency comes from the first application. MongoFilter, MongoRepositoryError, and operation context types also come from lilyrs::mongodb; no separate repository helper crate is required. BSON types come from the same facade. The Mongo derives resolve their helper paths; this example needs no separate driver, async-trait, or futures dependency.
[database]
mode = "single"
database_type = "mongodb"
connection_string = "mongodb://127.0.0.1:27017"
database_name = "lily"
query_timeout_secs = 10Typed collection and domain service#
use std::sync::Arc;
use lilyrs::http_api::Injectable;
use lilyrs::http_api::{CancellationToken, ExecutionCancellation, ServiceTrait};
use lilyrs::mongodb::{Collection, DatabaseService, MongoCollection, Repository};
use lilyrs::mongodb::{MongoFilter, MongoRepositoryError};
use lilyrs::mongodb::bson::{doc, oid::ObjectId};
use serde::{Deserialize, Serialize};
#[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 itself.
#[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 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,
}
}
}
// Add this if you choose a shared repository contract.
#[derive(Default, Injectable, Repository)]
#[service(lifetime = "Singleton")]
#[entity_type(User)]
#[collection_type(UserCollection)]
struct UserRepository {
#[inject]
collection: Arc<UserCollection>,
}
impl ServiceTrait for UserRepository {}Resolve Service<UserService> in the action and pass the request's ExecutionCancellation to find_by_name. Convert the entity into an HTTP DTO; storage types do not have to become your public API model directly.
Derive responsibilities#
MongoCollection generates both typed CRUD and the ServiceTrait implementation. Do not write a second ServiceTrait implementation. The optional Repository derive generates the common repository contract; it still requires Injectable and ServiceTrait.
MongoFilter::new rejects an empty filter. Use explicit MongoFilter::all to target every record. Multi-record reads are bounded by MongoPageRequest. Generated updates replace the entire entity; they are not PATCH operations. Use with_expected_revision for optimistic concurrency when needed.
Timeouts and cancellation#
DatabaseService::operation_context(token) preserves the configured query timeout. MongoOperationContext::detached() has no deadline. Use a configured context and the example's explicit cancellation bridge on the main HTTP path.
Factory mode#
In the facade, replace mongodb with mongodb-factory: features = ["http-api", "mongodb-factory"]. The base mongodb feature enables single mode and cannot coexist with factory mode in the same dependency graph. The collection injects Arc<MongoFactory> and chooses a named cell with #[cell_name("...")]. The db: Arc<DatabaseService> field filled by the generated lifecycle is not injected. Single and factory modes cannot be enabled together.
Sources for this page v0.1.0
This guide is based on the reviewed framework commit.