{"id":4354,"date":"2012-04-18T08:40:52","date_gmt":"2012-04-18T12:40:52","guid":{"rendered":"http:\/\/www.ericfeminella.com\/blog\/?p=4354"},"modified":"2020-03-12T23:00:41","modified_gmt":"2020-03-13T03:00:41","slug":"decoupling-backbone-modules","status":"publish","type":"post","link":"https:\/\/www.ericfeminella.com\/blog\/2012\/04\/18\/decoupling-backbone-modules\/","title":{"rendered":"Decoupling Backbone Modules"},"content":{"rendered":"<p>One of the principle design philosophies I have advocated over the years, especially through various articles on this site, has been the importance of decoupling. And while I could go into significant detail to elaborate on the importance of decoupling, suffice it to say that all designs &#8211; from simple APIs to complex applications &#8211; can benefit considerably from a decoupled design; namely, with respect to testability, maintainability and reuse.<\/p>\n<section>\n<h2>Decoupling in Backbone<\/h2>\n<p>Many of the examples which can be found around the web on <a href=\"http:\/\/backbonejs.org\/\" target=\"_blank\" rel=\"noopener noreferrer\">Backbone<\/a> are intentionally simple in that they focus on higher level concepts without diverging into specific implementation or design details. Of course, this makes sense in the context of basic examples and is certainly the right approach to take when explaining or learning something new. Once you get into real-world applications, though, one of the first things you&#8217;ll likely want to improve on is how modules communicate with each other; specifically, how modules can communicate without directly referencing one another. <\/p>\n<p>As I have mentioned <a href=\"https:\/\/www.ericfeminella.com\/blog\/2012\/04\/10\/persisting-backbone-collections\/\" target=\"_blank\" rel=\"noopener noreferrer\">previously<\/a>, Backbone is an extremely flexible framework, so there are many approaches one could take to facilitate the decoupling of modules in Backbone; the most common of which, and my preferred approach, is decoupling by way of events.<\/p>\n<h3>Basic Decoupling with Events<\/h3>\n<p>The simplest way to facilitate communication between discreet modules in Backbone is to have each module reference a shared event broker (a pub \/sub implementation). Modules can register themselves to listen for events of interest with the broker, and modules can also communicate with other modules via events as needed. Implementing such an API in Backbone is amazingly simple, in fact, so much so that the documentation provides an example in the following one liner:<\/p>\n<pre>\r\nvar dispatcher = _.clone( Backbone.Events );\r\n<\/pre>\n<p>Essentially, the <code>dispatcher<\/code> simply clones (or alternately, <a href=\"http:\/\/underscorejs.org\/#extend\" target=\"_blank\" rel=\"noopener noreferrer\">extends<\/a>) the <a href=\"http:\/\/backbonejs.org\/#Events\" target=\"_blank\" rel=\"noopener noreferrer\">Backbone.Events<\/a> object. Different modules can reference the same dispatcher to publish and subscribe to events of interest. For example, consider the following:<\/p>\n<pre>\r\n\/\/ A basic shared event broker\r\nvar broker = _.clone(Backbone.Events);\r\n\r\nvar Users = Backbone.Collection.extend({\r\n  \/\/ reference the broker, subscribe to an event\r\n  initialize: function(broker) {\r\n    this.broker = broker;\r\n    this.broker.on('users:add', this.add, this);\r\n  },\r\n\r\n  add: function(user) {\r\n    console.log(user.id);\r\n  }\r\n});\r\n\r\nvar UserEditor = Backbone.View.extend({\r\n  el: '#editor',\r\n\r\n  \/\/ reference the broker\r\n  initialize: function(broker) {\r\n    this.broker = broker;\r\n    this.$userId = this.$('#userId');\r\n  },\r\n\r\n  add: function() {\r\n    \/\/ publish an event\r\n    var user = new User({\r\n      id: this.$userId().val()\r\n    });\r\n    this.broker.trigger('users:add', user);\r\n  }\r\n});\r\n\/\/ ...\r\n<\/pre>\n<p>In the above example, the <code>Users<\/code> <a href=\"http:\/\/backbonejs.org\/#Collection\" target=\"_blank\" rel=\"noopener noreferrer\">Collection<\/a> is completely decoupled from the <code>UserEditor<\/code> <a href=\"http:\/\/backbonejs.org\/#View\" target=\"_blank\" rel=\"noopener noreferrer\">View<\/a>, and vice-versa. Moreover, any module can subscribe to the <code>'users:add'<\/code> <code>event<\/code> without having any knowledge of the module from which the event was published. Such a design is extremely flexible and can be leveraged to support any number of events and use-cases. The above example is rather simple; however, it demonstrates just how easy it is to decouple modules in Backbone with a shared <code>EventBroker<\/code>.<\/p>\n<h3>Namespacing Events<\/h3>\n<p>As can be seen in the previous example, the <code>add<\/code> <code>event<\/code> is prefixed with a <code>users<\/code> string followed by a colon. This is a common pattern used to namespace an event in order to ensure events with the same name which are used in different contexts do not conflict with one another. As a best practice, even if an application initially only has a few events, the events should be namespaced accordingly. Doing so will help to ensure that as an application grows in scope, adding additional events will not result in unintended behaviors.<br \/>\n<\/section>\n<section>\n<h2>A General Purpose EventBroker API<\/h2>\n<p>To help facilitate the decoupling of modules via namespaced events, I implemented a general purpose <a href=\"https:\/\/github.com\/efeminella\/backbone-eventbroker\/blob\/master\/src\/backbone-eventbroker.js\" target=\"_blank\" rel=\"noopener noreferrer\">EventBroker <\/a> which builds on the default implementation of the Backbone <a href=\"http:\/\/backbonejs.org\/#Events\" target=\"_blank\" rel=\"noopener noreferrer\">Events API<\/a>, adding additional support for creating namespace specific <code>EventBrokers<\/code> and registering multiple events of interest for a given context.<\/p>\n<h3>Basic Usage<\/h3>\n<p>The <code>EventBroker<\/code> can be used directly to publish and subscribe to events of interest:<\/p>\n<pre>\r\nvar Users = Backbone.Collection.extend({\r\n  broker: Backbone.EventBroker,\r\n  initialize: function() {\r\n    this.broker.on('users:add', this.add, this);\r\n  },\r\n\r\n  add: function(user) {\r\n    console.log(user.id);\r\n  }\r\n});\r\n\r\nvar UserEditor = Backbone.View.extend({\r\n  el: '#editor',\r\n  broker: Backbone.EventBroker,\r\n  initialize: function(broker) {\r\n    this.$userId = this.$('#userId');\r\n  },\r\n\r\n  add: function() {\r\n    \/\/ publish an event\r\n    var user = new User({\r\n      id: this.$userId().val()\r\n    });\r\n    this.broker.trigger('users:add', user);\r\n  }\r\n});\r\n\/\/ ...\r\n<\/pre>\n<h3>Creating namespaced EventBrokers<\/h3>\n<p>The <code>EventBroker<\/code> API can be used to create and retrieve any number of specific namespaced <code>EventBrokers<\/code>. A namespaced <code>EventBroker<\/code> ensures that all events are published and subscribed against a specific namespace.<\/p>\n<p>Namespaced <code>EventBrokers<\/code> are retrieved via <code>Backbone.EventBroker.get(<i>namespace<\/i>)<\/code>. If an <code>EventBroker<\/code> has not been created for the given namespace, it will be created and returned. All subsequent retrievals will return the same <code>EventBroker<\/code> instance for the specified namespace; i.e. only one unique <code>EventBroker<\/code> is created per namespace.<\/p>\n<pre>\r\nvar Users = Backbone.Collection.extend({\r\n  \/\/ use the 'users' broker\r\n  userBroker: Backbone.EventBroker.get('users'),\r\n  initialize: function(broker) {\r\n    this.userBroker.on('add', this.add, this);\r\n  },\r\n  add: function(user) {\r\n    console.log(user.id);\r\n  }\r\n});\r\n\r\nvar UserEditor = Backbone.View.extend({\r\n  el: '#editor',\r\n  \/\/ use the 'users' broker\r\n  usersBroker: Backbone.EventBroker.get('users'),\r\n\r\n  \/\/ also use the 'roles' broker\r\n  rolesBroker: Backbone.EventBroker.get('roles'),\r\n\r\n  initialize: function(broker) {\r\n    this.$userId = this.$('#userId');\r\n  },\r\n  \r\n  add: function() {\r\n    \/\/ publish an event\r\n    var user = new User({\r\n      id: this.$userId().val()\r\n    });\r\n    this.usersBroker.trigger('add', user);\r\n  }\r\n});\r\n<\/pre>\n<p>Since namespaced <code>EventBrokers<\/code> ensure events are only piped thru the <code>EventBroker<\/code> of the given namespace, it is not necessary to prefix event names with the specific namespace to which they belong. While this can simplify implementation code, you can still prefix event names to aid in readability if desired.<\/p>\n<pre>\r\nvar Users = Backbone.Collection.extend({\r\n  \/\/ use the 'users' broker\r\n  userBroker: Backbone.EventBroker.get('users'),\r\n\r\n  initialize: function(broker) {\r\n    \/\/ prefix the namespace if desired\r\n    this.userBroker.on('users:add', this.add, this);\r\n  },\r\n\r\n  add: function(user) {\r\n    console.log(user.id);\r\n  }\r\n});\r\n\r\nvar UserEditor = Backbone.View.extend({\r\n  el: '#editor',\r\n  \/\/ use the 'users' broker\r\n  usersBroker: Backbone.EventBroker.get('users'),\r\n  \/\/ also use the unique 'roles' broker\r\n  rolesBroker: Backbone.EventBroker.get('roles'),\r\n\r\n  initialize: function(broker) {\r\n    this.$userId = this.$('#userId');\r\n  },\r\n\r\n  add: function() {\r\n    \/\/ publish an event\r\n    var user = new User({\r\n      id: this.$userId().val()\r\n    });\r\n    \/\/ prefix the namespace if desired\r\n    this.usersBroker.trigger('users:add', user);\r\n  }\r\n});\r\n<\/pre>\n<h3>Registering Interests<\/h3>\n<p>Modules can register events of interest with an <code>EventBroker<\/code> via the default <a href=\"http:\/\/backbonejs.org\/#Events-on\" target=\"_blank\" rel=\"noopener noreferrer\">on<\/a> method or the <code>register<\/code> method. The <code>register<\/code> method allows for registering multiple event\/callback mappings for a given context in a manner similar to that of the <a href=\"http:\/\/backbonejs.org\/#View-events\" target=\"_blank\" rel=\"noopener noreferrer\">events hash<\/a> in a <a href=\"http:\/\/backbonejs.org\/#View\" target=\"_blank\" rel=\"noopener noreferrer\">Backbone.View<\/a>.<\/p>\n<pre>\r\n\/\/ Register event\/callbacks based on a hash and associated context\r\nvar Users = Backbone.Collection.extend({\r\n  broker: Backbone.EventBroker,\r\n  initialize: function() {\r\n    this.broker.register({\r\n      'user:select': 'select',\r\n      'user:deselect': 'deselect',\r\n      'user:edit': 'edit',\r\n      'user:update': 'update',\r\n      'user:remove': 'remove'\r\n    }, this);\r\n  },\r\n\r\n  select: function() {...},\r\n  deselect: function() {...},\r\n  edit: function() {...},\r\n  update: function() {...},\r\n  remove: function() {...}\r\n});\r\n<\/pre>\n<p>Alternately, Modules can simply define an &#8220;interests&#8221; property containing particular event\/callback mappings of interests and register themselves with an <code>EventBroker<\/code><\/p>\n<pre>\r\n\/\/ Register event\/callbacks based on a hash and associated context\r\nvar Users = Backbone.Collection.extend({\r\n  \/\/ defines events of interest and their corresponding callbacks\r\n  interests: {\r\n    'user:select': 'select',\r\n    'user:deselect': 'deselect',\r\n    'user:edit': 'edit',\r\n    'user:update': 'update',\r\n    'user:remove': 'remove'\r\n  },\r\n\r\n  initialize: function() {\r\n    \/\/ register this object with the EventBroker\r\n    Backbone.EventBroker.register(this);\r\n  },\r\n\r\n  select: function() {...},\r\n  deselect: function() {...},\r\n  edit: function() {...},\r\n  update: function() {...},\r\n  remove: function() {...}\r\n});\r\n<\/pre>\n<\/section>\n<p>For additional examples, see the <a href=\"https:\/\/github.com\/efeminella\/backbone-eventbroker\" target=\"_blank\" rel=\"noopener noreferrer\">backbone-eventbroker<\/a> project on github.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>One of the principle design philosophies I have advocated over the years, especially through various articles on this site, has been the importance of decoupling. And while I could go into significant detail to elaborate on the importance of decoupling, suffice it to say that all designs &#8211; from simple APIs to complex applications &#8211; can benefit considerably from a&#8230; <a class=\"read-more\" href=\"https:\/\/www.ericfeminella.com\/blog\/2012\/04\/18\/decoupling-backbone-modules\/\">Continue Reading<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false}}},"categories":[42,44,23,59,78,31,45,35,40],"tags":[],"class_list":["post-4354","post","type-post","status-publish","format-standard","hentry","category-apis","category-code-review","category-design-patterns","category-html5","category-javascript-2","category-oop","category-refactoring","category-software-engineering","category-test-driven-development"],"jetpack_publicize_connections":[],"aioseo_notices":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.ericfeminella.com\/blog\/wp-json\/wp\/v2\/posts\/4354","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.ericfeminella.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.ericfeminella.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.ericfeminella.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ericfeminella.com\/blog\/wp-json\/wp\/v2\/comments?post=4354"}],"version-history":[{"count":0,"href":"https:\/\/www.ericfeminella.com\/blog\/wp-json\/wp\/v2\/posts\/4354\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.ericfeminella.com\/blog\/wp-json\/wp\/v2\/media?parent=4354"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ericfeminella.com\/blog\/wp-json\/wp\/v2\/categories?post=4354"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ericfeminella.com\/blog\/wp-json\/wp\/v2\/tags?post=4354"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}