backbone_core/query.rs
1//! CQRS Query Pattern
2//!
3//! Provides traits for implementing the Query side of CQRS.
4//! Queries represent requests for information without side effects.
5//!
6//! # Example
7//!
8//! ```ignore
9//! use backbone_core::{Query, QueryHandler};
10//!
11//! // Define a query
12//! pub struct GetUserByEmailQuery {
13//! pub email: String,
14//! }
15//!
16//! impl Query for GetUserByEmailQuery {
17//! type Result = Option<UserDto>;
18//! }
19//!
20//! // Implement the handler
21//! pub struct GetUserByEmailHandler {
22//! read_model: Arc<dyn UserReadModel>,
23//! }
24//!
25//! #[async_trait::async_trait]
26//! impl QueryHandler<GetUserByEmailQuery> for GetUserByEmailHandler {
27//! type Error = QueryError;
28//!
29//! async fn handle(&self, query: GetUserByEmailQuery) -> Result<Option<UserDto>, Self::Error> {
30//! self.read_model.find_by_email(&query.email).await
31//! }
32//! }
33//! ```
34
35use async_trait::async_trait;
36
37/// Marker trait for CQRS queries.
38///
39/// Queries represent requests for information and should not
40/// cause any side effects in the system.
41pub trait Query: Send + Sync {
42 /// The result type returned by the query.
43 type Result: Send + Sync;
44}
45
46/// Handler for executing queries.
47///
48/// Query handlers retrieve data from read models and should
49/// be optimized for read performance.
50#[async_trait]
51pub trait QueryHandler<Q: Query>: Send + Sync {
52 /// Error type for query execution failures.
53 type Error: std::error::Error + Send + Sync;
54
55 /// Execute the query and return the result.
56 async fn handle(&self, query: Q) -> Result<Q::Result, Self::Error>;
57}
58
59/// Query dispatcher for routing queries to their handlers.
60///
61/// Provides a central point for query execution with
62/// optional caching and middleware support.
63#[async_trait]
64pub trait QueryDispatcher: Send + Sync {
65 /// Dispatch a query to its handler.
66 async fn dispatch<Q: Query>(
67 &self,
68 query: Q,
69 ) -> Result<Q::Result, Box<dyn std::error::Error + Send + Sync>>;
70}
71
72/// Trait for queries that support caching.
73pub trait CacheableQuery: Query {
74 /// Cache key for this query.
75 fn cache_key(&self) -> String;
76
77 /// Time-to-live for cached results in seconds.
78 fn cache_ttl(&self) -> Option<u64> {
79 None // No caching by default
80 }
81}
82
83/// Trait for paginated queries.
84pub trait PaginatedQuery: Query {
85 /// Get the page number (1-based).
86 fn page(&self) -> u32;
87
88 /// Get the page size.
89 fn page_size(&self) -> u32;
90
91 /// Get the offset for database queries.
92 fn offset(&self) -> u32 {
93 (self.page().saturating_sub(1)) * self.page_size()
94 }
95}
96
97/// Result wrapper for paginated queries.
98#[derive(Debug, Clone)]
99pub struct PaginatedQueryResult<T> {
100 /// The items for the current page.
101 pub items: Vec<T>,
102 /// Total number of items across all pages.
103 pub total: u64,
104 /// Current page number (1-based).
105 pub page: u32,
106 /// Number of items per page.
107 pub page_size: u32,
108 /// Total number of pages.
109 pub total_pages: u32,
110}
111
112impl<T> PaginatedQueryResult<T> {
113 /// Create a new paginated result.
114 pub fn new(items: Vec<T>, total: u64, page: u32, page_size: u32) -> Self {
115 let total_pages = if page_size > 0 {
116 ((total as f64) / (page_size as f64)).ceil() as u32
117 } else {
118 0
119 };
120
121 Self {
122 items,
123 total,
124 page,
125 page_size,
126 total_pages,
127 }
128 }
129
130 /// Check if there's a next page.
131 pub fn has_next(&self) -> bool {
132 self.page < self.total_pages
133 }
134
135 /// Check if there's a previous page.
136 pub fn has_previous(&self) -> bool {
137 self.page > 1
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 struct TestQuery {
146 id: String,
147 }
148
149 impl Query for TestQuery {
150 type Result = String;
151 }
152
153 struct TestHandler;
154
155 #[async_trait]
156 impl QueryHandler<TestQuery> for TestHandler {
157 type Error = std::io::Error;
158
159 async fn handle(&self, query: TestQuery) -> Result<String, Self::Error> {
160 Ok(format!("Result for {}", query.id))
161 }
162 }
163
164 #[tokio::test]
165 async fn test_query_handler() {
166 let handler = TestHandler;
167 let query = TestQuery {
168 id: "123".to_string(),
169 };
170 let result = handler.handle(query).await.unwrap();
171 assert_eq!(result, "Result for 123");
172 }
173
174 #[test]
175 fn test_paginated_result() {
176 let result: PaginatedQueryResult<i32> = PaginatedQueryResult::new(vec![1, 2, 3], 10, 1, 3);
177
178 assert_eq!(result.total_pages, 4);
179 assert!(result.has_next());
180 assert!(!result.has_previous());
181 }
182}