Skip to main content

eventide_application/
query_bus.rs

1use crate::{context::AppContext, error::AppError};
2use async_trait::async_trait;
3
4/// Query Bus abstraction for the CQRS read side.
5///
6/// The query bus routes a strongly typed query `Q` to the
7/// [`crate::query_handler::QueryHandler`] registered for that `(Q, R)` pair
8/// and returns the produced result `R` to the caller. It is the symmetric
9/// counterpart of [`crate::command_bus::CommandBus`] and lives on the read
10/// path: handlers should consult read models / projections rather than
11/// mutate aggregates.
12///
13/// # Implementation notes
14///
15/// - Implementations may target in-process dispatch (see
16///   [`crate::InMemoryQueryBus`]) or cross-process query routing.
17/// - The same query type may produce different result types — the
18///   `(query type, result type)` pair forms the lookup key in the in-memory
19///   implementation, allowing multiple read models to coexist.
20/// - Implementations must be `Send + Sync`.
21///
22/// # Errors
23///
24/// Methods return [`AppError`]. Typical failure modes include
25/// `HANDLER_NOT_FOUND` when nothing is registered for the requested
26/// `(Q, R)` pair, `TYPE_MISMATCH` when the result returned by the handler
27/// cannot be downcast to `R`, and any error surfaced by the handler itself.
28#[async_trait]
29pub trait QueryBus: Send + Sync {
30    /// Dispatch a query and return its associated result.
31    ///
32    /// The bus locates the handler for the concrete `(Q, R)` pair, hands
33    /// `q` to it, and forwards the produced `R` value to the caller.
34    ///
35    /// # Errors
36    ///
37    /// Returns [`AppError`] when no handler is registered for `(Q, R)`,
38    /// the handler fails, or the produced value cannot be coerced back
39    /// into the requested result type.
40    async fn dispatch<Q, R>(&self, ctx: &AppContext, q: Q) -> Result<R, AppError>
41    where
42        Q: Send + 'static,
43        R: Send + 'static;
44
45    /// Dispatch a batch of queries in order, returning a parallel vector of
46    /// results.
47    ///
48    /// The default implementation iterates sequentially and short-circuits
49    /// on the first error. Implementations may override this to issue
50    /// concurrent reads or coalesce identical queries.
51    ///
52    /// # Errors
53    ///
54    /// Returns the first [`AppError`] produced by any inner dispatch.
55    async fn dispatch_batch<Q, R>(
56        &self,
57        ctx: &AppContext,
58        queries: Vec<Q>,
59    ) -> Result<Vec<R>, AppError>
60    where
61        Q: Send + 'static,
62        R: Send + 'static,
63    {
64        let mut out = Vec::with_capacity(queries.len());
65        for q in queries {
66            out.push(self.dispatch::<Q, R>(ctx, q).await?);
67        }
68        Ok(out)
69    }
70}