sebelga / gstore-node

Google Datastore Entities Modeling for Node.js

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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