Cloud Provider Architecture
The Zenoo Hub’s cloud provider architecture implements a clean separation between business logic and infrastructure through a well-defined abstraction layer. This document provides a technical deep-dive into the architecture, design patterns, and implementation details.Architecture Overview
The cloud provider abstraction consists of three layers:Module Structure
Hub Domain
Module:hub-domain
Purpose: Contains pure domain models shared across all layers.
Key Classes:
ComponentId- Component identifier (name + revision)StringComponent- Component definition with DSLApiKeySecret- API key with permissionsApiKeyPermission- Permission definitionComponentApiKeyLookup- API key lookup dataSharableRecord- Sharable token recordExecuteRequest- Execution requestComponentConfigId- Configuration identifier
- Jackson (for JSON serialization)
- Lombok (for boilerplate reduction)
- Spring Core (utilities only)
- No cloud provider dependencies
- No business logic
- Immutable where possible (using records)
- JSON-serializable
Cloud Provider API
Module:cloud-provider-api
Purpose: Defines provider-agnostic interfaces that all cloud implementations must satisfy.
Key Interfaces:
Storage Interfaces
ComponentStore:Configuration Interfaces
ComponentConfigReader (Blocking):Secrets Management
ApiKeySecretStorage:hub-domain(domain models)- Spring Boot (configuration properties)
- Project Reactor (reactive types)
- Jackson (JSON serialization)
Backend Adapters
Location:backend/src/main/java/com/zenoo/hub/component/{config,security}
Purpose: Bridge between backend’s domain-specific types and cloud provider’s generic types.
Adapter Pattern Implementation
1. AttributeComponentConfigReaderAdapter Responsibility: Convert JSON strings from cloud provider to backend’sAttribute DSL type.
- Blocking interface for synchronous reads
- Type conversion: String -> Attribute
- Null/empty handling with sensible defaults
@Order(1)for precedence
Attribute and JSON for reactive storage operations.
- Reactive interface with Mono/Flux
- Bidirectional type conversion: Attribute
<->JSON - Error handling with defaults
- Transparent delegation
- No type conversion needed (uses domain models)
- Pure delegation pattern
- Maintains reactive types
Cloud Provider AWS
Module:cloud-provider-aws (composite module)
Purpose: AWS-specific implementations using DynamoDB, Secrets Manager, and CloudWatch.
Sub-module: aws-stores
Purpose: DynamoDB implementations for storage interfaces. Key Classes: ComponentDynamodbStoreBean:- Exception mapping from AWS SDK to cloud-provider-api
- Retry logic with exponential backoff
- Optimistic locking support
- Operation timeouts
- TTL-based automatic expiration
- High-performance token retrieval
- Configurable expiration
- Fast component -> API keys lookup
- Exposed function management
- Permission tracking
Sub-module: aws-secrets
Purpose: Secrets Manager implementations for configuration and secrets. Key Classes: AwsComponentConfigStorage:- Version management with stage labels
- Multi-region replication
- Encryption with KMS
- Secure API key storage
- Permission management
- Automatic rotation support
- JSON serialization with Jackson
- Custom serializers for permissions
- Caching with configurable TTL
- Audit trail support
Sub-module: aws-metrics
Purpose: CloudWatch metrics publishing. AwsMetricPublisher:- DynamoDB operation metrics
- Secret access metrics
- Custom dimensions support
Sub-module: aws-spring-boot-starter
Purpose: Spring Boot autoconfiguration for AWS provider. AwsCloudProviderAutoConfiguration:- Conditional activation based on classpath and configuration
- Component scanning for AWS beans
- Configuration properties binding
- Defaults to AWS for backward compatibility
Cloud Provider Local
Module:cloud-provider-local (single module)
Purpose: Lightweight, in-memory implementation for local development and testing.
Architecture Philosophy:
- Simplicity over features
- Zero external dependencies
- Fast startup and teardown
- Deterministic behavior for testing
- Single Module: Unlike AWS (4 submodules), local uses a single module for simplicity
- In-Memory Storage: All data stored in
ConcurrentHashMapinstances - No Persistence: Data lost on restart (by design)
- Thread-Safe: Uses concurrent collections and locks where needed
- Reactive: Returns
Mono<T>for API consistency
Storage Implementations
LocalComponentStore:- LATEST pseudo-revision resolution (same as AWS)
- Component versioning support
- Optimistic locking for concurrent updates
- Exception mapping to cloud-provider-api types
- Lazy expiration (checked on access)
- Scheduled cleanup (optional background task)
- Configurable cleanup interval
- Fine-grained locking with ReadWriteLock
- Fast component -> API keys lookup
- Bidirectional sync with ApiKeySecretStorage
- Bidirectional sync with ApiKeyStore
- Permission-based access control
- Secure storage (in-memory)
- Configuration versioning
- LATEST version resolution
- JSON storage format
Support Classes
LocalStoreSupport (Base Class):- Consistent error handling
- Exception mapping to cloud-provider-api types
- Logging support
- Background cleanup task
- Configurable interval
- Graceful shutdown
Auto-Configuration
LocalCloudProviderAutoConfiguration:- Conditional activation via
hub.cloud.provider.type=local - Component scanning for local provider beans
- Configuration properties binding
- Startup logging for visibility
Testing Utilities
The local provider exposes testing utilities:Cloud Provider GCP
Module:cloud-provider-gcp (composite module)
Purpose: GCP-specific implementations using Cloud Firestore (Native Mode), Secret Manager, and Cloud Monitoring.
Sub-module: gcp-stores
Purpose: Firestore implementations for storage interfaces. Key Classes: ComponentFirestoreStoreBean:- Exception mapping from Firestore SDK to cloud-provider-api
- Retry logic with exponential backoff
- Atomic LATEST pointer updates using transactions
- Operation timeouts
- Composite index auto-creation
- TTL-based automatic expiration (Firestore native TTL)
- High-performance token retrieval
- Configurable expiration
- Native Firestore TTL field support
- Fast component -> API keys lookup
- Bidirectional mapping support
- Firestore document-based storage
Sub-module: gcp-secrets
Purpose: Secret Manager implementations for configuration and secrets. Key Classes: GcpComponentConfigStorage:- Version management with label-based semantic versioning
- Multi-region replication support
- Automatic encryption at rest
- Caffeine caching with configurable TTL
- Secure API key storage
- Permission management
- JSON serialization with Jackson
- Version limits and automatic cleanup
- Label-based semantic versioning (workaround for Secret Manager limitations)
- Caching with configurable size and TTL
- Batch operations support
- Custom serializers for permissions
Sub-module: gcp-metrics
Purpose: Cloud Monitoring metrics publishing. GcpMetricPublisher:- Firestore operation metrics
- Secret access metrics
- Batch publishing (up to 200 time series per request)
- Level-based filtering (INFO, ERROR, TRACE)
- Custom dimensions support
Sub-module: gcp-spring-boot-starter
Purpose: Spring Boot autoconfiguration for GCP provider. GcpCloudProviderAutoConfiguration:- Conditional activation based on classpath and configuration
- Component scanning for GCP beans
- Configuration properties binding
- Application Default Credentials (ADC) support
- Workload Identity support for GKE
Comparison: AWS vs GCP vs Local
| Aspect | AWS Provider | GCP Provider | Local Provider |
|---|---|---|---|
| Module Structure | 4 submodules | 4 submodules | Single module |
| Storage | DynamoDB | Firestore (Native Mode) | ConcurrentHashMap |
| Secrets | Secrets Manager | Secret Manager | In-memory |
| Persistence | Full | Full | None |
| External Deps | AWS SDK, DynamoDB, Secrets Manager | GCP SDK, Firestore, Secret Manager | None |
| Setup Time | ~2 seconds | ~2 seconds | <100ms |
| Configuration | IAM, credentials, regions | IAM, credentials, project | None required |
| Multi-instance | Yes (via DynamoDB) | Yes (via Firestore) | No (in-memory) |
| Cost | Pay per use | Pay per use | Free |
| Thread Safety | AWS SDK handles | Firestore SDK handles | ConcurrentHashMap + locks |
| Expiration | DynamoDB TTL | Firestore TTL | Lazy + scheduled cleanup |
| Metrics | CloudWatch | Cloud Monitoring | Logging only |
| Versioning | Stage labels | Label-based semantic versioning | In-memory tracking |
| Multi-region | Global Tables | Automatic replication | No |
| Best For | Production on AWS | Production on GCP | Development, testing |
Design Trade-offs
Why single module instead of splitting?- Simplicity: Easier to understand and maintain
- No submodules needed (no external services to abstract)
- Faster compile times
- Smaller dependency graph
- Built-in thread safety
- Good performance for concurrent access
- No external dependencies
- Familiar to Java developers
- Keeps implementation simple
- Forces proper test isolation
- Encourages proper CI/CD practices
- Faster test execution
- JVM has no built-in TTL mechanism
- Scheduled task is simple and effective
- Lazy cleanup reduces CPU overhead
- Configurable for different use cases
Design Patterns
1. Adapter Pattern
Used By: Backend adapters Purpose: Convert between backend’s domain types and cloud provider’s generic types. Example:- Clean separation of concerns
- Type safety in backend
- Provider independence
2. Strategy Pattern
Used By: Cloud provider selection Purpose: Select cloud provider implementation at runtime. Example:- Runtime provider switching
- Easy testing with different providers
- Future extensibility
3. Repository Pattern
Used By: Storage interfaces Purpose: Abstract data persistence from business logic. Benefits:- Clean data access layer
- Testability with mocks
- Provider-agnostic business logic
4. Facade Pattern
Used By: ComponentConfigServiceFacade Purpose: Provide unified interface to multiple config readers. Benefits:- Single entry point for configuration
- Multiple reader support
- Precedence management
Exception Handling
Exception Flow
Exception Mapping
| AWS Exception | CloudProviderException Type | ErrorType |
|---|---|---|
| ResourceNotFoundException | StorageException | NOT_FOUND |
| ConditionalCheckFailedException | StorageException | CONFLICT |
| ValidationException | StorageException | INVALID_REQUEST |
| ProvisionedThroughputExceededException | StorageException | SERVICE_ERROR |
| AwsServiceException | StorageException | SERVICE_ERROR |
Retry Logic
Implementation:DynamoDBStoreSupport
Reactive Programming Model
Why Reactive?
- Non-blocking I/O - Better resource utilization
- Backpressure - Handle slow consumers
- Composability - Chain operations easily
- Error handling - Propagate errors through pipeline
Reactive Types
Mono<T>: Represents 0 or 1 element
Flux<T>: Represents 0 to N elements
Common Operators
Transformation:Dependency Injection Flow
Bean Registration Order
-
Cloud Provider Beans (AWS)
- Registered by
AwsStoresAutoConfiguration ComponentDynamodbStoreBean,AwsApiKeySecretStorage, etc.
- Registered by
-
Adapter Beans (Backend)
- Registered by component scanning
AttributeComponentConfigReaderAdapter, etc.- Autowired with cloud provider implementations
-
Service Beans (Backend)
ComponentConfigServiceFacade,ApiKeyServiceBean, etc.- Autowired with adapters
Example Injection Chain
Testing Strategy
Unit Tests
Adapter Tests:- Mock cloud provider delegates
- Test type conversions
- Test error handling
- Mock AWS SDK clients
- Test exception mapping
- Test retry logic
Integration Tests
LocalStack:- Full AWS service emulation
- End-to-end testing
- No cloud costs
- Embedded DynamoDB
- Isolated test environment
Example Test Structure
Performance Considerations
Connection Pooling
- AWS SDK manages connection pools automatically
- Default: 50 connections per client
- Tune for high-throughput scenarios
Caching
Secrets Manager:- Reduced API calls
- Lower latency
- Cost savings
- Stale data for up to cacheTtl
- Memory usage
Batch Operations
- Use DynamoDB
BatchGetItemfor multiple reads - Use
BatchWriteItemfor multiple writes - Up to 25 items per batch
Security Considerations
Principle of Least Privilege
- Grant minimum required IAM permissions
- Use resource-based policies where possible
- Separate read/write permissions
Encryption
At Rest:- DynamoDB: Default encryption with AWS-managed keys
- Secrets Manager: Automatic encryption with KMS
- All AWS API calls use HTTPS
- TLS 1.2+ required
Audit Logging
- CloudTrail logs all API calls
- CloudWatch Logs for application logs
- Structured logging for correlation
Future Extensibility
Adding New Providers
To add a new provider (e.g., Azure):- Create module
cloud-provider-azure - Implement interfaces from
cloud-provider-api - Map provider exceptions to
CloudProviderException - Create Spring Boot autoconfiguration
- Add tests
Supporting New Storage Types
To add new storage interfaces:- Define interface in
cloud-provider-api - Add domain models to
hub-domain - Implement in cloud provider modules
- Create adapter in backend (if type conversion needed)
- Update autoconfiguration