Define the interface to support firestore
sebelga opened this issue · comments
Firestore has a very similar API to Datastore but with small differences. We need to decide what consensus will be made to support both databases in gstore.
- An "Entity" in Datastore is a "Collection" in Firestore.
- Datastore has an "Ancestor" concept, while Firestore is about "Subcollection"
There are also differences around options that can be provided (excludeFromIndex
for example).
The Firestore node client (https://www.npmjs.com/package/@google-cloud/firestore) has clearly more adopter (500K+ downloads/week) then Datastore (https://www.npmjs.com/package/@google-cloud/datastore) with 60K downloads/week.
So my initial feeling is that gstore should have a new API in the next major release (8.x
) that uses the same terminology than Firestore. There will be a huge work to be made to update the documentation though.
Does anyone have thoughts on this?
I have been analyzing both clients and came to the conclusion that gstore should be able to support both Datastore and Firestore without too much change on the current API.
This is the direction that the new API will go
Fetching data
// GET an entity (or document in Firestore)
Model.get({ name: 'entityName' });
Model.get({ id: 'entityID' }); // Datastore will use this, Firestore will treat it as a `name` alias
Model.get({ key: entityKey }); // only for Datastore
// GET multiple entity
// In Firestore we would use a query like: myCollection.where('id', 'in', ["123","456","789"])
Model.mget([{ name: 'entityOne' }, { id: 12345 }, { name: 'entityThree' }]);
// Any other option will go in a second parameter
Model.get({ name: 'myDoc' }, {
ancestors: ['Parent', 'someID'] // In Firestore this would get a SubCollection doc
transaction, // Datastore or Firestore transaction
...options // all the other current options (cache, ttl, ...)
});
The other Model
APIs (Update, Delete) will follow the same pattern.
Transation
In Firestore, creating a transaction is an asynchronous task
firestore.runTransaction(transaction => { // do something with the Transaction });
In Datastore it is a synchronous task
const transaction = datastore.transaction();
gstore will wrap the Firestore runTransaction
and return a Promise
so the API will be
// Datastore
const transaction = gstore.transaction();
// Firestore
const transaction = await gstore.transaction();
Queries
Firestore does not require any method call to create a query.
firestore
.collection('cities')
.where('state', '==', 'CA');
.get();
Datastore does have a method to create a query
const query = datastore.createQuery();
query.filter('state', '=', 'CA');
query.run();
In order to be able to cache the query, gstore needs to know that we are about to execute a query.. Also, gstore extends the query run()
promise with the populate()
methods that can be chained to fetch nested entities/documents.
The API will thus remain the same for Firestore
User.query()
.where() // all the available chaining methods for querying
.startAt()
.run() // correspond to Firestore get() call
.populate('address') // fetches the address for each user returned