bl-using-collection-hooks-effectively

Using Collection Hooks Effectively

Business Logic Collection Hooks are a powerful means of creating server side code that allow you to modify, transform, and perform actions on your data before and/or after a request of a particular type (get, save, or delete) is executed, giving you full control of the request pipeline. In this tutorial, we are going to illustrate a few patterns for using collection hooks more effectively. If you are just getting started with Business Logic, check out our Get Started with Business Logic Tutorial, and our Business Logic Guide for a primer and for core concepts.

The source code contained in this tutorial can be cloned from the business-logic-conference-bookings repository on GitHub.

Modifying the Request

The functions used to define collection hooks (onPreSave, onPostFetch, etc.) accept three arguments - request, response, and modules. Two of these, request and response, are, respectively, abstractions of the HTTP request thhat is sent from the client to Kinvey, and the response returned to the client from Kinvey. The third, modules, contains a set of JavaScript libraries available for use in your business logic.

Let's take the example of an app that handles the booking of a conference room to a specific individual. In this particular case, the user doesn't care which room or where that room is located, only that a room is avaailable. In the onPreSave business logic for the bookings collection, we will find and reserve the next available room from a conferencerooms collection, and add that specific room to the booking before it is saved.

function onPreSave(request,response,modules){
  var rooms = modules.collectionAccess.collection('conferencerooms');

  // find the first available room, and set available = false if one is found.  
  rooms.findAndModify({"available":true}, 
          {"lastUsed": 1}, 
          {$set: {"available": false, "lastUsed": request.body.bookingTime}}, 
          function(err, result) {
    if (err) {
      // Database query error, return an error response to the client 
      // and terminate the request
      response.error(err);
    } else {
      // If no rooms found, End the request without saving, 
      // with a message of "no room available"
      if (!result._id) {
        response.body = {message: "No Rooms Available"};
        response.complete();  // completes the request and returns the result to the client 
      } else {
        // If a room was found, add the room ID and name
        // to the request for saving to the bookings collection

        request.body.room = {id: result._id, name: result.name};
        response.continue();    // continues the request 
      }
    }
  }); 
}

Here, if no rooms are found, a message is returned and the request completes, never saving the booking to the collection. If an available room is found, it is added to the request body, which is saved to the booking collection once the execution of onPreSave completes. Thus a request that contains the following data:

{ 
  "user": "johndoe",
  "bookingTime": "1396010640",
  "bookUntil": "1396011600"
}

would end up saving an entity that looked like this:

{
  "user" : "johndoe",
  "bookingTime": "1396010640",
  "bookUntil": "1396011600",
  "room": {
    "id": "52f22d694609ba980401dd56",
    "name": "Conference Room A"
  },
  "_acl": {
    "creator": "kid_TVcFFbFV1f"
  },
  "_kmd": {
    "lmt": "2014-03-28T12:46:44.322Z",
    "ect": "2014-03-28T12:46:44.322Z"
  },
  "_id": "53356f34403f26fb020fd2b7"
}

Modifying the Response

In the same way that you can manipulate a request prior to saving/fetching/deleting, you can also manipulate the response before it is sent to the client. This may be to add some information to the response from an alternate source, or to remove some fields from being returned to the client. For example, using our bookings example again, suppose that after booking the room, you want to only return the relevant booking ID, room name, and bookUntil information to the client, ignoring the other data and metadata.

function onPostSave(request, response, modules) {
  var savedEntity = response.body;

  // set the response body to specific attributes of the saved entity
  response.body = {
    "_id": savedEntity._id,
    "room": savedEntity.room.name, 
    "bookedUntil": savedEntity.bookUntil
  };
  response.complete(200);   
}

This takes what would have normally been sent back to the client:

{
  "user" : "johndoe",
  "bookingTime": "1396010640",
  "bookUntil": "1396011600",
  "room": {
    "id": "52f22d694609ba980401dd56",
    "name": "Conference Room A"
  },
  "_acl": {
    "creator": "kid_TVcFFbFV1f"
  },
  "_kmd": {
    "lmt": "2014-03-28T12:46:44.322Z",
    "ect": "2014-03-28T12:46:44.322Z"
  },
  "_id": "53356f34403f26fb020fd2b7"
}

and pares it down into just essential information:

{
  "_id": "53356f34403f26fb020fd2b7",
  "room": "Conference Room A",
  "bookedUntil": "1396011600"
}

When using the client libraries, it is important to remember that if a partial or modified document is returned to the client, and the client then saves that document, the saved document will replace the original document, and the missing properties will be lost. If you are limiting the data set returned, you should make sure to validate incoming requests on modified data in your pre-Save hook."

Storing Transient Data

Finally, there are times that data needs to be stored temporarily to be used in both pre- and post- hooks, but not saved to the database. For this purpose, Kinvey provides a temporary key-value object store to the business logic API. Small amounts (< 500 kb of total storage) of data and/or objects can be stored temporarily to be used between the pre- and post-hook of a request. This data can be accessed via the modules.utils.tempObjectStore object, and is destroyed upon the completion of a request pipeline.

Returning to our bookings example, suppose you want to access some additional data about the room to include in the response, such as the building it is located in, but you don't want to store it in the database. Rather than having to query the conferencerooms collection twice, you can temporarily store your booked room in the tempObjectStore. By adding a simple command to the callback in your onPreSave event, you can store the room in the tempObjectStore.

modules.utils.tempObjectStore.set("room", result);

In the post-hook, you can access the room object that you saved to the store just as easily, and include it in the response:

var building = modules.utils.tempObjectStore.get("room").building;

Wrap-Up

Collection hooks provide a powerful means of manipulating, transforming, and taking action on data requests as they are executed. By making efficient use of collection hooks, you can reduce calls from the client, reduce I/O operations to databases and external sources, validate incoming data and restricting outgoing data, and much more. When architecting your solution, be sure to think about ways that collection hooks can help to improve the functionality and performance of your application and provide a better all-around user experience.

For a deeper dive into Kinvey Business Logic, check out our Business Logic Reference

Got a question?