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}