Learn the best practices for building robust, scalable applications with VSTP. These guidelines will help you avoid common pitfalls and build production-ready systems.
Well-designed messages are the foundation of a maintainable VSTP application. Follow these principles for optimal message design.
// Weak typing
#[derive(Serialize, Deserialize)]
struct Message {
data: serde_json::Value, // Too generic
type: String, // Error-prone
}// Strong typing
#[derive(Serialize, Deserialize)]
struct UserMessage {
user_id: u64,
content: String,
timestamp: u64,
}
#[derive(Serialize, Deserialize)]
struct SystemMessage {
level: LogLevel,
message: String,
source: String,
}#[derive(Serialize, Deserialize)]
struct MessageV1 {
version: u32,
data: MessageData,
}
#[derive(Serialize, Deserialize)]
struct MessageData {
// Your actual message fields
}Robust error handling is crucial for production applications. VSTP provides comprehensive error types to help you handle failures gracefully.
use vstp::VstpError;
async fn handle_message(client: &VstpClient) -> Result<(), Box<dyn std::error::Error>> {
match client.receive::<MyMessage>().await {
Ok(message) => {
// Process message
process_message(message).await?;
}
Err(VstpError::Timeout) => {
// Handle timeout
log::warn!("Message receive timeout");
return Ok(());
}
Err(VstpError::Protocol(msg)) => {
// Handle protocol errors
log::error!("Protocol error: {}", msg);
return Err(msg.into());
}
Err(e) => {
// Handle other errors
log::error!("Unexpected error: {}", e);
return Err(e.into());
}
}
Ok(())
}use tokio::time::{sleep, Duration};
async fn send_with_retry(
client: &VstpClient,
message: MyMessage,
max_retries: u32,
) -> Result<(), Box<dyn std::error::Error>> {
for attempt in 0..max_retries {
match client.send(message.clone()).await {
Ok(_) => return Ok(()),
Err(e) if attempt == max_retries - 1 => return Err(e.into()),
Err(e) => {
log::warn!("Send attempt {} failed: {}", attempt + 1, e);
sleep(Duration::from_millis(100 * (attempt + 1) as u64)).await;
}
}
}
unreachable!()
}VSTP is designed for high performance, but following these guidelines will help you get the most out of it.
// Batch multiple messages
#[derive(Serialize, Deserialize)]
struct MessageBatch {
messages: Vec<MyMessage>,
batch_id: String,
}
async fn send_batch(client: &VstpClient, messages: Vec<MyMessage>) -> Result<(), VstpError> {
let batch = MessageBatch {
messages,
batch_id: uuid::Uuid::new_v4().to_string(),
};
client.send(batch).await
}use std::sync::Arc;
use tokio::sync::Mutex;
struct ConnectionPool {
connections: Arc<Mutex<Vec<VstpClient>>>,
max_connections: usize,
}
impl ConnectionPool {
async fn get_connection(&self) -> Result<VstpClient, VstpError> {
let mut conns = self.connections.lock().await;
if let Some(conn) = conns.pop() {
return Ok(conn);
}
// Create new connection
VstpClient::connect_tcp("127.0.0.1:8080").await
}
async fn return_connection(&self, conn: VstpClient) {
let mut conns = self.connections.lock().await;
if conns.len() < self.max_connections {
conns.push(conn);
}
}
}Security should be a primary concern when building networked applications. VSTP provides several security features, but you need to use them correctly.
use aes_gcm::{Aes256Gcm, Key, Nonce};
use aes_gcm::aead::{Aead, NewAead};
#[derive(Serialize, Deserialize)]
struct SecureMessage {
encrypted_data: Vec<u8>,
nonce: Vec<u8>,
signature: Vec<u8>,
}
async fn send_secure_message(
client: &VstpClient,
message: MyMessage,
key: &[u8],
) -> Result<(), Box<dyn std::error::Error>> {
// Encrypt message
let cipher = Aes256Gcm::new(Key::from_slice(key));
let nonce = Nonce::from_slice(b"unique nonce");
let encrypted = cipher.encrypt(nonce, message.to_string().as_bytes())?;
// Create secure message
let secure_msg = SecureMessage {
encrypted_data: encrypted,
nonce: nonce.to_vec(),
signature: sign_message(&encrypted, key)?,
};
client.send(secure_msg).await?;
Ok(())
}use validator::{Validate, ValidationError};
#[derive(Serialize, Deserialize, Validate)]
struct UserMessage {
#[validate(length(min = 1, max = 1000))]
content: String,
#[validate(range(min = 0, max = 100))]
priority: u8,
}
async fn handle_user_message(msg: UserMessage) -> Result<(), VstpError> {
// Validate input
msg.validate()
.map_err(|e| VstpError::Protocol(format!("Validation error: {}", e)))?;
// Process message
process_message(msg).await?;
Ok(())
}Comprehensive testing is essential for reliable applications. VSTP makes it easy to test your networked code.
#[cfg(test)]
mod tests {
use super::*;
use vstp::easy::{VstpClient, VstpServer};
#[tokio::test]
async fn test_message_handling() -> Result<(), Box<dyn std::error::Error>> {
let server = VstpServer::bind_tcp("127.0.0.1:0").await?;
let server_addr = "127.0.0.1:8080";
// Start server
tokio::spawn(async move {
server.serve(|msg: TestMessage| async move {
assert_eq!(msg.content, "test");
Ok(msg)
}).await
});
// Test client
let client = VstpClient::connect_tcp(server_addr).await?;
let test_msg = TestMessage {
content: "test".to_string(),
};
client.send(test_msg.clone()).await?;
let response: TestMessage = client.receive().await?;
assert_eq!(response.content, "test");
Ok(())
}
}#[tokio::test]
async fn test_full_workflow() -> Result<(), Box<dyn std::error::Error>> {
// Start test server
let server = start_test_server().await?;
// Test multiple clients
let mut clients = Vec::new();
for i in 0..10 {
let client = VstpClient::connect_tcp("127.0.0.1:8080").await?;
clients.push(client);
}
// Send messages from all clients
for (i, client) in clients.iter().enumerate() {
let msg = TestMessage {
content: format!("message {}", i),
};
client.send(msg).await?;
}
// Verify all messages were received
// ... verification logic
Ok(())
}Production applications need comprehensive monitoring to ensure reliability and performance.
use tracing::{info, warn, error, instrument};
#[instrument]
async fn handle_message(msg: MyMessage) -> Result<(), VstpError> {
info!("Processing message: {}", msg.id);
match process_message(msg).await {
Ok(result) => {
info!("Message processed successfully: {}", result);
Ok(())
}
Err(e) => {
error!("Failed to process message: {}", e);
Err(e)
}
}
}use prometheus::{Counter, Histogram, Registry};
lazy_static! {
static ref MESSAGES_RECEIVED: Counter = Counter::new(
"vstp_messages_received_total",
"Total number of messages received"
).unwrap();
static ref MESSAGE_PROCESSING_TIME: Histogram = Histogram::new(
"vstp_message_processing_seconds",
"Time spent processing messages"
).unwrap();
}
async fn handle_message_with_metrics(msg: MyMessage) -> Result<(), VstpError> {
let _timer = MESSAGE_PROCESSING_TIME.start_timer();
MESSAGES_RECEIVED.inc();
// Process message
let result = process_message(msg).await?;
Ok(result)
}use std::time::{Duration, Instant};
struct HealthChecker {
last_heartbeat: Arc<Mutex<Instant>>,
}
impl HealthChecker {
async fn start_heartbeat(&self, client: VstpClient) {
let last_heartbeat = self.last_heartbeat.clone();
tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(30));
loop {
interval.tick().await;
match client.send(HeartbeatMessage {}).await {
Ok(_) => {
*last_heartbeat.lock().await = Instant::now();
}
Err(e) => {
error!("Heartbeat failed: {}", e);
}
}
}
});
}
fn is_healthy(&self) -> bool {
let last_heartbeat = self.last_heartbeat.lock().unwrap();
last_heartbeat.elapsed() < Duration::from_secs(60)
}
}Following these best practices will help you build robust, scalable applications with VSTP: