3rd Party Events, Commands, And Requests - APPLICATION INFRASTRUCTURE - Building Backbone Plugins: Eliminate The Boilerplate In Backbone.js Apps (2014)

Building Backbone Plugins: Eliminate The Boilerplate In Backbone.js Apps (2014)

PART 4: APPLICATION INFRASTRUCTURE

Beyond views, past models and collections, and after the rest of Backbone has been used within the boundaries it creates, there is still a lot of room for growth. The building blocks that Backbone provides are essential in building well structured applications, well organized code, and maintainable feature sets. But these building blocks don’t provide everything you need to build scalable JavaScript application.

Scalability in this case is not a discussion of how many users can run the code at any given time. Since this a browser based JavaScript, it is reasonable to expect that an infinite number of users can use the app at the same time. After all, the browser that runs the code is limited to the current user. It doesn’t have to support more than that. Scalability in browser based JavaScript and Backbone apps, then, is the discussion of features. How well can an application be extended to add new features? How much effort does it take to re-organize the features in order to correct the workflow and process? Will all users be given all code for all features, when the log in to the app? Or can the features delivered to the browser be limited to what the user should see?

Part 4 will dive in to these discussions and create the basic infrastructure needed for scaling Backbone applications by feature set. New building blocks will be created to facilitate feature separation and communication between them. An application bootstrap will create the needed organization for application startup. It will also provide the means by which features can be separated and loaded as needed. Finally, a method of segmenting and organizing code within larger features will be created. This will allow features and functional areas of the application to be re-organized a modified as needed, in a much easier manner.

Chapter 14: 3rd Party Events, Commands, And Requests

One of the core tenets of building a Backbone application of any size, is using an event-driven approach. Everything in the Backbone way of writing code and integrating the different object types relies on events. They are used to communicate changes in models and collections, DOM changes and user interaction, and more. But events are not the only communication means that a scalable application needs.

There are times when the code needs to look beyond events - beyond a statement of “something happened” - and start to look at statements like “do this” and “do this, and let me know”. To venture in to these other realms of communication, additional patterns are needed - patterns such as commands and a request/response system.

Event Aggregators

Events are an integral part of Backbone because they provide a simple yet powerful way of connecting application pieces. One object - a listener - can be notified of something happening from another object - a publisher. The listening object can then respond appropriately and get things done.

The vast majority of events in a Backbone application are set up so that the object listening to an event has a direct reference to the object triggering the event. A Model -> View event relationship is a common example:

1 var SomeView = Backbone.View.extend({

2 initialize: function(){

3

4 // bind to an even from a model, to which

5 // I have a direct reference

6 this.listenTo(this.model, "change:foo", function(){

7 // do stuff

8 });

9

10 },

11 // ...

12 })

When it comes down to it, building a solid Backbone application requires a fundamental understanding of events and how events can be used to great advantage. But events must be viewed beyond two objects that have a direct reference or relationship. It is not always possible for the listener to have a reference to the publisher.

3rd Party Events

When an application begins to grow in size and complexity, it is unreasonable for every object that handles an event to have a reference to the object that publishes an event. Having every object reference each other for events creates a tangled mess of object dependencies. It very tightly couples implementation details of the application’s features to each other by crossing boundaries that should not be crossed. This creates a bad situation where changing one part of an application will cause ripple effects that require changes in other parts of the app.

To avoid this, a third party object can be used. An Event Aggregator is an intermediate object that allows other objects to publish events, and objects interested those events to subscribe to them without needing a reference to the publishing object.

Backbone has an event aggregator built in to it: the Backbone object itself. This object has Backbone.Events mixed in to it, allowing easy event aggregation without having to write any additional code.

A simple use of an event aggregator is view to view communication.

1 var FooView = Backbone.View.extend({

2 events: {

3 "click .doIt": "doItClicked"

4 },

5

6 doItClicked: function(e){

7 e.preventDefault();

8

9 // trigger an event through the

10 // built-in event aggregator

11 Backbone.trigger("do:it");

12

13 }

14 });

15

16 var BarView = Backbone.View.extend({

17 initialize: function(){

18

19 // subscribe to the "do:it" event from

20 // the built in event aggregator

21 this.listenTo(Backbone, "do:it", this.doIt);

22 },

23

24 doIt: function(){

25 // do stuff here

26 }

27 });

In this case, the two views need to communicate, but they do not have a direct reference to each other already. When that situation arises, an event aggregator can be used to facilitate the communication. As long as both of the views have a reference to the same event aggregator instance, they can communicate via that 3rd party.

DIY Event Aggregators

There are times when an event aggregator other than the one built in to Backbone is needed. For example, there may be a sub-application within a larger application. Allowing components with the sub-application to communicate with each other is important. It may also be important to prevent other parts of the over-all application from listening in on this communication. To handle this situation, context-specific event aggregators can be created with only one line of code:

var eventAgg = _.extend({}, Backbone.Events);

Now I can use the eventAgg object anywhere that has a reference to it. If I keep this object’s use within a sub-application, then I have effectively created an internal communications mechanism for that sub-app.

An EventAggregator Type

Having to re-write this one line of code to create new aggregators does get a little tedious, after a while. And although it is simple, it has very low semantic meaning. The only part of this line that tells me the intent is the variable name that is assigned to the result.

To fix the tedium of copy & paste and the nondescript nature of the code, I can build a simple EventAggregator type that can be instantiated:

1 // define an event aggregator

2 var EventAggregator = function(){

3 _.extend(this, Backbone.Events);

4 };

5

6 // use the event aggregator type

7 var eventAgg = new EventAggregator();

Having a constructor function (an object type) named EventAggregator solves both the copy & paste and ambiguity problem. It allows me to create an instance of an object type that has a clear semantic meaning, and it prevents me from having to remember the slightly odd looking syntax of extending an object literal with Backbone.Events.

No Silver Bullets

Event aggregators are an important concept and are a necessary part of building a scalable Backbone application. However, it doesn’t make sense to replace all event bindings with an event aggregator. The primary use case for them is when the object that needs to handle an event does not have a natural relationship or reference to the object that triggers the event. For example, a view that is binding to a model’s events does not need to use an event aggregator because the view already has a reference to the model. A menu view, though, may want to use an event aggregator to say a menu item was clicked. This would allow other parts of the application to handle the menu click without needing a reference to the menu.

Commands

While events are powerful, and 3rd party events through an Event Aggregator even more so, events have a specific semantic that comes with them: “something happened”. Unfortunately, I see a lot of semantic violations for events. It’s quite common to find code like this, for example:

1 foo.on("save:model", function(model){

2 model.save(/* options ... */);

3 });

If I look at the first line of this code, I might think “oh, good. The model was saved. I can go do…” But on looking at the second line, I see that the model has not yet been saved. Instead, this event is being used to tell another object to go save the model. The semantics of “something happened” have been changed and this code actually says “go do this”.

Building A Command System

Now there isn’t anything wrong with saying “go do this”. The problem with the above code is that the wrong semantics are being used within Backbone’s event system. “Go do this” is the semantic meaning of a command, not an event.

I’m going to build a very simple command system to handle the “go do this” semantic, then.

1 // Create a Commands type to handle commands

2 var Commands = function(){

3 this._handlers = {};

4 };

5

6 _.extend(Commands.prototype, {

7

8 // Handle a specific command by calling

9 // the specified handler function

10 handle: function(command, handler, context){

11 this._handlers[command] = {

12 handler: handler,

13 context: context

14 };

15 },

16

17 // Execute the specified command, passing

18 // the provided parameters to the handler

19 execute: function(command, params){

20 params = Array.prototype.slice.call(arguments, 1);

21 var config = this._handlers[command];

22 if (config){

23 config.handler.call(config.context, params);

24 }

25 }

26 });

With this in place, a command handler can be registered and the command can be properly executed, passing any needed parameters along with the execution call.

1 var cmds = new Commands();

2

3 cmds.handle("save:model", function(model){

4 model.save(/* options ... */);

5 });

To execute the command, the code that was previously triggering an event can now call the cmds.execute method:

1 foo.doSomething = function(){

2 cmds.execute("save:model", someModel);

3 }

information

ES6 “…params”

Many languages - like Ruby or C#, for example - provide a way to get all parameters passed in to a method, after a certain position. This is often referred to as a “splat” (Ruby) or params list (C#). In the current version of the JavaScript standard - ECMAScript 5 (or ES5) - there is no way of getting an array of all parameters after a current position. ECMAScript 6 is introducing a “rest parameters” parameter that will facilitate this: function(foo, ...bar). Until the majority of browsers and other JavaScript runtimes support this feature, though, the Array.slice method can be used to facilitate the same feature.

Why Have A Command System?

I often get asked why I bother having a command system in my apps. After all, triggering an event works perfectly fine… most of the time… at least, until it doesn’t. But it technically works. I always answer, “semantics”. When it comes down to it, semantics are important. As my friend Sharon Cichelli says,

Semantics will continue to be important until we find a way to communicate in something other than language.

Semantics play a role far beyond spoken words. They touch every part of the software that we write, including the code. And there are several semantically important things to note in the code for commands.

First, the Commands object has introduced a 3rd party to situation, much like an event aggregator. The introduction of a 3rd party provides an additional level of decoupling that is often needed. It allows the command handler to be registered from one area of the application, and the command execution to happen in another area entirely.

Second, the semantics of commands have been maintained in this code. Instead of having an event that is triggered, forcing a developer to look through the code to find a handler that is actually doing what the event said, this code is saying “go do this”. This difference alone can save hours of headache and days of debugging, wondering why an event is causing some such behavior, and why another event handler is getting fired before that behavior.

“Spent the day figuring out busted tests and trying to decipher other peoples’ intentions in code.” - Chris Hartjes @grmpyprogrammer

Lastly, the same semantics provide a more meaningful and semantically correct API for the Commands objects. Rather than re-use the on and trigger method names from events, the command system provides a handle and execute method. These method names help to maintain the semantics of commands, and provide distinction between events and commands at an API level.

Request/Response

Having a command system in place can be a liberating experience once the “AHA!” moment comes around, regarding the semantic difference between an event and a command. But, sometimes a command isn’t enough. Sometimes I need to do something more than just say “go do this.” Sometimes I need to say, “go do this, and let me know.” In this scenario, I’m dealing with a need to send a request and get a response.

Building A Request/Response System

The basics of a Request/Response system are very similar to that of a Command system. In fact, I’ve often used Commands as the basis for Request/Response. This does tend to lead to some muddy water, though. In spite of the requirement difference between Commands and Request/Response being so small - just the addition of “and let me know” - the semantic difference can be significant. A method name like handle might seem appropriate for requests, but renaming this to respondTo provides an much more rich set of semantics in this situation. Other, similar changes can be made in order to provide a very meaningful API, in spite of the similarities in implementation.

1 // Create a RequestResponse type to handle requests

2 var RequestResponse = function(){

3 this._handlers = {};

4 };

5

6 _.extend(RequestResponse.prototype, {

7

8 // Handle a specific request by calling

9 // the specified handler function, and

10 // returning the result

11 respondTo: function(request, handler, context){

12 this._handlers[request] = {

13 handler: handler,

14 context: context

15 };

16 },

17

18 // Request the specified information, passing

19 // the provided parameters to the responder

20 request: function(request, params){

21 params = Array.prototype.slice.call(arguments, 1);

22 var config = this._handlers[request];

23 if (config){

24 return config.responder.call(config.context, params);

25 }

26 }

27 });

With the RequestResponse type in place, it can be used to facilitate scenarios where one object needs information from another object, and does not need to know which object can respond to the request.

1 var reqRes = new RequestResponse();

2

3 reqRes.respondTo("price:check", function(product){

4 var currentPrice = someService.lookupPrice(product.id);

5 return currentPrice;

6 });

7

8 var ProductView = BBPlug.ItemView.extend({

9 template: "#product-template",

10

11 serializeData: function(){

12 var json = this.model.toJSON();

13 json.price = reqRes.request("price:check", this);

14 return json;

15 }

16 });

In this case, I’m using the BBPlug.ItemView from earlier chapters to build a view for a product. The data from the product is not quite complete when the view is created, though. I need to reach out and get the latest price for the product and I do so using the request/response system. Once the data is returned, the view can be rendered.

Why Build A Request/Response System?

The same core reasons for building a Command system also apply to a Request/Response system. The additional requirement of “and let me know”, however, adds a new dynamic: return values.

When it comes to the implementation, the only real difference between a Command system and a Request/Response system is that the Request/Response system has a return statement for the handler execution. But this one single return statement is part of a larger semantic understanding. When I say request("price:check"), there is value in understanding that a request has a response. I expect to receive a response because the semantics of “request” include a response. By comparison, execute("price:check") doesn’t have the same semantics. It just says “go do a price check” but doesn’t say anything about a response.

Lessons Learned

Scaling Backbone applications introduces a new set of problems, including that of naming things and creating proper semantics in those names. These problems exist within even the smallest of applications, of course, but they become more pronounced as an application grows.

Create Your Own Types

Backbone provides a foundation of building blocks, but it does not provide every type of object and every piece of functionality needed to build scalable applications. Understanding this, and recognizing the need to build additional object types on top of Backbone is important. Creating additional, specialized object types allows an application’s infrastructure to provide much needed functionality, reducing the amount of code the application has, over-all.

The view types that were created in the first two chapters extended directly from Backbone.View. This was the right thing to do because these specialized view types were providing implementations for common functionality within Backbone views. In the case of the EventAggregator, the functionality was there but it wasn’t available in the needed form for all the desired scenarios. Creating a custom object type and mixing in a little bit of Backbone’s provided functionality gives applications new building blocks - more specialized, and more capable.

Not Every Type Has To Build Off Backbone

The Commands and RequestResponse types were natural extensions that came from recognizing the semantics of an event vs command, etc. But in building these add-ons, none of Backbone’s building blocks were used. This creates a scenario where the code written may be useful outside of Backbone applications. It also shows that we have the freedom to move beyond the core pieces of Backbone and create our own building blocks for our systems’ needs.

Isolate Sub-Systems

As applications begin to grow in size and complexity, it becomes more likely that communication mechanisms will repeat names of events, commands and requests. To avoid potential problems with two independent sub-systems using the same names, create sub-system specific instances of event aggregators, command and request/response systems. This allows each sub-system to have it’s own communications bus, freely choosing what names it wants to use.

Semantics Are Important

With more code and more functionality to keep track of in growing applications, it is important to reduce the amount of overhead in a developer’s mind. Proper naming and semantic meaning are important for achieving this goal.

Being able to look at code that was written by someone else - or even written by the person reading it again 3 weeks later - and having an immediate understanding through the semantics of intention revealing names is an easy way to improve developer productivity. By creating anEventAggregator type, the intended use of the code becomes clear without having to read the fine details and usage.

Small Semantic Change, Big API Change

Seemingly unimportant or minuscule differences in requirements or semantics can have very significant effects on a system. It’s these subtleties that separate a generalized, high level difference

The requirements differences between a command and a request may be small, but they have created a large semantic and API difference. Instead of just copying the API for Commands in to the Request/Response system, new method names were chosen to reflect the semantics. This allowed the proper semantic meaning to be carried throughout the entire Request/Response system. It would also allow for the Commands and Request/Response systems to vary independently of each other. That is, the Request/Response system would not be forced to take an API change when the command system changes, or vice-versa.

Similarities Can Be Deceiving

The number of similarities between two sets of requirements can often be deceiving. Our desire, as humans, to see congruence and conformity often leads to a poor understanding of what we are actually looking at. It’s important, therefore, to look at the underlying intent and meaning - the semantics of the two things that are similar. It may just be that the similarities are only skin-deep, and that the semantics are pointing the code in two different directions.