Skip to content

About

Sample Application using Apache Iggy integration with Rust

Resources

Contributing

Security policy

Stars

4 stars

Watchers

0 watching

Forks

Repository files navigation

Iggy Sample Application

CI Release License: MIT Rust

A comprehensive demonstration of Apache Iggy message streaming integrated with Axum web framework in Rust.

Overview

This project showcases how to build a production-ready message streaming service using:

  • Apache Iggy server 0.10.0 - High-performance message streaming with io_uring shared-nothing architecture and optional VSR clustering
  • Iggy Rust SDK 0.12.0 - Paired with the server 0.10 release line; each SDK speaks only its own release's wire protocol, so SDK and server upgrade together (ADR-0001, ADR-0002)
  • Axum 0.8 - Ergonomic and modular Rust web framework
  • Tokio - Async runtime for Rust

Apache Iggy is capable of processing millions of messages per second with ultra-low latency, supporting TCP, QUIC, WebSocket, and HTTP transport protocols.

Features

Core Functionality

  • RESTful API for message publishing and consumption
  • True batch message sending (single network call for multiple messages)
  • Graceful shutdown with SIGTERM/SIGINT handling
  • Input validation and sanitization for resource names
  • Comprehensive error handling with Result types (no unwrap()/expect() in production code)
  • Zero clippy warnings - strict lints enforced, no #[allow(...)] in production code
  • Stream and topic management endpoints
  • Health checks and service statistics
  • Domain-driven event modeling (User, Order, Generic events)
  • Partition-based message routing

Production-Ready Features

  • Connection resilience with automatic reconnection and exponential backoff
  • Circuit breaker with fail-fast rejection and token-limited half-open recovery probing
  • Rate limiting with token bucket algorithm (configurable RPS and burst)
  • API key authentication with constant-time comparison (timing attack resistant)
  • Request ID propagation for distributed tracing
  • Configurable CORS with origin whitelist support
  • Background stats caching to avoid expensive queries on each request

Development & Testing

  • Docker Compose setup for local development
  • Comprehensive test suite (285 unit tests, 42 integration tests, 18 model tests, a metrics exporter smoke test, plus cluster and connectors tests that run weekly against their compose stacks)
  • Integration tests with testcontainers (auto-spins Iggy server)
  • Fuzz testing for input validation functions

Architecture

┌─────────────────────────────────────────────────────────────┐
│                      Axum HTTP Server                       │
│                        (Port 8000)                          │
├─────────────────────────────────────────────────────────────┤
│  Middleware Stack                                           │
│  Rate Limit → Auth → Request ID → Timeout → Tracing → CORS  │
├─────────────────────────────────────────────────────────────┤
│  Handlers                                                   │
│  ├── health.rs    - Health/readiness checks, stats          │
│  ├── messages.rs  - Send/poll messages                      │
│  ├── streams.rs   - Stream CRUD operations                  │
│  └── topics.rs    - Topic CRUD operations                   │
├─────────────────────────────────────────────────────────────┤
│  Services                                                   │
│  ├── producer.rs  - Message publishing logic                │
│  └── consumer.rs  - Message consumption logic               │
├─────────────────────────────────────────────────────────────┤
│  IggyClientWrapper (with auto-reconnection)                 │
│  High-level wrapper around Iggy SDK                         │
├─────────────────────────────────────────────────────────────┤
│  Observability Stack                                        │
│  Prometheus (9090) → Grafana (3001) + Web UI (3000/ui)      │
├─────────────────────────────────────────────────────────────┤
│  Apache Iggy Server                                         │
│  TCP (8090) / QUIC (8080) / HTTP (3000) / Metrics (/metrics)│
└─────────────────────────────────────────────────────────────┘

Prerequisites

  • Rust 1.95+ (edition 2024, MSRV: 1.95.0)
  • Docker & Docker Compose
  • curl or httpie (for testing)

Quick Start

1. Clone and Setup

cd iggy_sample
cp .env.example .env

2. Start the Full Stack

# Start Iggy server with observability stack
docker-compose up -d

This starts:

  • Iggy Server - Message streaming (ports 8090, 8080, 3000)
  • Iggy Web UI - Served by the server itself at /ui (port 3000)
  • iggy-pat-init - One-shot: creates the token Prometheus scrapes Iggy's /metrics with
  • Prometheus - Metrics collection (port 9090)
  • Grafana - Visualization dashboards (port 3001)

Upgrading from an earlier server: no release reads the previous one's data (0.10 refuses to start on 0.9 data), so the compose file stores data in a volume named for the release, now iggy_data_0_10. Older ones can be removed with docker volume rm iggy_sample_iggy_data_0_9 iggy_sample_iggy_data (the prefix is the compose project name, by default the checkout's directory name).

3. Run the Application

cargo run

The server will start on http://localhost:8000 (or the port specified in .env).

4. Verify It's Working

curl http://localhost:8000/health

Expected response:

{
  "status": "healthy",
  "iggy_connected": true,
  "version": "0.5.0",
  "timestamp": "2024-01-15T10:30:00Z"
}

5. Access the Dashboards

Service URL Credentials
Sample App API http://localhost:8000 -
Iggy HTTP API http://localhost:3000 -
Iggy Web UI http://localhost:3000/ui iggy / iggy
Prometheus http://localhost:9090 -
Grafana http://localhost:3001 admin / admin

API Reference

Health & Status

Endpoint Method Description
/health GET Health check with Iggy connection status
/ready GET Kubernetes readiness probe (200 if ready)
/stats GET Service statistics (streams, messages, uptime)
/cluster GET Cluster nodes with their roles and health (single-node when clustering is off)

HTTP Sink Receiver (connectors demo)

Routed only when SINK_INBOUND_TOKEN (or SINK_INBOUND_TOKEN_FILE) is set; see the connectors demo.

Endpoint Method Description
/sink/inbound POST Receive an ndjson batch from the HTTP sink connector (Authorization: Bearer <token>)
/sink/inbound GET What the sink delivered: counters and the newest deliveries

Messages (Default Stream/Topic)

Endpoint Method Description
/messages POST Send a single message
/messages GET Poll messages
/messages/batch POST Send multiple messages

Messages (Specific Stream/Topic)

Endpoint Method Description
/streams/{stream}/topics/{topic}/messages POST Send to specific topic
/streams/{stream}/topics/{topic}/messages GET Poll from specific topic

Stream Management

Endpoint Method Description
/streams GET List all streams
/streams POST Create a new stream
/streams/{name} GET Get stream details
/streams/{name} DELETE Delete a stream

Topic Management

Endpoint Method Description
/streams/{stream}/topics GET List topics in stream
/streams/{stream}/topics POST Create a topic
/streams/{stream}/topics/{topic} GET Get topic details
/streams/{stream}/topics/{topic} DELETE Delete a topic

Usage Examples

Send a User Event

curl -X POST http://localhost:8000/messages \
  -H "Content-Type: application/json" \
  -d '{
    "event": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "event_type": "user.created",
      "timestamp": "2024-01-15T10:30:00Z",
      "payload": {
        "type": "User",
        "data": {
          "action": "Created",
          "user_id": "550e8400-e29b-41d4-a716-446655440001",
          "email": "user@example.com",
          "name": "John Doe"
        }
      }
    }
  }'

Send an Order Event

curl -X POST http://localhost:8000/messages \
  -H "Content-Type: application/json" \
  -d '{
    "event": {
      "id": "550e8400-e29b-41d4-a716-446655440002",
      "event_type": "order.created",
      "timestamp": "2024-01-15T10:31:00Z",
      "payload": {
        "type": "Order",
        "data": {
          "action": "Created",
          "order_id": "550e8400-e29b-41d4-a716-446655440003",
          "user_id": "550e8400-e29b-41d4-a716-446655440001",
          "items": [
            {
              "product_id": "550e8400-e29b-41d4-a716-446655440004",
              "quantity": 2,
              "unit_price": 29.99
            }
          ],
          "total_amount": 59.98
        }
      }
    },
    "partition_key": "user-550e8400"
  }'

Send a Generic Event

curl -X POST http://localhost:8000/messages \
  -H "Content-Type: application/json" \
  -d '{
    "event": {
      "id": "550e8400-e29b-41d4-a716-446655440005",
      "event_type": "custom.event",
      "timestamp": "2024-01-15T10:32:00Z",
      "payload": {
        "type": "Generic",
        "data": {
          "custom_field": "any value",
          "nested": {"key": "value"}
        }
      }
    }
  }'

Poll Messages

# Poll from partition 1, starting at offset 0
curl "http://localhost:8000/messages?partition_id=1&count=10&offset=0"

# Poll with auto-commit
curl "http://localhost:8000/messages?partition_id=1&count=10&auto_commit=true"

A poll returns the messages that are Events. Anything else in the topic (another producer's payload, such as a webhook body from the connectors demo) is left out and counted in skipped; current_offset moves past it.

Send Batch Messages

curl -X POST http://localhost:8000/messages/batch \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440006",
        "event_type": "batch.event.1",
        "timestamp": "2024-01-15T10:33:00Z",
        "payload": {"type": "Generic", "data": {"index": 1}}
      },
      {
        "id": "550e8400-e29b-41d4-a716-446655440007",
        "event_type": "batch.event.2",
        "timestamp": "2024-01-15T10:33:01Z",
        "payload": {"type": "Generic", "data": {"index": 2}}
      }
    ]
  }'

Create a Stream

curl -X POST http://localhost:8000/streams \
  -H "Content-Type: application/json" \
  -d '{"name": "my-stream"}'

Create a Topic

curl -X POST http://localhost:8000/streams/my-stream/topics \
  -H "Content-Type: application/json" \
  -d '{"name": "my-topic", "partitions": 3}'

Per-topic storage options go in an optional options object. They are fixed when the topic is created; any you omit take the server's defaults.

curl -X POST http://localhost:8000/streams/my-stream/topics \
  -H "Content-Type: application/json" \
  -d '{
    "name": "payments",
    "partitions": 3,
    "options": {
      "durability": "persisted",
      "message_expiry": "30days",
      "max_topic_size": "10 GiB",
      "segment_size": "256 MiB"
    }
  }'
Option Values Server default
durability replicated or persisted: what a send waits for replicated
consumer_offset_durability The same, for explicit consumer-offset stores and deletes replicated
message_expiry A duration ("7days", "1h 30m") or "unlimited" unlimited
max_topic_size A size ("10 GiB") or "unlimited"; at least the segment size unlimited
segment_size 1 MiB to 1 GiB, in multiples of 512 B 1 GiB
messages_required_to_save 1 to 16777216 buffered messages per segment write 1024
size_of_messages_required_to_save Up to 1 GiB buffered per segment write 1 MiB
preallocate_segments true reserves each segment's full size on disk false
replication_factor Nodes that store each partition: 1 to 32, at most the node count (cluster demo) every node ("all"); 1 on a single node

An invalid value answers 400 naming the option (options.segment_size). An unknown key, including an option placed beside options instead of inside it, answers 422, so a typo never silently leaves a topic on a default. The durable storage guide explains what each option does.

Topic reads (GET /streams/{stream}/topics and /topics/{topic}) report every option with its value, in the form POST accepts, and its source:

"options": {
  "durability": {"value": "persisted", "source": "explicit"},
  "max_topic_size": {"value": "unlimited", "source": "default"},
  "segment_size": {"value": "256 MiB", "source": "explicit"}
}

explicit means the creating client sent the value, and default that the server filled it in. unset (with a null value) means the topic stores none. SDK 0.12 always sends both durability options, so on topics created through the SDK, this gateway included, they always read explicit.

List Streams

curl http://localhost:8000/streams

Get Statistics

curl http://localhost:8000/stats

Configuration

Configuration is loaded from environment variables. See .env.example for the common options; the tables below list all of them.

Server & Iggy Connection

Variable Default Description
HOST 0.0.0.0 Server bind address
PORT 8000 Server port
IGGY_CONNECTION_STRING iggy://iggy:iggy@localhost:8090 Iggy connection string; several comma-separated addresses are seeds, tried in order (format)
IGGY_STREAM sample-stream Default stream name
IGGY_TOPIC events Default topic name
IGGY_PARTITIONS 3 Partitions for default topic
IGGY_TOPIC_<OPTION> (unset) One per-topic option for the default topic, e.g. IGGY_TOPIC_DURABILITY=persisted, IGGY_TOPIC_SEGMENT_SIZE="256 MiB". Any option in the topic options table, same values. Applied only when the gateway creates the topic; for an existing topic, each differing option is logged as a warning
RUST_LOG info Log level (trace, debug, info, warn, error)

Connection Resilience

Variable Default Description
MAX_RECONNECT_ATTEMPTS 0 Max reconnect attempts (0 = infinite)
RECONNECT_BASE_DELAY_MS 1000 Base delay for exponential backoff
RECONNECT_MAX_DELAY_MS 30000 Max delay between reconnection attempts
HEALTH_CHECK_INTERVAL_SECS 30 Connection health check interval

Rate Limiting & Security

Variable Default Description
RATE_LIMIT_RPS 100 Requests per second (0 = disabled)
RATE_LIMIT_BURST 50 Instantaneous bucket capacity (replaces, not adds to, the default)
API_KEY (none) API key for authentication (disabled if not set)
AUTH_BYPASS_PATHS /health,/ready Comma-separated paths that bypass auth
CORS_ALLOWED_ORIGINS * Comma-separated allowed origins
TRUSTED_PROXIES (none) Comma-separated CIDR ranges of your reverse proxies; client IPs are taken from forwarded headers only when the peer is inside one. Required when API_KEY is set
ALLOW_UNVERIFIED_CLIENT_IPS false Start with API_KEY but no TRUSTED_PROXIES, for local development only: brute-force protection then throttles but never blocks
SINK_INBOUND_TOKEN (none) Bearer token of the HTTP sink receiver; 32 to 1024 printable characters. Unset, /sink/inbound does not exist
SINK_INBOUND_TOKEN_FILE (none) A file holding that token instead (trailing newline ignored); set one or the other

Message Limits & Observability

Variable Default Description
BATCH_MAX_SIZE 1000 Max messages per batch send
POLL_MAX_COUNT 100 Max messages per poll
STATS_CACHE_TTL_SECS 5 Stats cache refresh interval

Connection String Format

iggy://username:password@host:port

Example:

iggy://iggy:iggy@localhost:8090

For a cluster, list several addresses. They are seeds: the gateway tries them in order whenever it builds a client (at startup and on reconnect), and once signed in the SDK follows the cluster's roster itself.

iggy://iggy:iggy@10.29.0.10:8090,10.29.0.11:8090,10.29.0.12:8090

Options after ? go to the SDK. The gateway caps the SDK's reconnection retries at 3 unless you set the option yourself (ADR-0003). It rides out a server restart of a few seconds. After that, requests get an immediate 503 while the server is down, instead of each waiting up to 30 s. To keep retrying without a limit, the SDK's own default:

iggy://iggy:iggy@localhost:8090?reconnection_retries=unlimited

Project Structure

iggy_sample/
├── .github/
│   ├── workflows/
│   │   ├── ci.yml              # Main CI (tests, lint, coverage)
│   │   ├── pr.yml              # PR checks (size, commits, docs)
│   │   ├── release.yml         # Release builds, GitHub release, docs deploy
│   │   ├── verify-release.yml  # Checks each green release run
│   │   └── extended-tests.yml  # Weekly cluster, connectors, benchmark and memory tests
│   ├── dependabot.yml          # Automated dependency updates
│   └── pull_request_template.md
├── Cargo.toml              # Dependencies and metadata
├── Dockerfile              # Multi-stage build for production
├── docker-compose.yaml     # Local development setup
├── docker-compose.cluster.yaml # Three-node cluster demo (docs/cluster-demo.md)
├── docker-compose.connectors.yaml # Connectors demo overlay (docs/connectors-demo.md)
├── connectors/
│   ├── Dockerfile          # Connector runtime 0.6.0 + HTTP source/sink plugins
│   ├── init.sh             # Generates the demo's secrets and runtime token
│   ├── entrypoint.sh       # Renders the runtime config from the templates
│   ├── healthcheck.sh      # Healthy once both connectors run
│   └── templates/          # Runtime and connector TOML templates
├── deny.toml               # License/security policy (cargo-deny)
├── .env.example            # Environment variable template
├── CLAUDE.md               # Project documentation for AI assistants
├── README.md               # This file
├── src/
│   ├── main.rs             # Application entry point
│   ├── lib.rs              # Library exports
│   ├── config.rs           # Configuration from environment
│   ├── error.rs            # Error types with HTTP status codes
│   ├── state.rs            # Shared application state
│   ├── routes.rs           # Route definitions
│   ├── utils.rs            # Shutdown-signal helpers
│   ├── metrics.rs          # Prometheus metrics export
│   ├── iggy_client/        # Iggy SDK wrapper module
│   ├── validation.rs       # Input validation utilities
│   ├── middleware/
│   │   ├── mod.rs          # Middleware exports
│   │   ├── rate_limit.rs   # Token bucket rate limiting
│   │   ├── auth.rs         # API key authentication
│   │   ├── ip.rs           # Client IP extraction (trusted proxies)
│   │   ├── timeout.rs      # X-Request-Timeout handling
│   │   └── request_id.rs   # Request ID propagation
│   ├── models/
│   │   ├── mod.rs          # Model exports
│   │   ├── event.rs        # Domain events (uses rust_decimal)
│   │   ├── topic_options.rs # Per-topic options: validation and read-back
│   │   ├── sink.rs         # GET /sink/inbound response types
│   │   └── api.rs          # API request/response types
│   ├── services/
│   │   ├── mod.rs          # Service exports
│   │   ├── producer.rs     # Message producer service
│   │   ├── consumer.rs     # Message consumer service
│   │   └── sink_inbox.rs   # HTTP sink receiver: token, batch parsing, retention
│   └── handlers/
│       ├── mod.rs          # Handler exports
│       ├── cluster.rs      # GET /cluster
│       ├── health.rs       # Health endpoints
│       ├── messages.rs     # Message endpoints
│       ├── sink.rs         # POST/GET /sink/inbound
│       ├── streams.rs      # Stream management
│       ├── topics.rs       # Topic management
│       └── util.rs         # Shared handler utilities
├── scripts/
│   ├── cluster-failover-demo.sh # Kills the cluster leader under traffic
│   ├── connectors-demo.sh   # Runs both connector flows end to end
│   └── verify-release.sh    # Checks a release run step by step
├── tests/
│   ├── cluster_tests.rs     # Three-node cluster tests (ignored; weekly)
│   ├── connectors_tests.rs  # Connectors demo tests (ignored; weekly)
│   ├── integration_tests.rs # End-to-end API tests
│   ├── metrics_smoke_test.rs # Prometheus exporter smoke test
│   └── model_tests.rs       # Unit tests for models
└── fuzz/
    ├── Cargo.toml           # Fuzz testing configuration
    └── fuzz_targets/
        └── fuzz_validation.rs # Validation function fuzz tests

Testing

Run Unit Tests

cargo test

Run Integration Tests

Integration tests use testcontainers and spin up their own Iggy server automatically — only Docker needs to be running:

cargo test --test integration_tests

Run with Coverage

cargo install cargo-tarpaulin
cargo tarpaulin --out Html

Docker

Build and Run with Docker Compose

# Start everything (Iggy + App)
docker-compose up -d

# View logs
docker-compose logs -f app

# Stop
docker-compose down

Three-Node Cluster

docker-compose.cluster.yaml runs a three-node Iggy cluster with the gateway on port 8001, and scripts/cluster-failover-demo.sh kills the leader under traffic to show failover. See the cluster demo guide.

echo "IGGY_CLUSTER_SHARED_SECRET=$(openssl rand -hex 32)" >> .env
docker compose -f docker-compose.cluster.yaml up -d --build --wait
curl http://localhost:8001/cluster
scripts/cluster-failover-demo.sh

Connectors

docker-compose.connectors.yaml adds Iggy's connector runtime to the main stack, with upstream's HTTP source (signed webhooks into a persisted topic, host port 8085) and HTTP sink (events back to the gateway's POST /sink/inbound). It generates its own secrets. See the connectors demo guide, including what each direction guarantees.

docker compose -f docker-compose.yaml -f docker-compose.connectors.yaml up -d --build --wait
scripts/connectors-demo.sh

Build Docker Image Manually

docker build -t iggy-sample .
docker run -p 8000:8000 -e IGGY_CONNECTION_STRING=iggy://iggy:iggy@host.docker.internal:8090 iggy-sample

Observability Stack

The project includes a complete observability stack for monitoring and managing Iggy.

Components

Component Port Description
Iggy Server 3000 HTTP API + Prometheus metrics at /metrics (bearer token required since 0.9)
Sample App metrics 9091 (host) App Prometheus metrics (METRICS_PORT 9090 in-container)
Iggy Web UI 3000 (/ui) Dashboard for streams, topics, messages, users, served by the server
Prometheus 9090 Metrics collection with 15-day retention
Grafana 3001 Pre-configured dashboards for visualization
Connector runtime 8085 (webhooks) With the connectors overlay; its API (8081) and the webhook admin listener (8086) stay on the compose network

Iggy Web UI

The Iggy Web UI provides a comprehensive dashboard:

  • Streams & Topics: Create, browse, and delete streams and topics
  • Messages: Browse and inspect messages in real-time
  • Users: Manage users and permissions
  • Server Health: Monitor connections and server status

Since 0.9 the server embeds the Web UI (IGGY_HTTP_WEB_UI=true in docker-compose), so there is no separate container. Access it at http://localhost:3000/ui with credentials iggy/iggy.

Grafana Dashboards

Pre-configured dashboards are automatically provisioned:

  • Iggy Overview: Server status, request rates, message throughput, latency percentiles
  • Iggy Connectors: Connectors running, webhook requests, messages through each connector, the gateway's sink receiver, stage latency, errors (with the connectors overlay)

Access at http://localhost:3001 with credentials admin/admin.

Prometheus Metrics

Iggy exposes Prometheus-compatible metrics:

Since server 0.9, Iggy's /metrics requires a bearer credential (a JWT or a personal access token). The iggy-pat-init service creates a prometheus token on every docker-compose up and writes it to a volume Prometheus reads through credentials_file; the same token works for manual queries:

# View raw metrics from Iggy, with the token iggy-pat-init wrote
TOKEN=$(docker-compose run --rm --no-deps --entrypoint cat iggy-pat-init /secrets/iggy_pat)
curl -H "Authorization: Bearer $TOKEN" http://localhost:3000/metrics

# View the sample app's own metrics (message counters, reconnects,
# circuit breaker state; host port 9091 under docker-compose)
curl http://localhost:9091/metrics

# Query via Prometheus
curl 'http://localhost:9090/api/v1/query?query=up{job="iggy"}'

Configuration Files

observability/
├── iggy-pat-init.sh             # Creates the /metrics scrape token
├── prometheus/
│   ├── prometheus.yml           # Scrape configuration
│   └── connectors.yml           # Connector scrape jobs (connectors overlay)
└── grafana/
    └── provisioning/
        ├── datasources/
        │   └── datasources.yml  # Prometheus datasource
        └── dashboards/
            ├── dashboards.yml   # Dashboard provisioning
            ├── iggy-overview.json
            └── iggy-connectors.json

Adding OpenTelemetry (Optional)

Iggy supports OpenTelemetry for distributed tracing:

# Add to iggy service in docker-compose.yaml
environment:
  - IGGY_TELEMETRY_ENABLED=true
  - IGGY_TELEMETRY_SERVICE_NAME=iggy
  - IGGY_TELEMETRY_LOGS_TRANSPORT=grpc
  - IGGY_TELEMETRY_LOGS_ENDPOINT=http://otel-collector:4317
  - IGGY_TELEMETRY_TRACES_TRANSPORT=grpc
  - IGGY_TELEMETRY_TRACES_ENDPOINT=http://otel-collector:4317

Event Schema

Events follow a structured format with type-safe payloads:

{
  "id": "uuid",
  "event_type": "domain.action",
  "timestamp": "ISO8601",
  "payload": {
    "type": "User|Order|Generic",
    "data": { ... }
  },
  "correlation_id": "optional-uuid",
  "source": "optional-service-name"
}

Supported Event Types

User Events

  • Created - New user registration
  • Updated - User profile update
  • Deleted - User account deletion
  • LoggedIn - User authentication

Order Events

  • Created - New order placed
  • Updated - Order status change
  • Cancelled - Order cancellation
  • Shipped - Order shipment

Generic Events

  • Any JSON payload for flexible use cases

Error Handling

All errors return structured JSON responses:

{
  "error": "error_type",
  "message": "Human-readable message",
  "details": "Optional additional context"
}
Error Type HTTP Status Description
connection_failed 503 Iggy server unavailable
disconnected 503 Lost connection during operation
connection_reset 503 Connection was reset by peer
circuit_open 503 Circuit breaker open, failing fast
not_accepted 503 Iggy did not admit the request; nothing was applied, safe to retry
outcome_unknown 503 Iggy could not confirm whether the request was applied; resending may duplicate it
incompatible_broker 502 Iggy speaks a protocol version this build cannot use
timeout 504 Iggy operation exceeded the timeout
stream_error 500 Stream operation failed
topic_error 500 Topic operation failed
send_error 500 Message send failed
poll_error 500 Message poll failed
not_found 404 Resource not found
bad_request 400 Invalid request data

One Iggy outcome is not an error: when the server reports that a write was already applied but its reply is gone (RequestAlreadyApplied), the service answers 202 Accepted with {"status": "applied", "message": "..."}, so clients treat the write as done and do not resend it.

Security

This application implements multiple security layers suitable for production deployment.

Implemented Security Features

Feature Location Description
CORS src/routes.rs Configurable origin whitelist via CORS_ALLOWED_ORIGINS using tower-http
API Key Authentication src/middleware/auth.rs Constant-time comparison to prevent timing attacks
Rate Limiting src/middleware/rate_limit.rs Token bucket algorithm via Governor, configurable RPS and burst
Brute Force Protection src/middleware/auth.rs Per-IP tracking of failed authentication attempts
Input Validation src/validation.rs Sanitization of stream names, topic names, and event types
Trusted Proxy Support src/middleware/ip.rs X-Forwarded-For validation against configurable CIDR ranges
Request ID Propagation src/middleware/request_id.rs UUIDv4 generation for distributed tracing
Dependency Auditing .github/workflows/ci.yml, deny.toml cargo-deny advisory, license and source policy plus cargo-audit vulnerability scanning in CI (see SECURITY.md)
Vulnerability Reporting SECURITY.md Responsible disclosure policy

Not Included (by design)

Feature Reason
JWT API keys are appropriate for service-to-service auth; JWT adds complexity without benefit for this use case
RBAC/ABAC Single-purpose demonstration application, not a multi-tenant system
Encrypted cache Stats cache is internal, read-only, and contains no sensitive data

For deployment guidelines (reverse proxy configuration, trusted proxies), see CLAUDE.md.

Performance Considerations

  • Batch Operations: Use /messages/batch for high-throughput scenarios
  • Partitioning: Configure appropriate partition count for parallelism
  • Partition Keys: Use consistent keys for ordered processing within a partition
  • Auto-Commit: Enable for at-least-once delivery semantics
  • Connection Pooling: The client maintains persistent connections

Dependencies

Key dependencies (see Cargo.toml for full list):

Crate Version Purpose
iggy 0.12.0 Iggy Rust SDK
axum 0.8 Web framework
tokio 1.52 Async runtime
serde 1.0 Serialization
tracing 0.1 Structured logging
thiserror 2.0 Error handling
governor 0.10 Rate limiting (token bucket)
subtle 2.6 Constant-time comparison
tower-http 0.7 HTTP middleware (CORS, tracing)
rust_decimal 1.43 Exact decimal arithmetic for money
uuid 1.23 UUID generation
chrono 0.4 Date/time handling
testcontainers 0.27 Integration testing

CI/CD

This project uses GitHub Actions for continuous integration and deployment:

Workflow Trigger Description
ci.yml Push, PR, weekly Tests, linting, coverage, dependency policy, security audit
pr.yml PR Size checks, conventional commits, semver
release.yml Tag vX.Y.Z or vX.Y.Z-<suffix> Multi-platform builds, GitHub release, docs deploy
verify-release.yml Green Release run, or by hand Checks the run with scripts/verify-release.sh
extended-tests.yml Weekly Benchmarks, three-node cluster tests and failover demo, memory checks

Automated Checks

  • Formatting: cargo fmt --check
  • Linting: cargo clippy -- -D warnings, and shellcheck for scripts/
  • Tests: stable and beta on Linux, macOS and Windows, plus the MSRV on Linux
  • Coverage: cargo llvm-cov. CI's Code Coverage job writes the line total to its job summary, and on pull requests PR Checks' Coverage Diff warns below 60% of lines. Nothing is uploaded to a coverage service (TD-2026-09-03)
  • Dependency policy: cargo deny --locked check (advisories, bans, licenses, sources)
  • Security: cargo-audit vulnerability scanning; blocking on pushes and PRs, and the weekly run files an issue per new advisory

Dependabot

Automatically creates PRs for:

  • Cargo dependency updates, direct and transitive (weekly), except a version that needs a newer Rust than the MSRV (TD-2026-07-02)
  • GitHub Actions updates (weekly)

Documentation

See the docs/ directory for comprehensive guides covering event-driven architecture, partitioning strategies, durable storage configuration, and more.

Resources

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'feat: add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

About

Sample Application using Apache Iggy integration with Rust

Resources

Contributing

Security policy

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages