d_engine_client/lib.rs
1//! # d-engine-client
2//!
3//! Client library for interacting with d-engine Raft clusters via gRPC
4//!
5//! ## ⚠️ You Probably Don't Need This Crate
6//!
7//! **Use [`d-engine`](https://crates.io/crates/d-engine) instead:**
8//!
9//! ```toml
10//! [dependencies]
11//! d-engine = { version = "0.2", features = ["client"] }
12//! ```
13//!
14//! This provides the same API with simpler dependency management. The `d-engine-client` crate
15//! is automatically included when you enable the `client` feature.
16//!
17//! ## For Contributors
18//!
19//! This crate exists for architectural reasons:
20//! - Clean boundaries between client and server
21//! - Faster builds during development
22//! - Isolated client testing
23//!
24//! ## Quick Start
25//!
26//! ```rust,ignore
27//! use d_engine_client::Client;
28//!
29//! #[tokio::main]
30//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
31//! let client = Client::connect(vec!["http://localhost:50051"]).await?;
32//!
33//! // Write data
34//! client.put(b"key".to_vec(), b"value".to_vec()).await?;
35//!
36//! // Read data
37//! if let Some(value) = client.get(b"key".to_vec()).await? {
38//! println!("Value: {:?}", value);
39//! }
40//!
41//! Ok(())
42//! }
43//! ```
44//!
45//! ## Read Consistency
46//!
47//! Choose consistency level based on your needs:
48//!
49//! - `get_linearizable()` - Strong consistency (read from Leader)
50//! - `get_eventual()` - Fast local reads (stale OK)
51//! - `get_lease()` - Optimized with leader lease
52//!
53//! ## Features
54//!
55//! This crate provides:
56//! - [`Client`] - Main entry point; derefs to [`GrpcClient`]
57//! - [`ClientBuilder`] - Configurable client construction
58//! - [`ClientApi`] - Unified client operations trait (put/get/delete/CAS/watch)
59//!
60//! ## Documentation
61//!
62//! For comprehensive guides:
63//! - [Read Consistency](https://docs.rs/d-engine/latest/d_engine/docs/client_guide/read_consistency/index.html)
64//! - [Error Handling](https://docs.rs/d-engine/latest/d_engine/docs/client_guide/error_handling/index.html)
65
66mod builder;
67mod config;
68mod grpc_client;
69mod pool;
70mod proto;
71mod scoped_timer;
72mod utils;
73
74pub use builder::*;
75pub use config::*;
76pub use d_engine_core::client::{ClientApi, ClientApiError, ClientApiResult};
77pub use grpc_client::*;
78pub use pool::*;
79pub use utils::*;
80
81// ==================== Protocol Types (Essential for Public API) ====================
82
83/// Protocol types needed for client operations
84///
85/// These types are used in the public API and must be imported for client usage:
86/// - `ClientResult`: Response type from read operations
87/// - `ReadConsistencyPolicy`: Consistency guarantees for reads
88/// - `WriteCommand`: Write operation specifications
89pub mod protocol {
90 pub use d_engine_proto::client::ClientResult;
91 pub use d_engine_proto::client::ReadConsistencyPolicy;
92 pub use d_engine_proto::client::WatchEventType;
93 pub use d_engine_proto::client::WatchRequest;
94 pub use d_engine_proto::client::WatchResponse;
95 pub use d_engine_proto::client::WriteCommand;
96}
97
98/// Cluster management protocol types
99///
100/// Types required for cluster administration operations:
101/// - `NodeMeta`: Cluster node metadata
102/// - `NodeStatus`: Node status enumeration
103pub mod cluster_types {
104 pub use d_engine_proto::common::NodeStatus;
105 pub use d_engine_proto::server::cluster::NodeMeta;
106}
107
108// ==================== Hide Implementation Details ====================
109pub(crate) use proto::*;
110
111#[cfg(test)]
112mod error_test;
113#[cfg(test)]
114mod grpc_client_test;
115#[cfg(test)]
116mod mock_rpc;
117#[cfg(test)]
118mod mock_rpc_service;
119#[cfg(test)]
120mod pool_test;
121#[cfg(test)]
122mod utils_test;
123
124/// Main entry point for interacting with the d-engine cluster.
125///
126/// Derefs to [`GrpcClient`], which implements [`ClientApi`] for all KV operations.
127/// Created through [`ClientBuilder`].
128#[derive(Clone)]
129pub struct Client {
130 inner: std::sync::Arc<GrpcClient>,
131}
132
133#[derive(Clone)]
134pub struct ClientInner {
135 pool: ConnectionPool,
136 client_id: u32,
137 config: ClientConfig,
138 endpoints: Vec<String>,
139}
140
141impl std::ops::Deref for Client {
142 type Target = GrpcClient;
143
144 fn deref(&self) -> &Self::Target {
145 &self.inner
146 }
147}
148
149impl Client {
150 /// Create a configured client builder
151 ///
152 /// Starts client construction process with specified bootstrap endpoints.
153 /// Chain configuration methods before calling
154 /// [`build()`](ClientBuilder::build).
155 ///
156 /// # Arguments
157 /// * `endpoints` - Initial cluster nodes for discovery
158 ///
159 /// # Panics
160 /// Will panic if no valid endpoints provided
161 pub fn builder(endpoints: Vec<String>) -> ClientBuilder {
162 assert!(!endpoints.is_empty(), "At least one endpoint required");
163 ClientBuilder::new(endpoints)
164 }
165
166 /// Rediscover the cluster and rebuild the connection pool.
167 ///
168 /// Blocks until a leader whose noop entry is committed by majority is found,
169 /// or `ClientConfig::cluster_ready_timeout` elapses.
170 ///
171 /// **What this does:**
172 /// - Probes all endpoints in round-robin until one reports a ready leader
173 /// (`current_leader_id` is `Some` and present in the member list)
174 /// - Atomically replaces the cached leader/follower connections
175 /// - After `Ok(())`, `get_leader_id()` returns the new leader and all
176 /// write/read operations are routed to the correct node
177 ///
178 /// **What this does NOT do:**
179 /// - Does not guarantee that the caller's in-flight requests succeeded;
180 /// requests sent before `refresh()` may have failed and need to be retried
181 /// - Does not implement application-level retry — the caller is responsible
182 /// for re-issuing any operations that failed during the failover window
183 /// - Does not update endpoints permanently; pass `new_endpoints` to change
184 /// the bootstrap list for this and future refreshes
185 ///
186 /// **Typical usage after leader failover:**
187 /// ```ignore
188 /// client.refresh(None).await?; // blocks until new leader ready
189 /// client.put(key, value).await?; // now safe to retry operations
190 /// ```
191 pub async fn refresh(
192 &self,
193 new_endpoints: Option<Vec<String>>,
194 ) -> std::result::Result<(), ClientApiError> {
195 let old_inner = self.inner.client_inner.load();
196 let config = old_inner.config.clone();
197 let endpoints = new_endpoints.unwrap_or(old_inner.endpoints.clone());
198
199 let new_pool = ConnectionPool::create(endpoints.clone(), config.clone()).await?;
200
201 let new_inner = std::sync::Arc::new(ClientInner {
202 pool: new_pool,
203 client_id: old_inner.client_id,
204 config,
205 endpoints,
206 });
207
208 self.inner.client_inner.store(new_inner);
209 Ok(())
210 }
211}