VSTP Best Practices

Learn the best practices for building robust, scalable applications with VSTP. These guidelines will help you avoid common pitfalls and build production-ready systems.

Message Design

Well-designed messages are the foundation of a maintainable VSTP application. Follow these principles for optimal message design.

Use Strong Types

❌ Avoid

// Weak typing
#[derive(Serialize, Deserialize)]
struct Message {
    data: serde_json::Value,  // Too generic
    type: String,             // Error-prone
}

✅ Prefer

// 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,
}

Version Your Messages

#[derive(Serialize, Deserialize)]
struct MessageV1 {
    version: u32,
    data: MessageData,
}

#[derive(Serialize, Deserialize)]
struct MessageData {
    // Your actual message fields
}

Keep Messages Small

💡 Size Guidelines

  • TCP: Keep under 64KB for optimal performance
  • UDP: Keep under 1.5KB to avoid fragmentation
  • Use pagination for large data sets
  • Consider compression for text-heavy messages

Error Handling

Robust error handling is crucial for production applications. VSTP provides comprehensive error types to help you handle failures gracefully.

Handle All Error Types

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(())
}

Implement Retry Logic

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!()
}

Performance Optimization

VSTP is designed for high performance, but following these guidelines will help you get the most out of it.

Choose the Right Transport

🌐 Use TCP When

  • • Reliable delivery is required
  • • Message order matters
  • • You need built-in encryption
  • • Connection persistence is important

⚡ Use UDP When

  • • Low latency is critical
  • • High throughput is needed
  • • You can handle packet loss
  • • You need custom reliability

Optimize Message Batching

// 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
}

Connection Pooling

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 Best Practices

Security should be a primary concern when building networked applications. VSTP provides several security features, but you need to use them correctly.

Always Use TLS for TCP

🔒 TLS Benefits

  • Automatic encryption of all data
  • Perfect forward secrecy
  • Certificate-based authentication
  • Protection against man-in-the-middle attacks

Implement Application-Level Security for UDP

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(())
}

Validate All Input

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(())
}

Testing Strategies

Comprehensive testing is essential for reliable applications. VSTP makes it easy to test your networked code.

Unit Testing

#[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(())
    }
}

Integration Testing

#[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(())
}

Monitoring and Observability

Production applications need comprehensive monitoring to ensure reliability and performance.

Logging

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)
        }
    }
}

Metrics

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)
}

Health Checks

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)
    }
}

Summary

Following these best practices will help you build robust, scalable applications with VSTP:

  • Design strong, versioned message types
  • Implement comprehensive error handling and retry logic
  • Choose the right transport for your use case
  • Optimize for performance with batching and connection pooling
  • Implement proper security measures
  • Write comprehensive tests
  • Add monitoring and observability