backbone-arch-demo
An example of a Loosely Coupled Domain-Driven Backbone.js architecture.
All modules should conform to the single responsibility principal.
Requirements
- Node
- Grunt
- Ruby (if using SCSS/Compass)
Installation
Installation should be pretty straightforward:
- Clone this project into the directory of your choosing.
- On the command line,
cd
into this directory. - Run
npm install
. This will install all node dependencies. - Run
grunt
. This will run a build and create a/dist
directory.
Development should be done within the /src
directory. /dist
is your production directory and can be deployed in whatever way you choose.
The term 'root' when used below will refer to either /src
or /dist
Organization
The application is built in a single index.html
located in the root directory. The application is further organized into a sub-directories:
/app
- Our user-written application code./framework
- The base application framework, including a Router, Message Bus, and a BaseView all of our Backbone views will inherit./vendor
- Third-party librariesmain.js
- Require.js configuration and initializationapp.js
- The application initialization code. Starts the router and attaches a useful event handlertext.js
- Require.js plugin allowing import of files as text. Used for templating.
/app
/app
is further broken down into the following directories:
/css
/domain
/img
/pages
/sass
/widgets
/css
, sass
, and /img
contain what you would expect: stylesheets and images used within the application. This example application shows how to use and build Compass with the rest of the application.
/domain
contains our DDD-conformant Domain Models. These models can be manipulated or massaged as needed and the output assigned to widgets as ViewModels. This keeps a single canonical location for our data in the Domain models. Changes are propagated to the ViewModels.
The contents of /pages
and /widgets
are, for all intents and purposes, constructed identically. Each subdirectory represents a complete 'module' to be built with r.js and dynamically loaded by Require. The distinction is one of DOM hierarchy: /pages
are the top-most level views, spawning child widgets from the /widgets
directory. Widgets can themselves spawn other child widgets.
In the current example, there is no need for an intermediate level of hierarchy, but one could extend this to include a /regions
directory that represent sub-sections of a page (e.g. sidebar, content, etc.). These would be modules that are children of pages, but parents of yet more widgets.
A typical module is organized in the following manner:
ModulenameTemplate.html
ModulenamePage.js
/ModulenameWidget.js
(required)ModulenameViewModel.js
In this example, the template is an underscore.js template, but could be any other templating solution. In theory, there could be multiple templates per view, but it should at least cause you to consider splitting out yet another child widget.
The Page/Widget is the entry point for the module. The only way to include this page/widget would be to require the Widget. This, in practice, is the Backbone View.
The ViewModel is a Backbone Model. In many cases, the Domain model is not organized in a way that makes sense on the presentation layer, and needs to be manipulated. The ViewModel is this manipulation of our canonical Domain model that is better suited for displaying the widget in question.
In this example, a single template, view, and view model exist in each page/widget, but it is possible that multiples of each could be created. It is arguable whether this would be a signal for further refinement so that each module only requires a single view, view model, and/or template.
/framework
/framework
currently includes the following files:
BaseView.js
MessageBus.js
Repository.js
Router.js
BaseView.js is an extension of the Backbone.View object, and is itself extended by our application views. This allows our application's views to inherit what we consider to be useful functionality.
MessageBus.js extends Backbone.Events. It currently serves as a very thin facade, but could be enhanced to add additional functionality to the Backbone events.
Repository.js serves as a data controller of sorts, controlling the fetching and caching of our model data from the server. All requests for data go through here.
Router.js is our application router. It controls the the Page views invoked based on the URL.
Behavior
This is a loosely-coupled event-driven architecture. Backbone events are extended onto the MessageBus object, creating an application-level event/messaging bus.
In the current example, this is only used to alert all pages to a route change. The page is responsible for keeping track and removing each of its children on this event.
This can be extended so that pages and widgets can publish/subscribe to any number of namespaced events. See the following resources:
Outcome
This example architecture has a number of pages each loading a number of widgets:
- Home - This loads the mainnav as well as one each of three sample widgets. Note that widget loading order is NOT guaranteed. If widgets need to appear in a specific order, they should be given specific ID'd locations in which to render. These currently all render to the
section.content
element. - One, Two, Three - These pages load three copies of each of the three sample widgets. This shows the capability of loading multiple instances of each widget on the same page.
Note that each of the non-mainnav sample widgets is labeled with its cid to show the individual instances. On route changes, these cid numbers continue to increase as each widget is destroyed and recreated on a page change. Investigating the performance impact of this is a TODO below.
TODOS
- In this pattern, all widgets are destroyed and re-created on each new route. This has proven more reliable than having certain long-running widgets (such as a header or footer) across routes. Need to investigate performance gain/loss and/or whether long-running widgets can be made more reliable.
- Programmatically determine some grunt options
- Add unit testing framework
- Add documentation solutions to build (docco/jsdoc)
- Add image optimization to build
- Expand pattern and build process to work with multiple applications