# Cross-Platform NATSBridge Walkthrough

A comprehensive guide to building real-world applications with NATSBridge across **Julia**, **JavaScript**, and **Python/MicroPython**.

## Table of Contents

1. [Introduction](#introduction)
2. [Architecture Overview](#architecture-overview)
3. [Building a Chat Application](#building-a-chat-application)
4. [Building a File Transfer System](#building-a-file-transfer-system)
5. [Building a Streaming Data Pipeline](#building-a-streaming-data-pipeline)
6. [Performance Optimization](#performance-optimization)
7. [Best Practices](#best-practices)

---

## Introduction

This walkthrough will guide you through building several real-world applications using NATSBridge. We'll cover:

- Chat applications with rich media support
- File transfer systems with claim-check pattern
- Streaming data pipelines

Each section builds on the previous one, gradually increasing in complexity.

---

## Architecture Overview

### Cross-Platform System Components

```mermaid
flowchart TB
    subgraph JuliaApp["Julia Application"]
        JuliaAppCode[App Code]
        JuliaBridge[NATSBridge.jl]
        JuliaNATS[<b>NATS.jl</b>]
    end

    subgraph JSApp["JavaScript Application"]
        JSAppCode[App Code]
        JSBridge[NATSBridge.js]
        JSNATS[<b>nats.js</b>]
    end

    subgraph PythonApp["Python Application"]
        PythonAppCode[App Code]
        PythonBridge[NATSBridge.py]
        PythonNATS[<b>nats-py</b>]
    end

    subgraph Infrastructure["Infrastructure"]
        NATS[<b>NATS Server</b><br/>Message Broker]
        FileServer[<b>HTTP File Server</b><br/>Upload/Download]
    end

    JuliaAppCode --> JuliaBridge
    JuliaBridge --> JuliaNATS
    JSAppCode --> JSBridge
    JSBridge --> JSNATS
    PythonAppCode --> PythonBridge
    PythonBridge --> PythonNATS

    JuliaNATS --> NATS
    JSNATS --> NATS
    PythonNATS --> NATS

    NATS --> JuliaNATS
    NATS --> JSNATS
    NATS --> PythonNATS

    JuliaBridge -.->|HTTP POST upload| FileServer
    JSBridge -.->|HTTP POST upload| FileServer
    PythonBridge -.->|HTTP POST upload| FileServer

    FileServer -.->|HTTP GET download| JuliaBridge
    FileServer -.->|HTTP GET download| JSBridge
    FileServer -.->|HTTP GET download| PythonBridge

    style JuliaApp fill:#c5e1a5
    style JSApp fill:#bbdefb
    style PythonApp fill:#f8bbd0
    style NATS fill:#fff3e0
    style FileServer fill:#f3e5f5
```

### Message Flow

1. **Sender** creates a message envelope with payloads
2. **NATSBridge** serializes and encodes payloads
3. **Transport Decision**: Small payloads go directly to NATS, large payloads are uploaded to file server
4. **NATS** routes messages to subscribers
5. **Receiver** fetches payloads (from NATS or file server)
6. **NATSBridge** deserializes and decodes payloads

---

## Building a Chat Application

Let's build a full-featured chat application that supports text, images, and file attachments.

### Step 1: Set Up the Project

```bash
# Create project directory
mkdir -p chat-app/src
cd chat-app

# Create configuration file
cat > config.json << 'EOF'
{
  "nats_url": "nats://localhost:4222",
  "fileserver_url": "http://localhost:8080",
  "size_threshold": 1048576
}
EOF
```

### Step 2: Create the Chat Interface

#### Julia

```julia
# src/chat_ui.jl
using NATSBridge, NATS

struct ChatUI
    messages::Vector{Dict}
    current_room::String
end

function ChatUI()
    ChatUI(Dict[], "")
end

function send_message(ui::ChatUI, message_input::String, selected_file::Union{Nothing, String})
    data = []
    
    # Add text message
    if !isempty(message_input)
        push!(data, ("text", message_input, "text"))
    end
    
    # Add file if selected
    if selected_file !== nothing
        file_data = read(selected_file)
        file_type = get_file_type(selected_file)
        push!(data, ("attachment", file_data, file_type))
    end
    
    return data
end

function get_file_type(filename::String)::String
    if endswith(filename, ".png") || endswith(filename, ".jpg")
        return "image"
    elseif endswith(filename, ".mp3") || endswith(filename, ".wav")
        return "audio"
    elseif endswith(filename, ".mp4") || endswith(filename, ".avi")
        return "video"
    else
        return "binary"
    end
end

function add_message(ui::ChatUI, user::String, text::String, attachment::Union{Nothing, Dict})
    push!(ui.messages, Dict(
        "user" => user,
        "text" => text,
        "attachment" => attachment
    ))
end
```

#### JavaScript

```javascript
// src/chat_ui.js
const NATSBridge = require('./src/natsbridge.js');

class ChatUI {
    constructor() {
        this.messages = [];
        this.currentRoom = "";
    }

    sendMessage(messageInput, selectedFile = null) {
        const data = [];
        
        // Add text message
        if (messageInput.length > 0) {
            data.push(["text", messageInput, "text"]);
        }
        
        // Add file if selected
        if (selectedFile !== null) {
            const fileData = fs.readFileSync(selectedFile);
            const fileType = this.getFileType(selectedFile);
            data.push(["attachment", fileData, fileType]);
        }
        
        return data;
    }

    getFileType(filename) {
        if (filename.endsWith('.png') || filename.endsWith('.jpg')) {
            return 'image';
        } else if (filename.endsWith('.mp3') || filename.endsWith('.wav')) {
            return 'audio';
        } else if (filename.endsWith('.mp4') || filename.endsWith('.avi')) {
            return 'video';
        } else {
            return 'binary';
        }
    }

    addMessage(user, text, attachment = null) {
        this.messages.push({
            user,
            text,
            attachment
        });
    }
}

module.exports = ChatUI;
```

#### Python

```python
# src/chat_ui.py
from typing import List, Dict, Optional, Union

class ChatUI:
    def __init__(self):
        self.messages: List[Dict] = []
        self.current_room: str = ""

    def send_message(self, message_input: str, selected_file: Optional[str] = None) -> List[tuple]:
        data = []
        
        # Add text message
        if message_input:
            data.append(("text", message_input, "text"))
        
        # Add file if selected
        if selected_file:
            with open(selected_file, "rb") as f:
                file_data = f.read()
            file_type = self.get_file_type(selected_file)
            data.append(("attachment", file_data, file_type))
        
        return data

    def get_file_type(self, filename: str) -> str:
        if filename.endswith(('.png', '.jpg')):
            return "image"
        elif filename.endswith(('.mp3', '.wav')):
            return "audio"
        elif filename.endswith(('.mp4', '.avi')):
            return "video"
        else:
            return "binary"

    def add_message(self, user: str, text: str, attachment: Optional[Dict] = None):
        self.messages.append({
            "user": user,
            "text": text,
            "attachment": attachment
        })
```

### Step 3: Create the Message Handler

#### Julia

```julia
# src/chat_handler.jl
using NATSBridge, NATS

struct ChatHandler
    nats::NATS.Connection
    ui::ChatUI
end

function ChatHandler(nats_connection::NATS.Connection)
    ChatHandler(nats_connection, ChatUI())
end

function start(handler::ChatHandler)
    # Subscribe to chat rooms
    rooms = ["general", "tech", "random"]
    
    for room in rooms
        NATS.subscribe(handler.nats, "/chat/$room") do msg
            handle_message(handler, msg)
        end
    end
    
    println("Chat handler started")
end

function handle_message(handler::ChatHandler, msg::NATS.Msg)
    env = smartreceive(msg, fileserver_download_handler=_fetch_with_backoff)
    
    # Extract sender info from envelope
    sender = get(env, "sender_name", "Anonymous")
    
    # Process each payload
    for (dataname, data, type) in env["payloads"]
        if type == "text"
            add_message(handler.ui, sender, data, nothing)
        elseif type == "image"
            # Convert to data URL for display
            base64_data = base64encode(data)
            attachment = Dict(
                "type" => "image",
                "data" => "data:image/png;base64,$base64_data"
            )
            add_message(handler.ui, sender, "", attachment)
        else
            # For other types, use file server URL
            attachment = Dict("type" => type, "data" => data)
            add_message(handler.ui, sender, "", attachment)
        end
    end
end
```

#### JavaScript

```javascript
// src/chat_handler.js
const NATSBridge = require('./src/natsbridge.js');
const nats = require('nats');

class ChatHandler {
    constructor(natsConnection) {
        this.nats = natsConnection;
        this.ui = new (require('./chat_ui.js'))();
    }

    async start() {
        // Subscribe to chat rooms
        const rooms = ['general', 'tech', 'random'];
        
        for (const room of rooms) {
            this.nats.subscribe(`/chat/${room}`, async (msg) => {
                await this.handleMessage(msg);
            });
        }
        
        console.log('Chat handler started');
    }

    async handleMessage(msg) {
        const env = await NATSBridge.smartreceive(msg, {
            fileserver_download_handler: NATSBridge.fetchWithBackoff
        });
        
        // Extract sender info from envelope
        const sender = env.sender_name || 'Anonymous';
        
        // Process each payload
        for (const [dataname, data, type] of env.payloads) {
            if (type === 'text') {
                this.ui.addMessage(sender, data, null);
            } else if (type === 'image') {
                // Convert to data URL for display
                const base64Data = Buffer.from(data).toString('base64');
                const attachment = {
                    type: 'image',
                    data: `data:image/png;base64,${base64Data}`
                };
                this.ui.addMessage(sender, '', attachment);
            } else {
                // For other types, use file server URL
                const attachment = { type, data };
                this.ui.addMessage(sender, '', attachment);
            }
        }
    }
}

module.exports = ChatHandler;
```

#### Python

```python
# src/chat_handler.py
import asyncio
from typing import Optional
from natsbridge import smartreceive, fetch_with_backoff

class ChatHandler:
    def __init__(self, nats_connection):
        self.nats = nats_connection
        self.ui = ChatUI()

    async def start(self):
        # Subscribe to chat rooms
        rooms = ['general', 'tech', 'random']
        
        for room in rooms:
            await self.nats.subscribe(
                f'/chat/{room}',
                callback=self.handle_message
            )
        
        print('Chat handler started')

    async def handle_message(self, msg):
        env = await smartreceive(
            msg,
            fileserver_download_handler=fetch_with_backoff
        )
        
        # Extract sender info from envelope
        sender = env.get('sender_name', 'Anonymous')
        
        # Process each payload
        for dataname, data, type_ in env['payloads']:
            if type_ == 'text':
                self.ui.add_message(sender, data, None)
            elif type_ == 'image':
                # Convert to data URL for display
                import base64
                base64_data = base64.b64encode(data).decode('utf-8')
                attachment = {
                    'type': 'image',
                    'data': f'data:image/png;base64,{base64_data}'
                }
                self.ui.add_message(sender, '', attachment)
            else:
                # For other types, use file server URL or data
                attachment = {'type': type_, 'data': data}
                self.ui.add_message(sender, '', attachment)
```

### Step 4: Run the Application

```bash
# Start NATS
docker run -p 4222:4222 nats:latest

# Start file server
mkdir -p /tmp/fileserver
python3 -m http.server 8080 --directory /tmp/fileserver

# Run chat app
# Julia
julia src/chat_ui.jl
julia src/chat_handler.jl

# JavaScript
node src/chat_ui.js
node src/chat_handler.js

# Python
python3 src/chat_ui.py
python3 src/chat_handler.py
```

---

## Building a File Transfer System

Let's build a file transfer system that handles large files efficiently.

### Step 1: File Upload Service

#### Julia

```julia
# src/file_upload_service.jl
using NATSBridge, HTTP

struct FileUploadService
    broker_url::String
    fileserver_url::String
end

function FileUploadService(broker_url::String, fileserver_url::String)
    FileUploadService(broker_url, fileserver_url)
end

function upload_file(service::FileUploadService, file_path::String, recipient::String)::Dict
    file_data = read(file_path)
    file_name = basename(file_path)
    
    data = [("file", file_data, "binary")]
    
    env, env_json_str = smartsend(
        "/files/$recipient",
        data,
        broker_url=service.broker_url,
        fileserver_url=service.fileserver_url
    )
    
    return env
end

function upload_large_file(service::FileUploadService, file_path::String, recipient::String)::Dict
    file_size = stat(file_path).size
    
    if file_size > 100 * 1024 * 1024  # > 100MB
        println("File too large for direct upload, using streaming...")
        return stream_upload(service, file_path, recipient)
    end
    
    return upload_file(service, file_path, recipient)
end

function stream_upload(service::FileUploadService, file_path::String, recipient::String)::Dict
    # Implement streaming upload to file server
    # This would require a more sophisticated file server
    # For now, we'll use the standard upload
    return upload_file(service, file_path, recipient)
end
```

#### JavaScript

```javascript
// src/file_upload_service.js
const NATSBridge = require('./src/natsbridge.js');
const fs = require('fs');

class FileUploadService {
    constructor(brokerUrl, fileserverUrl) {
        this.broker_url = brokerUrl;
        this.fileserver_url = fileserverUrl;
    }

    async uploadFile(filePath, recipient) {
        const fileData = fs.readFileSync(filePath);
        const fileName = require('path').basename(filePath);
        
        const data = [["file", fileData, "binary"]];
        
        const [env, env_json_str] = await NATSBridge.smartsend(
            `/files/${recipient}`,
            data,
            {
                broker_url: this.broker_url,
                fileserver_url: this.fileserver_url
            }
        );
        
        return env;
    }

    async uploadLargeFile(filePath, recipient) {
        const stats = fs.statSync(filePath);
        const fileSize = stats.size;
        
        if (fileSize > 100 * 1024 * 1024) {  // > 100MB
            console.log('File too large for direct upload, using streaming...');
            return this.streamUpload(filePath, recipient);
        }
        
        return this.uploadFile(filePath, recipient);
    }

    async streamUpload(filePath, recipient) {
        // Implement streaming upload to file server
        // This would require a more sophisticated file server
        // For now, we'll use the standard upload
        return this.uploadFile(filePath, recipient);
    }
}

module.exports = FileUploadService;
```

#### Python

```python
# src/file_upload_service.py
from natsbridge import smartsend
import os

class FileUploadService:
    def __init__(self, broker_url: str, fileserver_url: str):
        self.broker_url = broker_url
        self.fileserver_url = fileserver_url

    async def upload_file(self, file_path: str, recipient: str) -> tuple:
        with open(file_path, "rb") as f:
            file_data = f.read()
        file_name = os.path.basename(file_path)
        
        data = [("file", file_data, "binary")]
        
        env, env_json_str = await smartsend(
            f"/files/{recipient}",
            data,
            broker_url=self.broker_url,
            fileserver_url=self.fileserver_url
        )
        
        return env, env_json_str

    async def upload_large_file(self, file_path: str, recipient: str) -> tuple:
        file_size = os.path.getsize(file_path)
        
        if file_size > 100 * 1024 * 1024:  # > 100MB
            print("File too large for direct upload, using streaming...")
            return await self.stream_upload(file_path, recipient)
        
        return await self.upload_file(file_path, recipient)

    async def stream_upload(self, file_path: str, recipient: str) -> tuple:
        # Implement streaming upload to file server
        # This would require a more sophisticated file server
        # For now, we'll use the standard upload
        return await self.upload_file(file_path, recipient)
```

### Step 2: File Download Service

#### Julia

```julia
# src/file_download_service.jl
using NATSBridge

struct FileDownloadService
    nats_url::String
end

function FileDownloadService(nats_url::String)
    FileDownloadService(nats_url)
end

function download_file(service::FileDownloadService, msg::NATS.Msg, sender::String, download_id::String)
    env = smartreceive(msg, fileserver_download_handler=fetch_from_url)
    
    # Process each payload
    for (dataname, data, type) in env["payloads"]
        if type == "binary"
            file_path = "/downloads/$dataname"
            write(file_path, data)
            println("File saved to $file_path")
        end
    end
end

function fetch_from_url(url::String, max_retries::Int, base_delay::Int, max_delay::Int, correlation_id::String)::Vector{UInt8}
    # Fetch data from URL with exponential backoff
    # Return downloaded data as Vector{UInt8}
end
```

#### JavaScript

```javascript
// src/file_download_service.js
const NATSBridge = require('./src/natsbridge.js');
const fs = require('fs');

class FileDownloadService {
    constructor(natsUrl) {
        this.nats_url = natsUrl;
    }

    async downloadFile(msg, sender, downloadId) {
        const env = await NATSBridge.smartreceive(msg, {
            fileserver_download_handler: NATSBridge.fetchWithBackoff
        });
        
        // Process each payload
        for (const [dataname, data, type] of env.payloads) {
            if (type === 'binary') {
                const filePath = `/downloads/${dataname}`;
                fs.writeFileSync(filePath, data);
                console.log(`File saved to ${filePath}`);
            }
        }
    }
}

module.exports = FileDownloadService;
```

#### Python

```python
# src/file_download_service.py
from natsbridge import smartreceive, fetch_with_backoff
import os

class FileDownloadService:
    def __init__(self, nats_url: str):
        self.nats_url = nats_url

    async def download_file(self, msg, sender: str, download_id: str):
        env = await smartreceive(
            msg,
            fileserver_download_handler=fetch_with_backoff
        )
        
        # Process each payload
        for dataname, data, type_ in env['payloads']:
            if type_ == 'binary':
                file_path = f'/downloads/{dataname}'
                os.makedirs('/downloads', exist_ok=True)
                with open(file_path, 'wb') as f:
                    f.write(data)
                print(f"File saved to {file_path}")
```

### Step 3: File Transfer CLI

#### Julia

```julia
# src/cli.jl
using NATSBridge

function main()
    println("File Transfer System")
    println("====================")
    println("1. Upload file")
    println("2. Download file")
    println("3. List pending downloads")
    
    print("Enter choice: ")
    choice = readline()
    
    if choice == "1"
        upload_file_cli()
    elseif choice == "2"
        download_file_cli()
    end
end

function upload_file_cli()
    print("Enter file path: ")
    file_path = readline()
    
    print("Enter recipient: ")
    recipient = readline()
    
    file_service = FileUploadService("nats://localhost:4222", "http://localhost:8080")
    
    try
        env = upload_file(file_service, file_path, recipient)
        println("Upload successful!")
        println("File ID: $(env["payloads"][1][1])")
    catch error
        println("Upload failed: $(error)")
    end
end

function download_file_cli()
    print("Enter sender: ")
    sender = readline()
    
    file_service = FileDownloadService("nats://localhost:4222")
    
    try
        download_file(file_service, sender)
        println("Download complete!")
    catch error
        println("Download failed: $(error)")
    end
end

main()
```

---

## Building a Streaming Data Pipeline

Let's build a data pipeline that processes streaming data from sensors.

### Step 1: Sensor Data Model

#### Julia

```julia
# src/sensor_data.jl
using Dates, DataFrames

struct SensorReading
    sensor_id::String
    timestamp::String
    value::Float64
    unit::String
    metadata::Dict{String, Any}
end

function SensorReading(sensor_id::String, value::Float64, unit::String, metadata::Dict{String, Any}=Dict())
    SensorReading(
        sensor_id,
        ISODateTime(now(), Dates.Second) |> string,
        value,
        unit,
        metadata
    )
end

struct SensorBatch
    readings::Vector{SensorReading}
end

function SensorBatch()
    SensorBatch(SensorReading[])
end

function add_reading(batch::SensorBatch, reading::SensorReading)
    push!(batch.readings, reading)
end

function to_dataframe(batch::SensorBatch)::DataFrame
    data = Dict{String, Any}()
    data["sensor_id"] = [r.sensor_id for r in batch.readings]
    data["timestamp"] = [r.timestamp for r in batch.readings]
    data["value"] = [r.value for r in batch.readings]
    data["unit"] = [r.unit for r in batch.readings]
    
    return DataFrame(data)
end
```

#### JavaScript

```javascript
// src/sensor_data.js
const NATSBridge = require('./src/natsbridge.js');

class SensorReading {
    constructor(sensorId, value, unit, metadata = {}) {
        this.sensor_id = sensorId;
        this.timestamp = new Date().toISOString();
        this.value = value;
        this.unit = unit;
        this.metadata = metadata;
    }
}

class SensorBatch {
    constructor() {
        this.readings = [];
    }

    addReading(reading) {
        this.readings.push(reading);
    }

    toDataFrame() {
        return {
            sensor_id: this.readings.map(r => r.sensor_id),
            timestamp: this.readings.map(r => r.timestamp),
            value: this.readings.map(r => r.value),
            unit: this.readings.map(r => r.unit)
        };
    }
}

module.exports = { SensorReading, SensorBatch };
```

#### Python

```python
# src/sensor_data.py
from datetime import datetime
from dataclasses import dataclass, field
from typing import List, Dict, Any

@dataclass
class SensorReading:
    sensor_id: str
    timestamp: str
    value: float
    unit: str
    metadata: Dict[str, Any] = field(default_factory=dict)

    @classmethod
    def create(cls, sensor_id: str, value: float, unit: str, metadata: Dict[str, Any] = None):
        return cls(
            sensor_id=sensor_id,
            timestamp=datetime.utcnow().isoformat(),
            value=value,
            unit=unit,
            metadata=metadata or {}
        )

class SensorBatch:
    def __init__(self):
        self.readings: List[SensorReading] = []

    def add_reading(self, reading: SensorReading):
        self.readings.append(reading)

    def to_dataframe(self):
        import pandas as pd
        return pd.DataFrame({
            'sensor_id': [r.sensor_id for r in self.readings],
            'timestamp': [r.timestamp for r in self.readings],
            'value': [r.value for r in self.readings],
            'unit': [r.unit for r in self.readings]
        })
```

### Step 2: Sensor Sender

#### Julia

```julia
# src/sensor_sender.jl
using NATSBridge, Dates, Random

struct SensorSender
    broker_url::String
    fileserver_url::String
end

function SensorSender(broker_url::String, fileserver_url::String)
    SensorSender(broker_url, fileserver_url)
end

function send_reading(sender::SensorSender, sensor_id::String, value::Float64, unit::String)
    reading = SensorReading(sensor_id, value, unit)
    
    data = [("reading", reading.metadata, "dictionary")]
    
    # Default: is_publish=True (automatically publishes to NATS)
    smartsend(
        "/sensors/$sensor_id",
        data,
        broker_url=sender.broker_url,
        fileserver_url=sender.fileserver_url
    )
end

function send_batch(sender::SensorSender, readings::Vector{SensorReading})
    batch = SensorBatch()
    for reading in readings
        add_reading(batch, reading)
    end
    
    df = to_dataframe(batch)
    
    # Convert to Arrow IPC format
    import Arrow
    table = Arrow.Table(df)
    
    # Serialize to Arrow IPC
    import IOBuffer
    buf = IOBuffer()
    Arrow.write(buf, table)
    
    arrow_data = take!(buf)
    
    # Send based on size (auto-selected by smartsend)
    data = [("batch", arrow_data, "arrowtable")]
    smartsend(
        "/sensors/batch",
        data,
        broker_url=sender.broker_url,
        fileserver_url=sender.fileserver_url
    )
end
```

#### JavaScript

```javascript
// src/sensor_sender.js
const NATSBridge = require('./src/natsbridge.js');
const { SensorReading, SensorBatch } = require('./sensor_data.js');

class SensorSender {
    constructor(brokerUrl, fileserverUrl) {
        this.broker_url = brokerUrl;
        this.fileserver_url = fileserverUrl;
    }

    async sendReading(sensorId, value, unit) {
        const reading = new SensorReading(sensorId, value, unit);
        
        const data = [["reading", reading.metadata, "dictionary"]];
        
        await NATSBridge.smartsend(
            `/sensors/${sensorId}`,
            data,
            {
                broker_url: this.broker_url,
                fileserver_url: this.fileserver_url
            }
        );
    }

    async sendBatch(readings) {
        const batch = new SensorBatch();
        for (const reading of readings) {
            batch.addReading(reading);
        }
        
        const df = batch.toDataFrame();
        
        // Convert to Arrow IPC
        const arrow = require('apache-arrow');
        const schema = new arrow.Schema([
            new arrow.Field('sensor_id', arrow.string()),
            new arrow.Field('timestamp', arrow.string()),
            new arrow.Field('value', arrow.float64()),
            new arrow.Field('unit', arrow.string())
        ]);
        
        const arrays = {
            sensor_id: new arrow.StringArray(df.sensor_id.map(s => String(s))),
            timestamp: new arrow.StringArray(df.timestamp),
            value: new arrow.Float64Array(df.value),
            unit: new arrow.StringArray(df.unit)
        };
        
        const recordBatch = arrow.RecordBatch.fromArrays(schema, arrays, df.value.length);
        const buffer = arrow.tableFromBatches([recordBatch]).toBuffer();
        const arrow_data = new Uint8Array(buffer);
        
        // Send based on size (auto-selected by smartsend)
        const data = [["batch", arrow_data, "arrowtable"]];
        await NATSBridge.smartsend(
            "/sensors/batch",
            data,
            {
                broker_url: this.broker_url,
                fileserver_url: this.fileserver_url
            }
        );
    }
}

module.exports = SensorSender;
```

#### Python

```python
# src/sensor_sender.py
from natsbridge import smartsend
from sensor_data import SensorReading, SensorBatch

class SensorSender:
    def __init__(self, broker_url: str, fileserver_url: str):
        self.broker_url = broker_url
        self.fileserver_url = fileserver_url

    async def send_reading(self, sensor_id: str, value: float, unit: str):
        reading = SensorReading.create(sensor_id, value, unit)
        
        data = [("reading", reading.metadata, "dictionary")]
        
        await smartsend(
            f"/sensors/{sensor_id}",
            data,
            broker_url=self.broker_url,
            fileserver_url=self.fileserver_url
        )

    async def send_batch(self, readings):
        batch = SensorBatch()
        for reading in readings:
            batch.add_reading(reading)
        
        df = batch.to_dataframe()
        
        # Convert to Arrow IPC
        import pyarrow as arrow
        import pyarrow.ipc as ipc
        import io
        
        table = arrow.Table.from_pandas(df)
        buf = io.BytesIO()
        sink = ipc.new_file(buf, table.schema)
        ipc.write_table(table, sink)
        sink.close()
        arrow_data = buf.getvalue()
        
        # Send based on size (auto-selected by smartsend)
        data = [("batch", arrow_data, "arrowtable")]
        await smartsend(
            "/sensors/batch",
            data,
            broker_url=self.broker_url,
            fileserver_url=self.fileserver_url
        )
```

---

## Performance Optimization

### 1. Batch Processing

#### Julia

```julia
# Batch multiple readings into a single message
function send_batch_readings(sender::SensorSender, readings::Vector{Tuple{String, Float64, String}})
    batch = SensorBatch()
    
    for (sensor_id, value, unit) in readings
        reading = SensorReading(sensor_id, value, unit)
        add_reading(batch, reading)
    end
    
    df = to_dataframe(batch)
    
    # Convert to Arrow IPC
    import Arrow
    table = Arrow.Table(df)
    
    # Serialize to Arrow IPC
    import IOBuffer
    buf = IOBuffer()
    Arrow.write(buf, table)
    
    arrow_data = take!(buf)
    
    # Send as single message
    smartsend(
        "/sensors/batch",
        [("batch", arrow_data, "arrowtable")],
        broker_url=sender.broker_url
    )
end
```

#### JavaScript

```javascript
// Batch multiple readings into a single message
async function sendBatchReadings(sender, readings) {
    const batch = new SensorBatch();
    
    for (const [sensorId, value, unit] of readings) {
        const reading = new SensorReading(sensorId, value, unit);
        batch.addReading(reading);
    }
    
    const df = batch.toDataFrame();
    
    // Convert to Arrow IPC
    const arrow = require('apache-arrow');
    const schema = new arrow.Schema([
        new arrow.Field('sensor_id', arrow.string()),
        new arrow.Field('timestamp', arrow.string()),
        new arrow.Field('value', arrow.float64()),
        new arrow.Field('unit', arrow.string())
    ]);
    
    const arrays = {
        sensor_id: new arrow.StringArray(df.sensor_id),
        timestamp: new arrow.StringArray(df.timestamp),
        value: new arrow.Float64Array(df.value),
        unit: new arrow.StringArray(df.unit)
    };
    
    const recordBatch = arrow.RecordBatch.fromArrays(schema, arrays, df.value.length);
    const buffer = arrow.tableFromBatches([recordBatch]).toBuffer();
    const arrow_data = new Uint8Array(buffer);
    
    // Send as single message
    const data = [["batch", arrow_data, "arrowtable"]];
    await NATSBridge.smartsend(
        "/sensors/batch",
        data,
        { broker_url: sender.broker_url }
    );
}
```

### 2. Connection Reuse

#### Julia

```julia
# Reuse NATS connections
function create_connection_pool()
    connections = Dict{String, NATS.Connection}()
    
    function get_connection(nats_url::String)::NATS.Connection
        if !haskey(connections, nats_url)
            connections[nats_url] = NATS.connect(nats_url)
        end
        return connections[nats_url]
    end
    
    function close_all()
        for conn in values(connections)
            NATS.drain(conn)
        end
        empty!(connections)
    end
    
    return (get_connection=get_connection, close_all=close_all)
end
```

#### Python

```python
# Reuse NATS connections
import asyncio
import nats

class ConnectionPool:
    def __init__(self):
        self.connections = {}
    
    async def get_connection(self, nats_url: str):
        if nats_url not in self.connections:
            self.connections[nats_url] = await nats.connect(nats_url)
        return self.connections[nats_url]
    
    async def close_all(self):
        for conn in self.connections.values():
            await conn.drain()
        self.connections.clear()
```

### 3. Caching

#### Julia

```julia
# Cache file server responses
using Base.Threads

const file_cache = Dict{String, Vector{UInt8}}()

function fetch_with_caching(url::String, max_retries::Int, base_delay::Int, max_delay::Int, correlation_id::String)::Vector{UInt8}
    if haskey(file_cache, url)
        return file_cache[url]
    end
    
    # Fetch from file server
    data = _fetch_with_backoff(url, max_retries, base_delay, max_delay, correlation_id)
    
    # Cache the result
    file_cache[url] = data
    
    return data
end
```

#### Python

```python
# Cache file server responses
import asyncio
import threading
from natsbridge import fetch_with_backoff

file_cache = {}
cache_lock = threading.Lock()

async def fetch_with_caching(url, max_retries, base_delay, max_delay, correlation_id):
    with cache_lock:
        if url in file_cache:
            return file_cache[url]
    
    # Fetch from file server
    data = await fetch_with_backoff(url, max_retries, base_delay, max_delay, correlation_id)
    
    # Cache the result
    with cache_lock:
        file_cache[url] = data
    
    return data
```

---

## Best Practices

### 1. Error Handling

#### Julia

```julia
function safe_smartsend(subject::String, data::Vector{Tuple}, kwargs...)
    try
        return smartsend(subject, data; kwargs...)
    catch error
        println("Failed to send message: $(error)")
        return nothing
    end
end
```

#### JavaScript

```javascript
async function safeSmartSend(subject, data, options = {}) {
    try {
        return await NATSBridge.smartsend(subject, data, options);
    } catch (error) {
        console.error(`Failed to send message: ${error}`);
        return null;
    }
}
```

#### Python

```python
from typing import List, Tuple, Optional, Union

async def safe_smartsend(
    subject: str,
    data: List[Tuple[str, Any, str]],
    **kwargs
) -> Optional[Tuple[dict, str]]:
    try:
        return await smartsend(subject, data, **kwargs)
    except Exception as error:
        print(f"Failed to send message: {error}")
        return None
```

### 2. Logging

#### Julia

```julia
using Logging

function log_send(subject::String, data::Vector{Tuple}, correlation_id::String)
    @info "Sending to $subject: $(length(data)) payloads, correlation_id=$correlation_id"
end

function log_receive(correlation_id::String, num_payloads::Int)
    @info "Received message: $num_payloads payloads, correlation_id=$correlation_id"
end
```

#### Python

```python
import logging
from typing import List, Tuple, Any

logger = logging.getLogger(__name__)

def log_send(subject: str, data: List[Tuple[str, Any, str]], correlation_id: str):
    logger.info(f"Sending to {subject}: {len(data)} payloads, correlation_id={correlation_id}")

def log_receive(correlation_id: str, num_payloads: int):
    logger.info(f"Received message: {num_payloads} payloads, correlation_id={correlation_id}")
```

---

## Conclusion

This walkthrough covered:

- Building a chat application with rich media support
- Building a file transfer system with claim-check pattern
- Building a streaming data pipeline for sensor data

For more information, check the [API documentation](../src/README.md) and [test examples](../test/).

---

## License

MIT
