Skip to content

Architecture

The Multicloud DB SDK is organized as a multi-module Maven project with a clean separation between the portable API, the service provider interface (SPI), and the provider implementations.


Module Overview

Module Artifact Description
multiclouddb-api com.microsoft.multiclouddb:multiclouddb-api Portable client interface, types, error model, factory, and SPI contracts. The only compile-time dependency your app needs.
multiclouddb-provider-cosmos com.microsoft.multiclouddb:multiclouddb-provider-cosmos Azure Cosmos DB adapter (Java SDK v4)
multiclouddb-provider-dynamo com.microsoft.multiclouddb:multiclouddb-provider-dynamo Amazon DynamoDB adapter (AWS SDK v2)
multiclouddb-provider-spanner com.microsoft.multiclouddb:multiclouddb-provider-spanner Google Cloud Spanner adapter (Google Cloud Spanner 6.62.0)
multiclouddb-conformance com.microsoft.multiclouddb:multiclouddb-conformance Cross-provider integration tests

Samples are maintained in a separate repository: microsoft/multiclouddb-sdk-for-java-samples


Dependency Graph

multiclouddb-api  ← must be released first if API changed
    ├── multiclouddb-provider-cosmos   ← independent of other providers
    ├── multiclouddb-provider-dynamo   ← independent of other providers
    └── multiclouddb-provider-spanner  ← independent of other providers

Providers depend on a released version of multiclouddb-api. They are independent of each other and can be released separately.


API Surface

All application code depends on multiclouddb-api. The core types are:

Type Purpose
MulticloudDbClient Portable interface: create, read, update, delete, upsert, query, provisionSchema, capabilities
MulticloudDbClientFactory Creates a MulticloudDbClient by discovering providers via ServiceLoader
MulticloudDbClientConfig Builder-pattern config: provider selection, connection, auth, feature flags
ResourceAddress (database, collection) pair targeting a container/table
MulticloudDbKey (partitionKey, sortKey) pair - every document needs at least a partition key
QueryRequest Portable expression, native expression, parameters, page size, continuation token, partition key scoping, limit, orderBy
QueryPage Result page: items + optional continuation token + optional diagnostics
SortOrder / SortDirection Sort specification for orderBy - validates field names against injection
DocumentResult Result of read(): document payload + optional DocumentMetadata
DocumentMetadata Write-metadata: lastModified, ttlExpiry, version
CapabilitySet / Capability Runtime introspection of provider capabilities
MulticloudDbException Structured error with category, provider, and native code
PortabilityWarning Signals non-portable behavior
OperationOptions Per-call timeout, TTL, metadata flag
OperationDiagnostics Latency, request units/charge, request ID, ETag, item count

Expression Types

Type Purpose
Expression AST node interface for parsed query expressions
ExpressionParser Parses portable expression strings into an AST
ExpressionValidator Validates parameter bindings and function usage
ExpressionTranslator SPI - translates AST to provider-native query syntax
TranslatedQuery Result of translation: query string + bound parameters

SPI (Provider Interface)

Provider modules implement two SPI contracts without importing each other:

SPI Interface Responsibility
MulticloudDbProviderAdapter Factory - creates a MulticloudDbProviderClient from config; registered via META-INF/services
MulticloudDbProviderClient CRUD + query + provisioning + capabilities - called by DefaultMulticloudDbClient

Provider Discovery

Providers are discovered at runtime via Java's ServiceLoader:

┌────────────────────────────────────────────────────────────────────────────────────┐
│  1. Your app calls MulticloudDbClientFactory.create(config)                        │
│                                                                                    │
│  2. The factory scans:                                                             │
│     META-INF/services/com.multiclouddb.spi.MulticloudDbProviderAdapter            │
│                                                                                    │
│  3. The matching adapter's createClient() builds a native                         │
│     SDK client (Cosmos SDK, DynamoDB SDK, or Spanner SDK)                         │
│                                                                                    │
│  4. A DefaultMulticloudDbClient wraps it with error mapping,                      │
│     diagnostics, and the portable contract                                        │
└────────────────────────────────────────────────────────────────────────────────────┘

No provider imports in application code. Just drop the provider JAR on the classpath (or add it as a <scope>runtime</scope> Maven dependency).


Project Structure

multiclouddb-sdk-java/
├── pom.xml                                # Parent POM (aggregator)
├── multiclouddb-api/                      # Portable API + SPI contracts
│   └── src/main/java/com/multiclouddb/
│       ├── api/                           # Public types
│       │   ├── internal/                  # DefaultMulticloudDbClient
│       │   └── query/                     # Expression AST, parser, validator
│       └── spi/                           # Provider SPI interfaces
├── multiclouddb-provider-cosmos/          # Azure Cosmos DB adapter
│   └── src/main/
│       ├── java/.../cosmos/               # CosmosProviderClient, capabilities
│       └── resources/META-INF/services/   # ServiceLoader registration
├── multiclouddb-provider-dynamo/          # Amazon DynamoDB adapter
├── multiclouddb-provider-spanner/         # Google Cloud Spanner adapter
├── multiclouddb-conformance/              # Cross-provider integration tests
└── specs/                                 # Design documents

# Sample applications (separate repo):
# https://github.com/microsoft/multiclouddb-sdk-for-java-samples

Design Decisions

Why MulticloudDbKey Is an Explicit Parameter

Every CRUD operation requires an explicit MulticloudDbKey parameter - the SDK never extracts key material from the document body:

client.upsert(addr, MulticloudDbKey.of("tenant-1", "pos-42"), doc);

Why not extract from the document?

Each provider stores key fields using different names:

Provider MulticloudDbKey.partitionKey() stored as MulticloudDbKey.sortKey() stored as
Cosmos DB partitionKey (custom field) id (built-in Cosmos field)
DynamoDB partitionKey (hash key) sortKey (range key)
Spanner partitionKey (column) sortKey (column)

A convention-based extractor would need provider-specific logic in what is supposed to be a provider-agnostic interface, which defeats portability.

Concern Explicit MulticloudDbKey Extracted from Document
read() / delete() Works - no document needed Impossible - no document
Consistency All 5 operations use the same pattern Writes differ from reads
Compile-time safety Missing key = compiler error Missing field = runtime error
Source of truth Key is authoritative Ambiguous when fields disagree

See the Developer Guide for the full rationale.