QuickDID is a high-performance AT Protocol identity resolution service written in Rust. It provides handle-to-DID resolution with Redis-backed caching and queue processing.

QuickDID - Development Guide for Claude#

Overview#

QuickDID is a high-performance AT Protocol identity resolution service written in Rust. It provides handle-to-DID resolution with Redis-backed caching and queue processing.

Configuration#

QuickDID follows the 12-factor app methodology and uses environment variables exclusively for configuration. There are no command-line arguments except for --version and --help.

Configuration is validated at startup, and the service will exit with specific error codes if validation fails:

  • error-quickdid-config-1: Missing required environment variable
  • error-quickdid-config-2: Invalid configuration value
  • error-quickdid-config-3: Invalid TTL value (must be positive)
  • error-quickdid-config-4: Invalid timeout value (must be positive)

Common Commands#

Building and Running#

# Build the project
cargo build

# Run in debug mode (requires environment variables)
HTTP_EXTERNAL=localhost:3007 SERVICE_KEY=did:key:z42tmZxD2mi1TfMKSFrsRfednwdaaPNZiiWHP4MPgcvXkDWK cargo run

# Run tests
cargo test

# Type checking
cargo check

# Linting
cargo clippy

# Show version
cargo run -- --version

# Show help
cargo run -- --help

Development with VS Code#

The project includes a .vscode/launch.json configuration for debugging with Redis integration. Use the "Debug executable 'quickdid'" launch configuration.

Architecture#

Core Components#

  1. Handle Resolution (src/handle_resolver/)

    • BaseHandleResolver: Core resolution using DNS and HTTP
    • RateLimitedHandleResolver: Semaphore-based rate limiting with optional timeout
    • CachingHandleResolver: In-memory caching layer
    • RedisHandleResolver: Redis-backed persistent caching
    • SqliteHandleResolver: SQLite-backed persistent caching
    • Uses binary serialization via HandleResolutionResult for space efficiency
    • Resolution stack: Cache → RateLimited (optional) → Base → DNS/HTTP
  2. Binary Serialization (src/handle_resolution_result.rs)

    • Compact storage format using bincode
    • Strips DID prefixes for did:web and did:plc methods
    • Stores: timestamp (u64), method type (i16), payload (String)
  3. Queue System (src/queue/)

    • Supports MPSC (in-process), Redis, SQLite, and no-op adapters
    • HandleResolutionWork items processed asynchronously
    • Redis uses reliable queue pattern (LPUSH/RPOPLPUSH/LREM)
    • SQLite provides persistent queue with work shedding capabilities
  4. HTTP Server (src/http/)

    • XRPC endpoints for AT Protocol compatibility
    • Health check endpoint
    • DID document serving via .well-known

Key Technical Details#

DID Method Types#

  • did:web: Web-based DIDs, prefix stripped for storage
  • did:plc: PLC directory DIDs, prefix stripped for storage
  • Other DID methods stored with full identifier

Redis Integration#

  • Caching: Uses MetroHash64 for key generation, stores binary data
  • Queuing: Reliable queue with processing/dead letter queues
  • Key Prefixes: Configurable via QUEUE_REDIS_PREFIX environment variable

Handle Resolution Flow#

  1. Check cache (Redis/SQLite/in-memory based on configuration)
  2. If cache miss and rate limiting enabled:
    • Acquire semaphore permit (with optional timeout)
    • If timeout configured and exceeded, return error
  3. Perform DNS TXT lookup or HTTP well-known query
  4. Cache result with appropriate TTL
  5. Return DID or error

Environment Variables#

Required#

  • HTTP_EXTERNAL: External hostname for service endpoints (e.g., localhost:3007)
  • SERVICE_KEY: Private key for service identity (DID format)

Optional#

  • HTTP_PORT: Server port (default: 8080)
  • PLC_HOSTNAME: PLC directory hostname (default: plc.directory)
  • REDIS_URL: Redis connection URL for caching
  • SQLITE_URL: SQLite database URL for caching (e.g., sqlite:./quickdid.db)
  • QUEUE_ADAPTER: Queue type - 'mpsc', 'redis', 'sqlite', 'noop', or 'none' (default: mpsc)
  • QUEUE_REDIS_PREFIX: Redis key prefix for queues (default: queue:handleresolver:)
  • QUEUE_WORKER_ID: Worker ID for queue operations (default: worker1)
  • QUEUE_BUFFER_SIZE: Buffer size for MPSC queue (default: 1000)
  • QUEUE_SQLITE_MAX_SIZE: Max queue size for SQLite work shedding (default: 10000)
  • CACHE_TTL_MEMORY: TTL for in-memory cache in seconds (default: 600)
  • CACHE_TTL_REDIS: TTL for Redis cache in seconds (default: 7776000)
  • CACHE_TTL_SQLITE: TTL for SQLite cache in seconds (default: 7776000)
  • QUEUE_REDIS_TIMEOUT: Redis blocking timeout in seconds (default: 5)
  • RESOLVER_MAX_CONCURRENT: Maximum concurrent handle resolutions (default: 0 = disabled)
  • RESOLVER_MAX_CONCURRENT_TIMEOUT_MS: Timeout for acquiring rate limit permit in ms (default: 0 = no timeout)
  • RUST_LOG: Logging level (e.g., debug, info)

Error Handling#

All error strings must use this format:

error-quickdid-<domain>-<number> <message>: <details>

Current error domains and examples:

  • config: Configuration errors (e.g., error-quickdid-config-1 Missing required environment variable)
  • resolve: Handle resolution errors (e.g., error-quickdid-resolve-1 Failed to resolve subject)
  • queue: Queue operation errors (e.g., error-quickdid-queue-1 Failed to push to queue)
  • cache: Cache-related errors (e.g., error-quickdid-cache-1 Redis pool creation failed)
  • result: Serialization errors (e.g., error-quickdid-result-1 System time error)
  • task: Task processing errors (e.g., error-quickdid-task-1 Queue adapter health check failed)

Errors should be represented as enums using the thiserror library.

Avoid creating new errors with the anyhow!(...) or bail!(...) macro.

Testing#

Running Tests#

# Run all tests
cargo test

# Run with Redis integration tests
TEST_REDIS_URL=redis://localhost:6379 cargo test

# Run specific test module
cargo test handle_resolver::tests

Test Coverage Areas#

  • Handle resolution with various DID methods
  • Binary serialization/deserialization
  • Redis caching and expiration
  • Queue processing logic
  • HTTP endpoint responses

Development Patterns#

Error Handling#

  • Uses strongly-typed errors with thiserror for all modules
  • Each error has a unique identifier following the pattern error-quickdid-<domain>-<number>
  • Graceful fallbacks when Redis/SQLite is unavailable
  • Detailed tracing for debugging
  • Avoid using anyhow!() or bail!() macros - use proper error types instead

Performance Optimizations#

  • Binary serialization reduces storage by ~40%
  • MetroHash64 for fast key generation
  • Connection pooling for Redis
  • Configurable TTLs for cache entries
  • Rate limiting via semaphore-based concurrency control

Code Style#

  • Follow existing Rust idioms and patterns
  • Use tracing for logging, not println!
  • Prefer Arc for shared state across async tasks
  • Handle errors explicitly, avoid .unwrap() in production code

Common Tasks#

Adding a New DID Method#

  1. Update DidMethodType enum in handle_resolution_result.rs
  2. Modify parse_did() and to_did() methods
  3. Add test cases for the new method type

Modifying Cache TTL#

  • For in-memory: Set CACHE_TTL_MEMORY environment variable
  • For Redis: Set CACHE_TTL_REDIS environment variable
  • For SQLite: Set CACHE_TTL_SQLITE environment variable

Debugging Resolution Issues#

  1. Enable debug logging: RUST_LOG=debug
  2. Check Redis cache: redis-cli GET "handle:<hash>"
  3. Check SQLite cache: sqlite3 quickdid.db "SELECT * FROM handle_resolution_cache;"
  4. Monitor queue processing in logs
  5. Check rate limiting: Look for "Rate limit permit acquisition timed out" errors
  6. Verify DNS/HTTP connectivity to AT Protocol infrastructure

Dependencies#

  • atproto-identity: Core AT Protocol identity resolution
  • bincode: Binary serialization
  • deadpool-redis: Redis connection pooling
  • metrohash: Fast non-cryptographic hashing
  • tokio: Async runtime
  • axum: Web framework