Docstore
The DocStoreClient
class in the multicloudj
library provides a portable document store like abstraction over NoSQSL database providers like Amazon DynamoDB, Alibaba Tablestore, and Google Firestore. It supports core document operations like create, read, update, delete (CRUD), batching, and querying with the support of indexing.
Internally, each provider is implemented via a driver extending AbstractDocStore
.
Feature Support Across Providers
Core API Features
Feature Name | GCP Firestore | AWS DynamoDB | ALI Tablestore | Comments |
---|---|---|---|---|
Create Document | ✅ Supported | ✅ Supported | ✅ Supported | Insert new documents |
Get Document | ✅ Supported | ✅ Supported | ✅ Supported | Get the document by key |
Put Document | ✅ Supported | ✅ Supported | ✅ Supported | Insert or replace document |
Replace Document | ⏱️ End of June’25 | ✅ Supported | ✅ Supported | Replace existing document |
Delete Document | ✅ Supported | ✅ Supported | ✅ Supported | Remove document by key |
Update Document | ⏱️ End of June’25 | ⏱️ Coming Soon | ⏱️ Coming Soon | Update operations not yet implemented in any provider |
Batch Operations
Feature Name | GCP Firestore | AWS DynamoDB | ALI Tablestore | Comments |
---|---|---|---|---|
Batch Get | ✅ Supported | ✅ Supported | ✅ Supported | Retrieve multiple documents in one call |
Batch Write | ✅ Supported | ✅ Supported | ✅ Supported | Write multiple documents atomically |
Atommic Writes | ⏱️ End of June’25 | ✅ Supported | ✅ Supported | Atomic write operations across multiple documents |
Query Features
Feature Name | GCP Firestore | AWS DynamoDB | ALI Tablestore | Comments |
---|---|---|---|---|
Basic Queries | ✅ Supported | ✅ Supported | ✅ Supported | Filter and projection queries |
Compound Filters | ✅ Supported | ✅ Supported | ✅ Supported | Multiple filter conditions |
Order By | ✅ Supported | ✅ Supported | ✅ Supported | Sort query results |
Order By in Full Scan | ❌ Not Supported | ❌ Not Supported | ❌ Not Supported | ** It’s too expensive ** |
Limit/Offset | ✅ Supported | ✅ Supported | ✅ Supported | Pagination support |
Index-based Queries | ✅ Supported | ✅ Supported | ✅ Supported | Query using secondary indexes |
Query Planning | ✅ Supported | ✅ Supported | ✅ Supported | Explain query execution plans |
Advanced Features
Feature Name | GCP Firestore | AWS DynamoDB | ALI Tablestore | Comments |
---|---|---|---|---|
Revision/Versioning | ✅ Supported | ✅ Supported | ✅ Supported | Optimistic concurrency control |
Single Key Collections | ✅ Supported | ✅ Supported | ✅ Supported | Collections with only partition key |
Two Key Collections | ✅ Supported | ✅ Supported | ✅ Supported | Collections with partition + sort key(uses indexes in firestore) |
Configuration Options
Configuration | GCP Firestore | AWS DynamoDB | ALI Tablestore | Comments |
---|---|---|---|---|
Regional Support | ✅ Supported | ✅ Supported | ✅ Supported | Region-specific operations |
Custom Endpoints | ✅ Supported | ✅ Supported | ✅ Supported | Override default service endpoints |
Credentials Override | ✅ Supported | ✅ Supported | 📅 In Roadmap | Custom credential providers via STS |
Collection Options | ✅ Supported | ✅ Supported | ✅ Supported | Table/collection configuration |
Creating a Client
To begin using DocStoreClient
, use the static builder:
CollectionOptions collectionOptions = new CollectionOptions.CollectionOptionsBuilder()
.withTableName("chameleon-test")
.withPartitionKey("pName")
.withSortKey("s")
.withRevisionField("docRevision")
.build();
DocStoreClient client = DocStoreClient.builder("aws")
.withRegion("us-west-2")
.withCollectionOptions(collectionOptions)
.build();
Document Representation
The Document
class accepts either a user-defined class or a generic map.
Option 1: Using a POJO (Player)
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Player {
private String pName;
private int i;
private float f;
private boolean b;
private String s;
}
Player player = new Player("Alice", 42, 99.5f, true, "metadata");
Document doc = new Document(player);
Option 2: Using a HashMap
Map<String, Object> map = new HashMap<>();
map.put("pName", "Alice");
map.put("i", 42);
map.put("f", 99.5f);
map.put("b", true);
map.put("s", "metadata");
Document doc = new Document(map);
Actions
Once you have initialized a docstore client, you can call action methods on it to read, modify, and write documents. These are referred to as actions, and can be executed individually or as part of a batch using an action list.
DocStore supports the following types of actions:
- Get retrieves a document.
- Create creates a new document.
- Replace replaces an existing document.
- Put puts a document whether or not it already exists.
- Update applies modifications to a document (not supported yet).
- Delete deletes a document.
Each of the following examples illustrates one of these actions.
Basic Operations
Create
Create will throw an exception ResourceAlreadyExists
if the document already exists.
client.create(doc);
Get
To retrieve a document, you must provide a Document
initialized with the corresponding object and pre-populate the fields that uniquely identify it (e.g., partition key and sort key):
Player player = new Player();
player.setPName("Alice"); // Assuming pName is the partition key
player.setS("metadata"); // Assuming s is the sort key
client.get(new Document(player));
With optional fields you want to retrieve:
client.get(new Document(player), "pName", "f");
Replace
Replaces the existing doc, will throw ResourceNotFound
is the document doesn’t exist.
client.replace(doc);
Put
Put is similar to create but will not throw in case the document doesn’t exist.
client.put(doc);
Delete
To delete a document, the input must also have the required key fields populated:
Player player = new Player();
player.setPName("Alice");
player.setS("metadata");
client.delete(new Document(player));
Update (Not Supported)
client.update(doc, Map.of("f", 120.0f)); // Throws UnSupportedOperationException
Batch Operations
Batch Get
List<Document> docs = List.of(
new Document().put("pName", "Alice").put("s", "metadata"),
new Document().put("pName", "Bob").put("s", "stats")
);
client.batchGet(docs);
Batch Put
List<Document> docs = List.of(
new Document().put("pName", "Alice").put("f", 10.5f),
new Document().put("pName", "Bob").put("f", 20.0f)
);
client.batchPut(docs);
Queries
DocStore’s get
action retrieves a single document by its primary key. However, when you need to retrieve or manipulate multiple documents that match a condition, you can use queries.
Queries allow you to:
- Retrieve all documents that match specific conditions.
- Delete or update documents in bulk based on criteria.
The query interface is chainable and supports filtering and sorting (depending on driver support).
DocStore can also optimize queries automatically. Based on your filter conditions, it attempts to determine whether a global secondary index (GSI) or a local secondary index (LSI) can be used to execute the query more efficiently. This helps reduce latency and improves performance.
Queries support the following methods:
- Where: Describes a condition on a document. You can ask whether a field is equal to, greater than, or less than a value. The “not equals” comparison isn’t supported, because it isn’t portable across providers.
- OrderBy: Specifies the order of the resulting documents, by field and direction. For portability, you can specify at most one OrderBy, and its field must also be mentioned in a Where clause.
- Limit: Limits the number of documents in the result.
Query query = client.query();
// Apply filtering, sorting, etc.
(Depends on driver implementation.)
Advanced Usage
Action Lists
ActionList actions = client.getActions();
actions.put(doc1).get(doc2).delete(doc3);
actions.run();
You can also chain operations directly using the fluent API with enableAtomicWrites()
for atomic execution:
client.getActions()
.create(new Document(new Player("Alice", 1, 3.99f, true, "CA")))
.create(new Document(new Player("Bob", 2, 3.99f, true, "PT")))
.create(new Document(new Player("Carol", 3, 3.99f, true, "PA")))
.enableAtomicWrites()
.create(new Document(new Player("Dave", 4, 3.99f, true, "TX")))
.create(new Document(new Player("Eve", 5, 3.99f, true, "OR")))
.create(new Document(new Player("Frank", 6, 3.99f, true, "NJ")))
.run();
Atomic Writes:
If you want to write your documents atomically, just use the enableAtomicWrites
as above and all the writes are this will be executed atomically.
Close the Client
client.close();