Skip to main content

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}