Skip to content

Data Proxy

Aaron Hanusa edited this page Jun 4, 2016 · 32 revisions

The Data Proxy is the actor within the peasy-js framework that is responsible for data storage and retrieval, and serves as an abstraction layer for data stores that encompass (but not limited to) the following:

Possible implementations

  • Relational Databases - SQLite, MySQL, Oracle, SQL Server, etc.
  • Document (NoSQL) Databases - MongoDB, VelocityDB, etc.
  • Services - HTTP, SOAP, etc.
  • Cache Stores - Redis, Azure, etc.
  • Queues - RabbitMQ, MSMQ, etc.
  • File System
  • In-memory data stores for testing

Abstracting the data store layer allows you to swap data proxy implementations within your business services to deliver solutions that are scalable and testable, and to support a multitude of deployment scenarios, all while being able to have them subjected to the command execution pipeline.

Public functions

getAll(callback)

Asynchronously returns all values from a data source in an array and is especially useful for lookup data.

getById(id, callback)

Accepts the id of the entity that you want to query and asynchronously returns an object.

insert(object, callback)

Accepts an object and asynchronously inserts it into the data store. This method should return a new object instance with updated state.

update(object, callback)

Accepts an object and asynchronously updates it in the data store. This method should return a new object instance with updated state.

destroy(id, callback)

Accepts the id of the entity that you want deleted and asynchronously deletes it from the data store.

Creating a Data Proxy

Creating a data proxy is simple. Create an object with the functions which you choose to support. These functions will then be consumed and invoked by a command based on the successful result of rule executions.

Below is an example of an in-memory data proxy implementation. Please note that this code might be written more efficiently and has been scaled back for brevity and serves only as an example:

var customerDataProxy = function() {

  var store = [];
  
  return {
    getById: getById,
    getAll: getAll,
    insert: insert,
    update: update,
    destroy: destroy
  };

  function getById(id, done) {
    var person = findBy(id);
    done(null, Object.assign({}, person));
  }

  function getAll(done) {
    var all = store.map(function(item) {
      return Object.assign({}, item);
    });
    done(null, all);
  }

  function insert(data, done) {
    data.id = store.length + 1;
    store.push(Object.assign({}, data));
    done(null, data);
  }

  function update(data, done) {
    var person = findBy(data.id);
    person.name = data.name;
    done(null, data);
  }

  function destroy(id, done) {
    var person = findBy(id);
    var index = store.indexOf(person);
    store.splice(index, 1);
    done(null);
  }
  
  function findBy(id) {
    var person = store.filter((function(p) {
      return p.id === id;
    }))[0];
    return person;
  }
};

Sample implementation using MongoDB

The following code sample shows what an implementation of data proxy serving as a customer data store might look like using MongoDB. Please note that this code might be written more efficiently and has been scaled back for brevity and serves only as an example:

var mongodb = require('mongodb').MongoClient;
var objectId = require('mongodb').ObjectID;

var customerDataProxy = function() {

  var connectionString = 'mongodb://localhost:12345/orderEntry';

  return {
    getAll: getAll,
    getById: getById,
    insert: insert,
    update: update,
    destroy: destroy
  };

  function getAll(done) {
    mongodb.connect(connectionString, function(err, db) {
      if (err) { done(err); }
      var collection = db.collection('customers');
      collection.find({}).toArray(function(err, data) {
        db.close();
        done(err, data);
      });
    });
  };

  function getById(id, done) {
    mongodb.connect(connectionString, function(err, db) {
      if (err) { done(err); }
      var collection = db.collection('customers');
      var oId = new objectId(id);
      collection.findOne({_id: oId}, function(err, data) {
        db.close();
        done(err, data);
      });
    });
  };

  function insert(data, done) {
    mongodb.connect(connectionString, function(err, db) {
      if (err) { done(err); }
      var collection = db.collection('customers');
      collection.insert(data, function(err, data) {
        db.close();
        done(err, data);
      });
    });
  };

  function update(data, done) {
    mongodb.connect(connectionString, function(err, db) {
      if (err) { done(err); }
      var collection = db.collection('customers');
      collection.update({_id: objectId(data._id)}, data, function(err, data) {
        db.close();
        done(err, data);
      });
    });
  };

  function destroy(id, done) {
    mongodb.connect(connectionString, function(err, db) {
      if (err) { done(err); }
      var collection = db.collection('customers');
      collection.remove({_id: objectId(id)}, function(err, data) {
        db.close();
        done(err, data);
      });
    });
  };

};

Extending an Angular Resource or React Data Store

Coming soon...

Swappable Data Proxies

Because business services have a dependency upon the data proxy abstraction, this means that data proxies can be swapped out and replaced with different implementations. The ability to swap data proxies provides the following benefits:

In many production systems, backend node.js applications are often written to communicate directly with a database. This configuration can lead to bottlenecks and poor performance over time as client consumption increases. peasy-js allows you to easily remedy this type of situation by swapping a data proxy that communicates directly with a database with one that scales more efficiently.

For example, instead of injecting data proxies that communicate directly with a database, you might inject data proxies that communicate with a cache or queue. For retrieval, the cache might return cached data, and for storage, you might update the cache or a queue and have a backend process/service monitoring it for changes. The process/service would then be responsible for data storage manipulation against a persistent data store (database, file system, etc).

Another possible solution would be to expose CRUD operations via HTTP services, and inject a data proxy capable of performing CRUD actions against your HTTP services into your business services. A side benefit of having clients use HTTP services is that you could gain the benefits of HTTP Caching, almost for free, which can provide scalability gains.

Because business services rely on data proxy implementations for data store abstraction, introducing solutions that scale become trivial.

Multiple deployment scenarios

Because data proxies are swappable, you are able to reconfigure data storage without having to refactor your code. Let's take a scenario where an organization has deployed a node.js application that directly communicates with a database. As time passes, the organization receives third party pressure to expose the database to the outside world via web services (HTTP, SOAP, etc.).

After the web services are created, it is decided that the node.js application should dogfood the web services and no longer communicate directly with the database. Consuming business services that abstract data stores easily allow swapping out the database data proxies with proxies that communicate with the web services.

Another example might be supporting an online/offline mode in the client. In this scenario, business and validation logic must always remain, however, the datastore might need to persist data to http services or a local cache, respectively. Simply injecting the correct data proxy implementation into a business service in the time of need could help to easily achieve this workflow almost effortlessly.

Testability

Unit testing should be fast. Really fast. Databases are slow and come with a slew of issues when testing your code via automated unit tests. This is another scenario where swappable data proxies provide great benefit. By creating mock data proxies, you can easily inject them into your business services and focus on testing initialization logic, validation and business rule logic, and command logic.

Available concrete implementations

The following libraries contain base class implementations that abstract communications with varying data stores.

Coming soon...

Clone this wiki locally