> ## 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.

# Cloud Provider Architecture

> Technical deep-dive into the cloud provider abstraction layer architecture, design patterns, and implementation details

# 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:

```
┌─────────────────────────────────────────────────────┐
│                Hub Backend                          │
│  - Workflow Engine                                  │
│  - Component Management                             │
│  - Business Logic                                   │
│                                                     │
│  Adapters:                                          │
│  ├─ AttributeComponentConfigReaderAdapter           │
│  ├─ AttributeComponentConfigStorageAdapter          │
│  └─ ApiKeyStoreAdapter                              │
└────────────────────┬────────────────────────────────┘
                     │ (depends on interfaces only)
                     ▼
┌─────────────────────────────────────────────────────┐
│           Cloud Provider API                        │
│                                                     │
│  Interfaces:                                        │
│  ├─ ComponentStore                                  │
│  ├─ SharableStore                                   │
│  ├─ ApiKeyStore                                     │
│  ├─ ApiKeySecretStorage                             │
│  ├─ ComponentConfigStorage                          │
│  └─ ComponentConfigReader                           │
│                                                     │
│  Exceptions:                                        │
│  ├─ CloudProviderException                          │
│  ├─ StorageException                                │
│  └─ ApiKeyException                                 │
└────────────────────┬────────────────────────────────┘
                     │ (implementations)
                     ▼
┌─────────────────────────────────────────────────────┐
│              Cloud Provider AWS                     │
│                                                     │
│  Sub-modules:                                       │
│  ├─ aws-stores (DynamoDB)                           │
│  │   ├─ ComponentDynamodbStoreBean                  │
│  │   ├─ SharableDynamodbStoreBean                   │
│  │   └─ ApiKeyLookupStoreBean                       │
│  │                                                  │
│  ├─ aws-secrets (Secrets Manager)                   │
│  │   ├─ AwsComponentConfigStorage                   │
│  │   ├─ AwsComponentConfigReader                    │
│  │   └─ AwsApiKeySecretStorage                      │
│  │                                                  │
│  ├─ aws-metrics (CloudWatch)                        │
│  │   └─ AwsMetricPublisher                          │
│  │                                                  │
│  └─ aws-spring-boot-starter                         │
│      └─ Auto-configuration                          │
└─────────────────────────────────────────────────────┘
```

## 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 DSL
* `ApiKeySecret` - API key with permissions
* `ApiKeyPermission` - Permission definition
* `ComponentApiKeyLookup` - API key lookup data
* `SharableRecord` - Sharable token record
* `ExecuteRequest` - Execution request
* `ComponentConfigId` - Configuration identifier

**Dependencies:** Minimal

* Jackson (for JSON serialization)
* Lombok (for boilerplate reduction)
* Spring Core (utilities only)

**Design Principles:**

* 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:**

```java theme={null}
public interface ComponentStore {
    Mono<StringComponent> get(ComponentId id);
    Mono<ComponentId> store(ComponentId id, StringComponent component);
    Mono<List<ComponentId>> delete(ComponentId id);
}
```

**SharableStore:**

```java theme={null}
public interface SharableStore {
    Mono<SharableRecord> get(String token);
    Mono<String> store(SharableRecord record);
    Mono<String> delete(String token);
    Mono<String> expire(String token);
}
```

**ApiKeyStore:**

```java theme={null}
public interface ApiKeyStore extends ApiKeyReader {
    Mono<String> create(ApiKeySecret apiKeySecret);
    Mono<String> update(ApiKeySecret apiKeySecret);
    Mono<String> delete(String secretName);
}
```

#### Configuration Interfaces

**ComponentConfigReader** (Blocking):

```java theme={null}
public interface ComponentConfigReader {
    String getByKey(ComponentConfigId configId);
}
```

**ComponentConfigStorage** (Reactive):

```java theme={null}
public interface ComponentConfigStorage {
    Mono<String> get(ComponentConfigId configId);
    Mono<ComponentConfigId> store(ComponentConfigId configId, String json);
    Mono<ComponentConfigId> delete(ComponentConfigId configId);
    Mono<String> getVersionOfLatestConfig(String configName);
}
```

#### Secrets Management

**ApiKeySecretStorage:**

```java theme={null}
public interface ApiKeySecretStorage extends ApiKeyReader {
    Mono<String> create(ApiKeySecret apiKeySecret);
    Mono<String> update(ApiKeySecret apiKeySecret);
    Mono<String> delete(String name);
}
```

**Exception Hierarchy:**

```
CloudProviderException
├── StorageException
└── ApiKeyException
```

**Error Types:**

```java theme={null}
public enum ErrorType {
    NOT_FOUND,         // Resource not found
    CONFLICT,          // Conflict (e.g., version mismatch)
    INVALID_REQUEST,   // Invalid input
    SERVICE_ERROR      // Cloud provider service error
}
```

**Dependencies:**

* `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's `Attribute` DSL type.

```java theme={null}
@Component
@Order(1)
class AttributeComponentConfigReaderAdapter implements ComponentConfigReader {
    private final com.zenoo.hub.cloudprovider.config.ComponentConfigReader delegate;

    @Override
    public Attribute getByKey(ComponentConfigId configId) {
        String json = delegate.getByKey(configId);
        if (json == null || json.isEmpty()) {
            return AttributeBuilder.of();
        }
        return AttributeBuilder.ofJson(json);
    }
}
```

**Key Features:**

* Blocking interface for synchronous reads
* Type conversion: String -> Attribute
* Null/empty handling with sensible defaults
* `@Order(1)` for precedence

**2. AttributeComponentConfigStorageAdapter**

**Responsibility:** Convert between `Attribute` and JSON for reactive storage operations.

```java theme={null}
@Component
class AttributeComponentConfigStorageAdapter implements ComponentConfigStorage {
    private final com.zenoo.hub.cloudprovider.config.ComponentConfigStorage delegate;

    @Override
    public Mono<Attribute> get(ComponentConfigId configId) {
        return delegate.get(configId)
                .map(AttributeBuilder::ofJson)
                .defaultIfEmpty(AttributeBuilder.of())
                .onErrorReturn(AttributeBuilder.of());
    }

    @Override
    public Mono<ComponentConfigId> store(ComponentConfigId configId, Attribute config) {
        String json = AttributeBuilder.toJson(config);
        return delegate.store(configId, json);
    }
}
```

**Key Features:**

* Reactive interface with Mono/Flux
* Bidirectional type conversion: Attribute `<->` JSON
* Error handling with defaults
* Transparent delegation

**3. ApiKeyStoreAdapter**

**Responsibility:** Simple pass-through adapter for API key operations.

```java theme={null}
@Component
class ApiKeyStoreAdapter implements ApiKeyStore {
    private final ApiKeySecretStorage delegate;

    @Override
    public Mono<ApiKeySecret> get(String key) {
        return delegate.get(key);
    }

    // ... other methods delegate directly
}
```

**Key Features:**

* 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:**

```java theme={null}
@Service
public class ComponentDynamodbStoreBean extends DynamoDBStoreSupport
                                         implements ComponentStore {

    @Override
    public Mono<StringComponent> get(ComponentId id) {
        return getComponent(id)
                .switchIfEmpty(Mono.error(() -> NotFoundException.component(id)))
                .onErrorMap(this::wrapAwsException)
                .doOnError(error -> log.warn("Component `{}` not found", id, error));
    }

    private Throwable wrapAwsException(Throwable throwable) {
        if (throwable instanceof ResourceNotFoundException) {
            return new StorageException(
                throwable.getMessage(),
                throwable,
                CloudProviderException.ErrorType.NOT_FOUND
            );
        }
        // ... other mappings
    }
}
```

**Features:**

* Exception mapping from AWS SDK to cloud-provider-api
* Retry logic with exponential backoff
* Optimistic locking support
* Operation timeouts

**SharableDynamodbStoreBean:**

* TTL-based automatic expiration
* High-performance token retrieval
* Configurable expiration

**ApiKeyLookupStoreBean:**

* 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

**AwsApiKeySecretStorage:**

* Secure API key storage
* Permission management
* Automatic rotation support

**Features:**

* 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:**

```java theme={null}
@AutoConfiguration
@ConditionalOnClass(DynamoDbAsyncClient.class)
@ConditionalOnProperty(name = "hub.cloud.provider.type",
                       havingValue = "aws",
                       matchIfMissing = true)
@Import({
    AwsStoresAutoConfiguration.class,
    AwsSecretsAutoConfiguration.class,
    AwsMetricsAutoConfiguration.class
})
public class AwsCloudProviderAutoConfiguration {
    // Configuration beans
}
```

**Features:**

* 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

**Key Characteristics:**

* **Single Module:** Unlike AWS (4 submodules), local uses a single module for simplicity
* **In-Memory Storage:** All data stored in `ConcurrentHashMap` instances
* **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:**

```java theme={null}
@Service
public class LocalComponentStore extends LocalStoreSupport
                                 implements ComponentStore {

    // Key: "componentName:revision"
    private final ConcurrentHashMap<String, StringComponent> components =
        new ConcurrentHashMap<>();

    // Key: "componentName", Value: "latestRevision"
    private final ConcurrentHashMap<String, String> latestRevisions =
        new ConcurrentHashMap<>();

    @Override
    public Mono<StringComponent> get(ComponentId id) {
        return wrapWithErrorHandling(
            Mono.fromCallable(() -> {
                // Resolve LATEST pseudo-revision
                ComponentId resolvedId = resolveRevision(id);
                StringComponent component = components.get(toKey(resolvedId));
                if (component == null) {
                    throw new NoSuchElementException("Component not found: " + id);
                }
                return component;
            }),
            "get component",
            id.getName()
        );
    }

    private ComponentId resolveRevision(ComponentId id) {
        if ("LATEST".equals(id.getRevision())) {
            String latestRevision = latestRevisions.get(id.getName());
            if (latestRevision == null) {
                throw new NoSuchElementException("No versions found for: " + id.getName());
            }
            return id.toBuilder().revision(latestRevision).build();
        }
        return id;
    }
}
```

**Features:**

* LATEST pseudo-revision resolution (same as AWS)
* Component versioning support
* Optimistic locking for concurrent updates
* Exception mapping to cloud-provider-api types

**LocalSharableStore:**

```java theme={null}
@Service
public class LocalSharableStore extends LocalStoreSupport
                                 implements SharableStore {

    private final ConcurrentHashMap<String, SharableRecord> sharables =
        new ConcurrentHashMap<>();

    @Override
    public Mono<SharableRecord> get(String token) {
        return wrapWithErrorHandling(
            Mono.fromCallable(() -> {
                SharableRecord record = sharables.get(token);
                if (record == null) {
                    throw new NoSuchElementException("Sharable not found: " + token);
                }
                // Lazy expiration check
                if (isExpired(record)) {
                    sharables.remove(token);
                    throw new NoSuchElementException("Sharable expired: " + token);
                }
                return record;
            }),
            "get sharable",
            token
        );
    }

    public boolean isExpired(SharableRecord record) {
        if (record.isExpired()) return true;
        if (record.getExpiration() != null &&
            Instant.now().isAfter(record.getExpiration())) {
            return true;
        }
        return false;
    }
}
```

**Features:**

* Lazy expiration (checked on access)
* Scheduled cleanup (optional background task)
* Configurable cleanup interval

**LocalApiKeyStore:**

```java theme={null}
@Service
public class LocalApiKeyStore extends LocalStoreSupport
                              implements ApiKeyStore {

    // Key: componentName
    private final ConcurrentHashMap<String, ApiKeyRecord> apiKeyRecords =
        new ConcurrentHashMap<>();

    // ApiKeyRecord uses ReadWriteLock for thread safety
    public static class ApiKeyRecord {
        private final ReadWriteLock lock = new ReentrantReadWriteLock();
        private final Set<String> secrets = new HashSet<>();
        private boolean exposed = false;
        private Set<String> exposedFunctions = new HashSet<>();

        public void addSecret(String secretName) {
            lock.writeLock().lock();
            try {
                secrets.add(secretName);
            } finally {
                lock.writeLock().unlock();
            }
        }

        public Set<String> getSecretsOrEmpty() {
            lock.readLock().lock();
            try {
                return new HashSet<>(secrets);
            } finally {
                lock.readLock().unlock();
            }
        }
    }
}
```

**Features:**

* Fine-grained locking with ReadWriteLock
* Fast component -> API keys lookup
* Bidirectional sync with ApiKeySecretStorage

**LocalApiKeySecretStorage:**

```java theme={null}
@Service
public class LocalApiKeySecretStorage extends LocalStoreSupport
                                      implements ApiKeySecretStorage {

    private final ConcurrentHashMap<String, ApiKeySecret> secrets =
        new ConcurrentHashMap<>();

    private final LocalApiKeyStore apiKeyStore;

    @Override
    public Mono<String> create(ApiKeySecret apiKeySecret) {
        return wrapWithErrorHandling(
            Mono.fromCallable(() -> {
                secrets.put(apiKeySecret.name(), apiKeySecret);
                return apiKeySecret.name();
            })
            // Bidirectional sync: update API key store
            .flatMap(name -> apiKeyStore.addSecret(apiKeySecret).thenReturn(name)),
            "create secret",
            apiKeySecret.name()
        );
    }
}
```

**Features:**

* Bidirectional sync with ApiKeyStore
* Permission-based access control
* Secure storage (in-memory)

**LocalComponentConfigStorage:**

```java theme={null}
@Service
public class LocalComponentConfigStorage extends LocalStoreSupport
                                         implements ComponentConfigStorage {

    // Key: "configKey:version", Value: JSON string
    private final ConcurrentHashMap<String, String> configs =
        new ConcurrentHashMap<>();

    // Key: "configKey", Value: "latestVersion"
    private final ConcurrentHashMap<String, String> latestVersions =
        new ConcurrentHashMap<>();
}
```

**Features:**

* Configuration versioning
* LATEST version resolution
* JSON storage format

#### Support Classes

**LocalStoreSupport (Base Class):**

```java theme={null}
public abstract class LocalStoreSupport {

    protected <T> Mono<T> wrapWithErrorHandling(
            Mono<T> operation,
            String operationName,
            String resourceId) {
        return operation
            .onErrorMap(this::wrapException)
            .doOnError(error ->
                log.warn("Local store operation '{}' failed for '{}'",
                         operationName, resourceId, error));
    }

    protected Throwable wrapException(Throwable throwable) {
        if (throwable instanceof IllegalArgumentException) {
            return new StorageException(
                throwable.getMessage(),
                throwable,
                CloudProviderException.ErrorType.INVALID_REQUEST
            );
        }
        if (throwable instanceof NoSuchElementException) {
            return new StorageException(
                throwable.getMessage(),
                throwable,
                CloudProviderException.ErrorType.NOT_FOUND
            );
        }
        return new StorageException(
            throwable.getMessage(),
            throwable,
            CloudProviderException.ErrorType.SERVICE_ERROR
        );
    }
}
```

**Features:**

* Consistent error handling
* Exception mapping to cloud-provider-api types
* Logging support

**SharableCleanupScheduler:**

```java theme={null}
@Component
@ConditionalOnProperty(name = "hub.local.cleanupEnabled",
                       havingValue = "true",
                       matchIfMissing = true)
public class SharableCleanupScheduler {

    @Autowired
    private LocalSharableStore sharableStore;

    @Autowired
    private LocalProviderConfig config;

    private ScheduledExecutorService scheduler;

    @PostConstruct
    public void start() {
        scheduler = Executors.newSingleThreadScheduledExecutor();
        long intervalMs = config.getCleanupInterval().toMillis();

        scheduler.scheduleAtFixedRate(
            () -> {
                try {
                    int removed = sharableStore.cleanupExpired();
                    if (removed > 0) {
                        log.info("Cleaned up {} expired sharables", removed);
                    }
                } catch (Exception e) {
                    log.error("Cleanup task failed", e);
                }
            },
            intervalMs,
            intervalMs,
            TimeUnit.MILLISECONDS
        );
    }

    @PreDestroy
    public void stop() {
        if (scheduler != null) {
            scheduler.shutdown();
        }
    }
}
```

**Features:**

* Background cleanup task
* Configurable interval
* Graceful shutdown

#### Auto-Configuration

**LocalCloudProviderAutoConfiguration:**

```java theme={null}
@AutoConfiguration
@ConditionalOnProperty(
    prefix = "hub.cloud.provider",
    name = "type",
    havingValue = "local"
)
@ComponentScan(basePackages = {
    "com.zenoo.hub.cloudprovider.local.store",
    "com.zenoo.hub.cloudprovider.local.secret",
    "com.zenoo.hub.cloudprovider.local.config"
})
@EnableConfigurationProperties({
    LocalProviderConfig.class,
    CloudProviderComponentConfig.class,
    CloudProviderSharableConfig.class
})
public class LocalCloudProviderAutoConfiguration {

    @Bean
    @ConditionalOnProperty(name = "hub.local.cleanupEnabled",
                           havingValue = "true",
                           matchIfMissing = true)
    public SharableCleanupScheduler sharableCleanupScheduler() {
        return new SharableCleanupScheduler();
    }

    @PostConstruct
    public void logActivation() {
        log.info("Local Cloud Provider auto-configuration enabled");
        log.info("Using in-memory storage for all cloud provider operations");
        log.info("Note: All data will be lost on application restart");
    }
}
```

**Features:**

* 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:

```java theme={null}
// Check storage size
int componentCount = localComponentStore.size();

// Clear storage between tests
localComponentStore.clear();
localSharableStore.clear();

// Manual cleanup
int expiredCount = localSharableStore.cleanupExpired();

// Get all tokens (for testing)
List<String> allTokens = localSharableStore.getAllTokens();
```

### 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:**

```java theme={null}
@Service
public class ComponentFirestoreStoreBean extends FirestoreStoreSupport
                                          implements ComponentStore {

    @Override
    public Mono<StringComponent> get(ComponentId id) {
        return getComponentFromFirestore(id)
                .switchIfEmpty(Mono.error(() -> NotFoundException.component(id)))
                .onErrorMap(this::wrapFirestoreException)
                .doOnError(error -> log.warn("Component `{}` not found", id, error));
    }

    private Throwable wrapFirestoreException(Throwable throwable) {
        if (throwable instanceof com.google.cloud.firestore.FirestoreException) {
            return GcpExceptionMapper.map((FirestoreException) throwable);
        }
        return throwable;
    }
}
```

**Features:**

* 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

**SharableFirestoreStoreBean:**

* TTL-based automatic expiration (Firestore native TTL)
* High-performance token retrieval
* Configurable expiration
* Native Firestore TTL field support

**ApiKeyLookupStoreBean:**

* 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

**GcpApiKeySecretStorage:**

* Secure API key storage
* Permission management
* JSON serialization with Jackson
* Version limits and automatic cleanup

**Features:**

* 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:**

```java theme={null}
@AutoConfiguration
@ConditionalOnClass(Firestore.class)
@ConditionalOnProperty(name = "hub.cloud.provider.type",
                       havingValue = "gcp")
@Import({
    GcpStoresAutoConfiguration.class,
    GcpSecretsAutoConfiguration.class,
    GcpMetricsAutoConfiguration.class
})
public class GcpCloudProviderAutoConfiguration {
    // Configuration beans

    @Bean
    @ConditionalOnMissingBean
    public Firestore firestore(GcpConfig gcpConfig,
                                 GcpFirestoreConfig firestoreConfig) {
        GoogleCredentials credentials = resolveCredentials(gcpConfig);
        FirestoreOptions options = FirestoreOptions.newBuilder()
                .setCredentials(credentials)
                .setProjectId(gcpConfig.getProjectId())
                .setDatabaseId(firestoreConfig.getDatabase())
                .build();
        return options.getService();
    }
}
```

**Features:**

* 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

**Why ConcurrentHashMap?**

* Built-in thread safety
* Good performance for concurrent access
* No external dependencies
* Familiar to Java developers

**Why no persistence?**

* Keeps implementation simple
* Forces proper test isolation
* Encourages proper CI/CD practices
* Faster test execution

**Why scheduled cleanup instead of automatic expiration?**

* 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:**

```
Backend (Attribute) <-> Adapter <-> Cloud Provider (String/JSON)
```

**Benefits:**

* 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:**

```java theme={null}
hub:
  cloud:
    provider:
      type: aws  # Strategy selection
```

**Benefits:**

* 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

```
AWS SDK Exception
      |
DynamoDBStoreSupport.wrapAwsException()
      |
CloudProviderException (with ErrorType)
      |
Backend catches and handles
```

### 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`

```java theme={null}
protected <T> Mono<T> applyRetry(Mono<T> mono, String request, String partitionKey) {
    return mono.timeout(dynamodbTimeout)
            .doOnError(e -> log.warn("Error during dynamodb request: {} {}", request, partitionKey, e))
            .retryWhen(dynamodbRetry
                    .doBeforeRetry(retry -> log.debug("Retrying DynamoDB request {} {}", request, partitionKey)));
}

private static boolean isRetryableError(Throwable e) {
    return (e instanceof TimeoutException)
            || RETRYABLE_EXCEPTIONS.contains(e.getClass())
            || ((e instanceof AwsServiceException) && isRetryableStatusCode(awsServiceException));
}
```

**Configuration:**

```yaml theme={null}
hub:
  aws:
    dynamodb:
      retryStrategy:
        requestTimeout: 500ms
        maxRetries: 10
        backoff: 100ms
```

## 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

```java theme={null}
Mono<StringComponent> get(ComponentId id);
```

**`Flux<T>`:** Represents 0 to N elements

```java theme={null}
Flux<ComponentId> deleteByPartitionKey(String name);
```

### Common Operators

**Transformation:**

```java theme={null}
.map(json -> AttributeBuilder.ofJson(json))
```

**Error Handling:**

```java theme={null}
.onErrorMap(this::wrapAwsException)
.defaultIfEmpty(AttributeBuilder.of())
.onErrorReturn(AttributeBuilder.of())
```

**Composition:**

```java theme={null}
.flatMap(component -> deleteRelated(component))
.then(deleteComponent())
```

## Dependency Injection Flow

### Bean Registration Order

1. **Cloud Provider Beans** (AWS)
   * Registered by `AwsStoresAutoConfiguration`
   * `ComponentDynamodbStoreBean`, `AwsApiKeySecretStorage`, etc.

2. **Adapter Beans** (Backend)
   * Registered by component scanning
   * `AttributeComponentConfigReaderAdapter`, etc.
   * Autowired with cloud provider implementations

3. **Service Beans** (Backend)
   * `ComponentConfigServiceFacade`, `ApiKeyServiceBean`, etc.
   * Autowired with adapters

### Example Injection Chain

```
ComponentConfigServiceFacade
      | (injects List<ComponentConfigReader>)
AttributeComponentConfigReaderAdapter
      | (injects cloud provider reader)
AwsComponentConfigReader
      | (injects AWS SDK client)
SecretsManagerClient
```

## Testing Strategy

### Unit Tests

**Adapter Tests:**

* Mock cloud provider delegates
* Test type conversions
* Test error handling

**Cloud Provider Tests:**

* Mock AWS SDK clients
* Test exception mapping
* Test retry logic

### Integration Tests

**LocalStack:**

* Full AWS service emulation
* End-to-end testing
* No cloud costs

**Test Containers:**

* Embedded DynamoDB
* Isolated test environment

### Example Test Structure

```java theme={null}
@Test
void shouldConvertJsonToAttribute() {
    // Given
    var delegate = Mock(ComponentConfigReader.class);
    var adapter = new AttributeComponentConfigReaderAdapter(delegate);
    var json = '{"key": "value"}';

    // When
    when(delegate.getByKey(any())).thenReturn(json);
    var result = adapter.getByKey(configId);

    // Then
    assertThat(result.key).isEqualTo("value");
}
```

## Performance Considerations

### Connection Pooling

* AWS SDK manages connection pools automatically
* Default: 50 connections per client
* Tune for high-throughput scenarios

### Caching

**Secrets Manager:**

```yaml theme={null}
hub:
  aws:
    secrets:
      cache: true
      cacheTtl: 300s
```

**Benefits:**

* Reduced API calls
* Lower latency
* Cost savings

**Trade-offs:**

* Stale data for up to cacheTtl
* Memory usage

### Batch Operations

* Use DynamoDB `BatchGetItem` for multiple reads
* Use `BatchWriteItem` for 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

**In Transit:**

* 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):

1. Create module `cloud-provider-azure`
2. Implement interfaces from `cloud-provider-api`
3. Map provider exceptions to `CloudProviderException`
4. Create Spring Boot autoconfiguration
5. Add tests

### Supporting New Storage Types

To add new storage interfaces:

1. Define interface in `cloud-provider-api`
2. Add domain models to `hub-domain`
3. Implement in cloud provider modules
4. Create adapter in backend (if type conversion needed)
5. Update autoconfiguration

## See Also

* [Cloud Provider Overview](/technical-specification/hub-backend/cloud-providers/overview)
* [AWS Provider Details](/technical-specification/hub-backend/cloud-providers/aws)
* [GCP Provider Details](/technical-specification/hub-backend/cloud-providers/gcp)
* [GCP Managed Kafka Setup](/technical-specification/hub-backend/cloud-providers/gcp-managed-kafka)
* [Local Provider Details](/technical-specification/hub-backend/cloud-providers/local)
* [Implementing Custom Providers](/technical-specification/hub-backend/cloud-providers/implementing-providers)
* [Configuration Reference](/technical-specification/hub-backend/cloud-providers/configuration-reference)
