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 variableerror-quickdid-config-2: Invalid configuration valueerror-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#
-
Handle Resolution (
src/handle_resolver/)BaseHandleResolver: Core resolution using DNS and HTTPRateLimitedHandleResolver: Semaphore-based rate limiting with optional timeoutCachingHandleResolver: In-memory caching layerRedisHandleResolver: Redis-backed persistent cachingSqliteHandleResolver: SQLite-backed persistent caching- Uses binary serialization via
HandleResolutionResultfor space efficiency - Resolution stack: Cache → RateLimited (optional) → Base → DNS/HTTP
-
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)
-
Queue System (
src/queue/)- Supports MPSC (in-process), Redis, SQLite, and no-op adapters
HandleResolutionWorkitems processed asynchronously- Redis uses reliable queue pattern (LPUSH/RPOPLPUSH/LREM)
- SQLite provides persistent queue with work shedding capabilities
-
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 storagedid: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_PREFIXenvironment variable
Handle Resolution Flow#
- Check cache (Redis/SQLite/in-memory based on configuration)
- If cache miss and rate limiting enabled:
- Acquire semaphore permit (with optional timeout)
- If timeout configured and exceeded, return error
- Perform DNS TXT lookup or HTTP well-known query
- Cache result with appropriate TTL
- 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 cachingSQLITE_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
thiserrorfor 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!()orbail!()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
tracingfor logging, notprintln! - Prefer
Arcfor shared state across async tasks - Handle errors explicitly, avoid
.unwrap()in production code
Common Tasks#
Adding a New DID Method#
- Update
DidMethodTypeenum inhandle_resolution_result.rs - Modify
parse_did()andto_did()methods - Add test cases for the new method type
Modifying Cache TTL#
- For in-memory: Set
CACHE_TTL_MEMORYenvironment variable - For Redis: Set
CACHE_TTL_REDISenvironment variable - For SQLite: Set
CACHE_TTL_SQLITEenvironment variable
Debugging Resolution Issues#
- Enable debug logging:
RUST_LOG=debug - Check Redis cache:
redis-cli GET "handle:<hash>" - Check SQLite cache:
sqlite3 quickdid.db "SELECT * FROM handle_resolution_cache;" - Monitor queue processing in logs
- Check rate limiting: Look for "Rate limit permit acquisition timed out" errors
- Verify DNS/HTTP connectivity to AT Protocol infrastructure
Dependencies#
atproto-identity: Core AT Protocol identity resolutionbincode: Binary serializationdeadpool-redis: Redis connection poolingmetrohash: Fast non-cryptographic hashingtokio: Async runtimeaxum: Web framework