Skip to main content

Local Provider

The local provider is a lightweight, in-memory implementation of the cloud provider abstraction designed for local development and testing. It requires zero external dependencies and provides instant startup with no configuration overhead.

Overview

The local provider stores all data in memory using thread-safe ConcurrentHashMap structures. All data is lost when the application stops, making it ideal for:
  • Local Development - Quick iteration without AWS setup
  • Integration Testing - Fast, isolated test execution
  • CI/CD Pipelines - No external service dependencies
  • Prototyping - Rapid experimentation
  • Learning - Simple to understand and debug
Not suitable for:
  • Production deployments
  • Data persistence across restarts
  • Multi-instance deployments (no shared state)
  • Large datasets (memory-bound)

Quick Start

1. Add Dependency

Add to your build.gradle:

2. Configure Provider

Add to your application.yml:
That’s it! No credentials, no external services, no additional setup required.

3. Start the Hub

The Hub will log:

4. Optional Configuration

Configure cleanup and logging (defaults work for most cases):

Architecture

Storage Design

The local provider uses Java’s ConcurrentHashMap for thread-safe in-memory storage:

Thread Safety

All storage implementations use ConcurrentHashMap for thread-safe concurrent access:
  • No external locking required
  • Safe for multi-threaded request handling
  • Atomic operations for consistency
  • Read-write locks for complex operations (API key records)

Reactive Support

All operations return Project Reactor Mono<T> for consistency with the cloud provider abstraction:

Storage Components

Component Store

Purpose: Store Hub component definitions with versioning Features:
  • CRUD operations for components
  • Revision-based versioning
  • LATEST pseudo-revision support
  • Metadata and dependency tracking
Key Format: componentName:revision Example:

API Key Store

Purpose: Manage API key associations with components Features:
  • Associate API keys with components
  • Track exposed components and functions
  • Fast lookup by component name
  • Bidirectional sync with ApiKeySecretStorage
Example:

API Key Secret Storage

Purpose: Store encrypted API key secrets Features:
  • Secure secret storage
  • Permission-based access control
  • Component-level permissions
  • Bidirectional sync with ApiKeyStore
Example:

Sharable Store

Purpose: Store temporary tokens (magic links, temporary access) Features:
  • TTL-based expiration
  • Lazy expiration (checked on access)
  • Optional scheduled cleanup
  • Reusable and one-time tokens
Expiration Strategy:
  1. Lazy Expiration: Checked on get() - expired records are deleted on access
  2. Scheduled Cleanup: Background task removes expired tokens (configurable interval)
Example:

Component Config Storage

Purpose: Store component configuration with versioning Features:
  • Configuration versioning
  • LATEST version support
  • JSON storage format
  • In-memory caching
Key Format: configId.key:configId.version Example:

Component Token Service

Purpose: Validate component access tokens Features:
  • Token-based authentication
  • Component permission validation
  • Coordinates ApiKeyStore and ApiKeySecretStorage
Example:

Configuration Reference

Core Configuration

Local Provider Configuration

Sharable Expiration Configuration

Complete Example Configuration

application-local.yml:

Usage Scenarios

Scenario 1: Pure Local Development

Use Case: Developing without any external services Configuration:
Benefits:
  • Instant startup
  • No AWS credentials needed
  • No external service dependencies
  • Easy to reset (just restart)
  • Zero cost
Limitations:
  • Data lost on restart
  • No persistence
  • Single-instance only

Scenario 2: Integration Testing

Use Case: Fast, isolated integration tests Configuration:
Benefits:
  • Fast test execution
  • No test data pollution
  • Deterministic behavior
  • No external service flakiness
  • Parallel test execution
Best Practices:
  • Use @DirtiesContext to reset state between tests
  • Clear stores manually if needed: localComponentStore.clear()
  • Use fast cleanup intervals

Scenario 3: CI/CD Pipeline

Use Case: Automated testing in CI/CD Configuration:
Benefits:
  • No secrets management in CI
  • Fast pipeline execution
  • No external dependencies
  • Consistent behavior
  • Cost-free

Scenario 4: Learning and Prototyping

Use Case: Learning Hub DSL and component development Configuration:
Benefits:
  • Simple to understand
  • Easy to debug
  • No setup overhead
  • Experiment freely

Switching Between Providers

Local -> AWS

To switch from local to AWS provider: Before (Local):
After (AWS):
Note: All data in local storage will be lost. Components and configuration must be recreated in AWS.

Using Profiles for Easy Switching

application-local.yml:
application-aws.yml:
Activate:

Using Environment Variables

Local vs LocalStack vs AWS

When to Use Local Provider

  • Quick prototyping and experimentation
  • Unit and integration testing
  • CI/CD pipelines (no external deps)
  • Learning Hub DSL
  • No need for persistence

When to Use LocalStack

  • Testing AWS-specific features (DynamoDB queries, TTL)
  • Validating AWS configuration
  • Multi-region testing
  • Testing with AWS SDK clients
  • Need persistence between restarts

When to Use AWS

  • Production deployments
  • Staging environments
  • Need for data persistence
  • Multi-instance deployments
  • Compliance requirements

Features

Thread-Safe Concurrent Access

All storage implementations use ConcurrentHashMap for safe concurrent access:
  • Multiple threads can read/write simultaneously
  • No external synchronization required
  • Atomic operations guarantee consistency

Reactive Programming Model

All operations return Mono<T> for consistency:
  • Integrates with Spring WebFlux
  • Non-blocking I/O support
  • Composable async operations
Example:

Automatic Cleanup

Expired sharables are automatically removed:
  1. Lazy Cleanup: On access, expired items are deleted
  2. Scheduled Cleanup: Background task runs at configured interval
Configuration:
Manual Cleanup:

Versioning Support

Component and configuration versioning with LATEST support: Store specific version:
Retrieve latest:

Bidirectional Sync

ApiKeyStore and ApiKeySecretStorage maintain bidirectional sync: When creating a secret:
  1. Secret is stored in ApiKeySecretStorage
  2. Component association is created in ApiKeyStore
When deleting a secret:
  1. Secret is removed from ApiKeySecretStorage
  2. Component association is removed from ApiKeyStore
This ensures consistency between key storage and component lookup.

Limitations

1. No Persistence

All data is lost on application restart:
  • Components must be redeployed
  • Configuration must be recreated
  • API keys must be regenerated
  • Sharables are lost
Workaround: Use initialization scripts to populate data on startup.

2. Single Instance Only

No shared state between multiple Hub instances:
  • Each instance has its own in-memory storage
  • Components registered in one instance are not visible to others
  • API keys are not shared
Workaround: Use AWS provider or LocalStack for multi-instance deployments.

3. Memory-Bound

Storage is limited by available heap memory:
  • Large component definitions consume memory
  • Many API keys increase memory usage
  • Long-running instances may accumulate sharables
Workaround:
  • Enable automatic cleanup
  • Reduce cleanup interval
  • Monitor heap usage
  • Restart periodically in development

4. No Audit Trail

No logging of storage operations:
  • Cannot track who created/modified components
  • No history of changes
  • No compliance audit support
Workaround: Use verbose logging for debugging.

5. No Cross-Region Support

Single JVM, single region only:
  • No replication
  • No failover
  • No multi-region deployment
Workaround: Use AWS provider with global tables for multi-region.

Testing

Unit Tests

Use local provider for fast unit tests:

Integration Tests

Clear storage between tests for isolation:

Test Utilities

Local stores provide test utilities:

Troubleshooting

Components Not Found

Symptom: Component exists but get() returns empty Causes:
  1. Component stored with different revision
  2. Using wrong component name
  3. Storage was cleared
Solutions:
  1. Check component ID matches exactly: ComponentId.of("name", "revision")
  2. Use LATEST if unsure: ComponentId.of("name", "LATEST")
  3. Enable verbose logging: hub.local.verboseLogging: true
  4. Check if storage was cleared between tests

Sharables Expired Too Quickly

Symptom: Tokens expire before expected Causes:
  1. Cleanup interval too aggressive
  2. Expiration set incorrectly
  3. Time zone issues
Solutions:
  1. Increase cleanup interval: hub.local.cleanupInterval: 10m
  2. Check expiration setting: hub.cloud.provider.sharable.defaultTtl: 24h
  3. Use absolute expiration times in tests
  4. Disable cleanup in tests: hub.local.cleanupEnabled: false

Memory Issues

Symptom: OutOfMemoryError after extended runtime Causes:
  1. Too many components stored
  2. Sharables not being cleaned up
  3. Large component definitions
  4. Memory leak in application code
Solutions:
  1. Enable cleanup: hub.local.cleanupEnabled: true
  2. Reduce cleanup interval: hub.local.cleanupInterval: 1m
  3. Clear storage periodically: componentStore.clear()
  4. Increase heap size: -Xmx2g
  5. Monitor heap usage with JMX
  6. Profile with VisualVM or JProfiler

Data Lost Between Tests

Symptom: Test fails because data from previous test is missing Causes:
  1. Using @DirtiesContext between tests
  2. Storage cleared in @BeforeEach
  3. Different Spring context per test
Solutions:
  1. Share Spring context: Use same test configuration
  2. Don’t clear storage if data should persist
  3. Re-populate required data in each test
  4. Use @SpringBootTest with consistent properties

API Keys Not Working

Symptom: API key validation fails Causes:
  1. Secret not created in ApiKeySecretStorage
  2. Association not created in ApiKeyStore
  3. Wrong secret name
  4. Bidirectional sync failed
Solutions:
  1. Use ApiKeySecretStorage.create() (handles sync automatically)
  2. Check secret exists: apiKeySecretStorage.get("key-name")
  3. Check association: apiKeyStore.getSecrets(componentId)
  4. Enable verbose logging to see sync operations

Advanced Usage

Custom Initialization

Populate storage on startup:

Monitoring Storage Size

Track storage size for capacity planning:

Exporting Data

Export data for backup or migration:

See Also