Documentation Index
Fetch the complete documentation index at: https://platform.docs.zenoo.com/llms.txt
Use this file to discover all available pages before exploring further.
Implementing Cloud Providers
Guide for implementing support for new cloud providers (Azure, GCP, or custom infrastructure).
Overview
Adding a new cloud provider requires:
- Implementing cloud-provider-api interfaces
- Mapping provider exceptions to CloudProviderException
- Creating Spring Boot autoconfiguration
- Writing tests
Reference Implementations
Before implementing a new provider, review existing implementations:
Local Provider (Simple Reference)
Module: cloud-provider-local
The local provider is the simplest complete implementation and serves as an excellent reference:
Advantages as a reference:
- Single module (easier to understand)
- No external dependencies (focus on core interfaces)
- Simple storage (ConcurrentHashMap)
- Clear error handling patterns
- Well-commented code
- Complete test coverage
Key classes to study:
cloud-provider-local/
├── store/
│ ├── LocalComponentStore.java # ComponentStore implementation
│ ├── LocalApiKeyStore.java # ApiKeyStore implementation
│ └── LocalSharableStore.java # SharableStore implementation
├── secret/
│ └── LocalApiKeySecretStorage.java # ApiKeySecretStorage implementation
├── config/
│ ├── LocalComponentConfigStorage.java # ComponentConfigStorage implementation
│ └── LocalComponentConfigReader.java # Synchronous config reader
├── support/
│ └── LocalStoreSupport.java # Base class with error handling
└── autoconfigure/
├── LocalCloudProviderAutoConfiguration.java
├── LocalProviderConfig.java
└── SharableCleanupScheduler.java
Example: Simple ComponentStore:
@Service
public class LocalComponentStore extends LocalStoreSupport
implements ComponentStore {
private final ConcurrentHashMap<String, StringComponent> components =
new ConcurrentHashMap<>();
@Override
public Mono<StringComponent> get(ComponentId id) {
return wrapWithErrorHandling(
Mono.fromCallable(() -> {
ComponentId resolvedId = resolveLatestRevision(id);
StringComponent component = components.get(toKey(resolvedId));
if (component == null) {
throw new NoSuchElementException("Component not found: " + id);
}
return component;
}),
"get component",
id.getName()
);
}
// Error handling in base class (LocalStoreSupport)
protected Throwable wrapException(Throwable throwable) {
if (throwable instanceof NoSuchElementException) {
return new StorageException(
throwable.getMessage(),
throwable,
CloudProviderException.ErrorType.NOT_FOUND
);
}
// ... other mappings
}
}
Study these patterns:
- Error Handling -
LocalStoreSupport.wrapWithErrorHandling()
- Exception Mapping - Java exceptions -> CloudProviderException
- Reactive Wrapping -
Mono.fromCallable() for synchronous operations
- LATEST Resolution -
resolveLatestRevision() pattern
- Auto-Configuration - Conditional bean creation
AWS Provider (Production Reference)
Module: cloud-provider-aws (4 submodules)
The AWS provider is a production-ready implementation showing advanced patterns:
Advantages as a reference:
- Real-world external service integration (DynamoDB, Secrets Manager)
- Advanced error handling and retries
- Multi-region support
- Metrics publishing (CloudWatch)
- Production-grade testing
Module structure:
cloud-provider-aws/
├── aws-stores/ # DynamoDB storage implementations
├── aws-secrets/ # Secrets Manager implementations
├── aws-metrics/ # CloudWatch metrics
└── aws-spring-boot-starter/ # Auto-configuration
Study these patterns:
- SDK Integration - AWS SDK v2 async client usage
- Retry Logic - Exponential backoff for transient errors
- Connection Pooling - Managing client lifecycle
- Metric Publishing - CloudWatch integration
- Multi-Module Structure - When to split providers
View AWS provider documentation
Choosing a Reference
| Aspect | Local Provider | AWS Provider |
|---|
| Complexity | Simple | Complex |
| Lines of Code | ~1,500 | ~5,000 |
| External Deps | None | AWS SDK, DynamoDB, Secrets Manager |
| Module Structure | Single | 4 submodules |
| Best for Learning | Core patterns | Production patterns |
| Best for Starting | Yes | Use as second reference |
Recommendation: Start with the local provider to understand core patterns, then review AWS provider for production considerations.
Module Structure
Create a new module following the pattern:
cloud-provider-{name}/
├── {name}-stores/
│ └── src/main/java/com/zenoo/hub/cloudprovider/{name}/stores/
├── {name}-secrets/
│ └── src/main/java/com/zenoo/hub/cloudprovider/{name}/secrets/
├── {name}-metrics/
│ └── src/main/java/com/zenoo/hub/cloudprovider/{name}/metrics/
└── {name}-spring-boot-starter/
└── src/main/java/com/zenoo/hub/cloudprovider/{name}/autoconfigure/
Implementing Storage Interfaces
ComponentStore Implementation
@Service
public class AzureComponentStore implements ComponentStore {
private final CosmosClient cosmosClient;
@Override
public Mono<StringComponent> get(ComponentId id) {
return Mono.defer(() -> {
try {
var response = cosmosClient
.getDatabase(databaseName)
.getContainer(containerName)
.readItem(id.getFullName(),
new PartitionKey(id.getName()),
ComponentRecord.class);
return Mono.just(response.getItem().toStringComponent());
} catch (CosmosException e) {
return Mono.error(mapException(e));
}
});
}
private Throwable mapException(CosmosException e) {
if (e.getStatusCode() == 404) {
return new StorageException(
"Component not found",
e,
CloudProviderException.ErrorType.NOT_FOUND
);
}
return new StorageException(
"Storage error",
e,
CloudProviderException.ErrorType.SERVICE_ERROR
);
}
}
SharableStore Implementation
@Service
public class AzureSharableStore implements SharableStore {
@Override
public Mono<SharableRecord> get(String token) {
// Implementation using Azure Cosmos DB or Table Storage
}
@Override
public Mono<String> store(SharableRecord record) {
// Implementation with TTL support
}
}
Implementing Configuration Interfaces
ComponentConfigStorage Implementation
@Service
public class AzureComponentConfigStorage implements ComponentConfigStorage {
private final SecretClient secretClient;
@Override
public Mono<String> get(ComponentConfigId configId) {
return Mono.defer(() -> {
try {
KeyVaultSecret secret = secretClient.getSecret(
buildSecretName(configId)
);
return Mono.just(secret.getValue());
} catch (ResourceNotFoundException e) {
return Mono.empty();
} catch (Exception e) {
return Mono.error(mapException(e));
}
});
}
}
Spring Boot Autoconfiguration
Main Configuration Class
@AutoConfiguration
@ConditionalOnClass(CosmosClient.class)
@ConditionalOnProperty(
name = "hub.cloud.provider.type",
havingValue = "azure"
)
@Import({
AzureStoresAutoConfiguration.class,
AzureSecretsAutoConfiguration.class
})
public class AzureCloudProviderAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public CosmosClient cosmosClient(AzureConfig config) {
return new CosmosClientBuilder()
.endpoint(config.getEndpoint())
.key(config.getKey())
.buildClient();
}
}
Register Autoconfiguration
Create META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:
com.zenoo.hub.cloudprovider.azure.autoconfigure.AzureCloudProviderAutoConfiguration
Configuration Properties
@Data
@Validated
@ConfigurationProperties(prefix = "hub.azure")
public class AzureConfig {
@NotBlank
private String endpoint;
@NotBlank
private String key;
@NotBlank
private String databaseName;
}
Testing
Unit Tests
@ExtendWith(MockitoExtension.class)
class AzureComponentStoreTest {
@Mock
private CosmosClient cosmosClient;
private AzureComponentStore store;
@Test
void shouldGetComponent() {
// Test implementation
}
}
Integration Tests
Use Test Containers or Azure emulator for integration tests.
Example: Azure Provider Structure
// Configuration
@ConfigurationProperties("hub.azure")
public class AzureConfig {
String endpoint;
String key;
}
// Component Store
@Service
public class AzureComponentStore implements ComponentStore {
// Cosmos DB implementation
}
// Secrets Management
@Service
public class AzureKeyVaultStorage implements ApiKeySecretStorage {
// Key Vault implementation
}
// Autoconfiguration
@AutoConfiguration
@ConditionalOnProperty(name = "hub.cloud.provider.type", havingValue = "azure")
public class AzureCloudProviderAutoConfiguration {
// Bean definitions
}
See Also