A possible architecture with Backbone
Introduction
This document try to present a solution, as generic as possible, to handle the complexity of doing Web Business Applications.
As you will realize I have took lof of ideas from Marionette JS, Odata and Backbone itself. Please read about them if you feel you are missing something.
In this document I try to apply the Backbone guidelines and the continuous enforcement of almost one single development principle: SOLID1.
By not mean I have all the answers, nor this proposal is perfect or I am trying to blame others. By the contrary, this is just my two cents on the construction of business apps.
So please read this post not as brand new idea, but as the result of my experience working on eCommerce apps and a compendium of best practices.
Vision, Aim
As I express in the introduction my aim is define a technical overview for business apps. Define a code structure that is flexible enough for the current state of the market, where every single line of code written adds value, where the amount of refactor (a huge problem is in big companies) is minimum.
I see a code base where A/B testing is something easy and natural, where code can be added and removed by safely editing a package.json file, where new teams can be created and use the code without any worries at all.
My vision is a code that evolves, where the complexity is per module and related with business aspect exclusively.
Content
I think that the root of the complexity is in the interaction among agent and the understanding of each of them inside an application. For this reason the current document is organized around each agent in our code base, putting the focus on the client side.
Besides, for each of them a definition and a list of contracts is provided.
Any interaction outside the ones defined here will be prohibited.
Note: I assume the existence of an underlying framework that provide basic functionality on top of backbone. But don’t worry, it will only provide generic features and those will be described in this document.
Contracts
As one of the most important points to model complexity is a clear definition of the valid interaction, each of them will be defined through a concise template.
What they are?
Each contract define the valid interface in which each component/agent can be used.
Why?
As business application tend to be big code bases with lot of developers, when the clear the aim of each agent the easier it will be to add new feature to the code.
Template
Overview
This section provides a brief idea of that the contract is about.
Purpose
A list of the aims pursued by the contract.
Content
This is the main section of the contract where the interaction is described.
Benefits
List of the benefits gained after applying the proposed rule. This allow to have a better judgment of the value of the contract.
Extensibility
Independently what kind of application we are building we need to plan for the unknown, for the evolution of our code.
Examples
In order to evacuate any possible doubt all contract provide an example of applicability.
Application Definition
Definition
This is the only none-technical contract in the entire post. Nevertheless it is not less important, as it set the mind set that all developer must have when facing a new feature.
Through the pass of the time the conception of business apps have changed, from simple applications that fulfill small requirements to complex frameworks.
Part of the reason why business apps are so complex is because market is complex, and the need to improve ROI is constant. Having defined what all stakeholder expect from the app is really important. Should we develop the as if it will be used by other teams? Are there Manager interested in creating a framework from this project? How much the team involved is expected to grow?
As a developer you must think that the code you are producing will always be extended and used by someone else to create something that you have not idea.
Components
Any big application posses lot of component, but for the sake of simplicity let mention the following:
- Code base: All your code.
- Developers: We as authors and customizers of this code bases are part of it, and assuming it will give us the needed vision to work.
Contracts
Team-Application Definition Contract
Overview
This contract formalize and synthesize the responsibilities and the goal every developer must understand in order to develop in the app.
Purpose
- Define the mind set every person that edit the code needs to have.
Content
Believe it or not your code is many things, from a future framework to a customer facing application.
This translate to the fact that every class you made will be for sure ran under unplanned context. From different browsers to different aim and usages. Lot of our code base is reused by other teams, from in-company to third parties and who knows what more.
On the other hand, the final reason why you are developing is to delivery a functional application that provide value/features for final customers.
All these conditions makes from any business app a complex scenario hard to model. There is no golden guide to rule them all, so at the end of the day will be the developer who decide which is the best way to create something.
My humble help is this, we need to think of our work as the fulfillment of a contract with the rest of the consumer of our code.
Benefits
- More aspect will start to be configurable.
- Communication will be easy as all the team will be in the same page.
- People will regularly start to be more careful about code editions.
Extensibility
When the expected result is clear new teams can be created easily than when the only aim is create new features.
Examples
N/A.
Modules
Definition
Having an excellent understand of what a module is, is more than essential, as they are our main unit of work. (At least in this post). In other word, is the definition itself what ends up being the contract to interact with modules.
RAE: “Piece or unitary group of pieces that are used repetitively to build a thing”
Wikipedia: “Modular programming is a software design technique that emphasizes separating the functionality of a program into independent, interchangeable modules, such that each contains everything necessary to execute only one aspect of the desired functionality”
From my personal perspective, a module defines as a group of asset that work together to achieve one single requirement. They represent our unit of “work”, encapsulating and abstracting away all its internal implementation details.
When defining what a module does you must be able to do this without using any linkers at all.
Components
Based on our previous definition, every component needed to fulfill the module's aim, apart from the framework, is part of the module. Some examples can be:
- Configuration
- JavaScript: Views, Routers, Module, Stores, Utilities classes, Services & Backend Models
- Assets: Images, Fonts, translations, etc
Although this definition is correct, it is incomplete. In the same way certain files depend on other files inside a module, some modules depend on other modules.
Another point to consider is the relation between a module and the framework. Even that we are the creators of our app this relation and dependency must be clearly defined and used. Independently of what dependency manager do you use, AMD, CommonJS or whatever.
Contracts
The following list is mainly advocated to formalize what a module is. For this reason there are extremely related to each other. They could even be one single contract but to be more understandable they are divided into 3 related aspects.
Before analyzing these contracts, we should see how the industry is handling this issue. One excellent example that solve almost all the problems we are facing is npm, so I base many ideas expressed here on this project.
Module-Definition-Unit Contract
Overview
If you agree that you will divide all your app into modules, there can not exist anything else than modules. No special modules that are actually mini-apps nor anything like that.
Purpose
- Respect and honor the definition of module.
- Reduce the needs to apply cross-modules hacks.
Content
In one single sentence:
Everything that belong to a module must be inside that module & everything is a module.
Deducing from the module definition all assets that directly helps the module to fulfill the it's aim, must be part of the module.
Give a definition to know if certain assets directly help a module or not, that works in all cases, is extremely hard and in practically unnecessary.
In other words, I consider each module as a require code to implement one feature, considering backend and frontend assets.
In order to have a good understanding in this area next I will analyzing some of the most important assets.
Module's configuration must be located inside the each module. If there are components that are located outside a module, it is totally impossible to use such module in any other context without replicate its configuration. (Believe me, all big companies fails on this)
In the majority of the cases modules should only access its own configuration. It does not have too much sense that a module need read/write the configuration of other module. Although there are exceptions like cross-module configurations that can be helpful to reduce duplicate code, even in this case this configuration must be in one single place, in the application configuration.
There are scenarios that can seem confusing at fist, for instance those modules that handle the user preferences. This one seems to be an exception of the previous statement. But in fact, modules likes this are just like any other module, it brings information from back-end and expose it in the client side. So if a module need to access this information you have to declare a dependency from one module to another (please see next contract for a reference on inter-modules dependency management).
In this section we didn’t define what a configuration is, anyway for the sake of simplicity I consider ONLY JSON2 data to be valid configuration.
Bootstrapping information is a common task to retrieve information and configuration from the back-end at the load time. Again what and how is exposed are decisions that must be taken by the module and be specified inside the module boundaries. Of course, supported by a generic functionality provided by the underlying framework (in this case I am of your code, not Backbone not anything else).
Do not use global variableslike MyCompany.ENVIRONMENT inside modules.
In some scenarios you can use them to load multiples back-end configs, but in any case remember that this is a task performed by the underlying framework to optimize load time..
Based on the previous aspect each module will be able to specify a front-end and back-end configuration, and now, with this points, a way to bootstrap back-end information.
Notice the different between configuration and information, with the latter, information, we can be retrieve the result of any computation in the back-end.s
Module's composition files and the units of composition to create. We claim that our application is composed by using multiples modules together.
In other words, exclusively only when you are working inside a module or adding a new feature, you can refer to files inside a module.
Consequences of this are simple, we continue needing to concatenate front-end and back-end files, so instead of grabbing files inside modules, we should ask each module what assets need to be concatenated in the back-end and in the front-end.
If we see npm, each node package specify main property that points to the file that represent the entry point of the module.
In our case we will need to specify:
- Front-end entry point: As specified by npm with a main entry is an excellent idea.
Besides we will require that from that file all its internal dependencies can be computed so we can generate the tree of files that will take part of the front-end code.
- Back-end entry point: Currently this is not in part of npm, so I propose again making this convention explicit by defining a backEndMain property in the package.json file of the module.
- Front-end configuration: This is again a new concept. A file which only has JSON data that is being pointed by the property configuration of the package.json file.
The underlying framework will be responsible for loading this file and send it to the module through a module API (more of this on the next contract).
- Back-end configuration: This is the counterpart of the previous point.
- Back-end bootstrapping information. Although this is something you will want to handle carefully as it affect directly your loading time, is an API you will need to have explicitly. Again, another package.json property, in this case called backEndBoostrapModel, which will point to a back-end file with one single method, loadData, that will be executed to bootstrap the require information.
Benefits
- Real modularity in our code base.
- The mechanism to add or remove what data is boostrapper will be module-agnostic.
- The use of the global variable must be occasional and well justified.
- There wont exist computation (functions) on the configuration.
- Allows life cycles per module and not force an entire release of all your application.
- Configuration will be safe to edit.
- Configuration will be serialized safety and thanks to this new visual editor can be created on top of this.
- Creators/Customizers of modules wont be require to know how the framework work in order to bootstrap information or edit the configuration of a module.
- Any file inside a module can be edited without affecting others.
Extensibility
As module's assets are simple files, extend a module is just a matter of adding a new file to it.
Examples
Modules as unit of composition can be seen more clearly when we contrast them with files as unit of composition. If we use files, it does not make any sense to have them grouped in folder, by just prefixed them with the functionality they are related would be enough. In this case our code base would be a big list of files where some of them start it name, let say, “Address” other with “CreditCard”, and we can after the functionality add another prefix to indicate if it is front or back-end logic, so a sample file could be: Address.Front.Details.View.js.
Module-Definition-Dependencies Contract
Overview
This contract try to formalize what are the parts of a module and what is needed for a module to fulfill its purpose. All the points treated in this contract are direct consequence of the module definition.
Purpose
- Enforce our Module's definition.
- Allow real modularity in our code.
- Reduce unexpected side effect when editing a module.
- Release developers from the requirement of knowing how the entire application works in order to understand a single piece of the code.
- Permit adding and removing modules, for instance to make A/B Testing, in an extremely simple way.
Content
This section model how modules handle to express dependencies. There are two main points regarding dependencies that we need to define; Given a module WHAT are its dependencies, and given a module HOW to know what exports.
What are the dependencies of a module should be defined in the package.json file.
It is of paramount importance to notice the different kind of dependencies modules can have:
- Mandatory Dependencies: When a module, in order to achieve its aim, needs from another module, a mandatory dependency is created.
For example: the module Checkout.Shipping.Address has a mandatory dependency with the module Address, because if the latter is not present the former by no means will be able to accomplish anything.
As it is predicable, this kind of dependencies are the less common in the context of feature modules. This is a consequence of our initial definition of a module. One module must provide only one functionality.
Notes: All feature used from the underlying framework must be treated as ordinary modules regarding to dependency management. Having global will significantly increase the coupling of the final application. Besides this would mean that there are more than one way to handle dependencies, which is the aim at all. (so no global $ nor jQuery)
- Optional Dependencies: Almost all of dependencies that a module has falls in this section. I recommend this so when a module is not present you application will continue working.
For example: The main PDP View must optionally depends on the Cart Model. If it is present in the application the 'Add to Cart' button will be visible in the view, but if not (perhaps the current application is only a read-only catalog) the PDP must continue working.
As we base or modules on npm we should us the property dependencies for mandatory dependencies and optionalDependencies for optional dependencies.
Side Effects
- One peculiar side effect is at a View level, composite Views will start supporting null as a child view, as there could be the cases where an optional dependency is not meet.
Benefits
- It wont be allowed to require other files outside the module.
- As backbone already provide mechanism to access current view, there wont we any reason to require jQuery. So no module will use jQuery by itself at all.
- Allows life cycles per module and not force an entire release of all the application.
- Based on npm with a touch of “Universal” JS.
Extensibility
As we have defined all the dependencies on properties of the package.json file by just editing this file we will be adding or removing any dependency.
Examples
An imaginary module 'Transaction Searcher' module will depend optionally on Deposit Application, Credit Memo and Payment, as these module can or cannot be present in the current application and this does not means that the Transaction Searcher is not able to accomplish its aim.
Module-Application-Interaction Contract
Overview
This contract defines how to access other modules information and how the interaction between a module and the application must be.
This can be seen just as a study case where I apply the Dependency Injection pattern in the context of a business apps.
Purpose
- Allow modules to interact with each other.
- Allow composition on module's functionality.
- Allow modules to access Application services.
- Allow module to exports functionality in a context agnostic way.
Content
It is useless to define all the dependencies and components a module has if there is not way to access them.
If our composition unit is the module it has not sense to require individual files, we must interact with a module. In other words, every module must publicly expose the API to interact with it.
I can see two aspect in this contract.
Exporting or exposing internal module components and information. Fortunately we can use the mechanism that npm provides. The only consideration, in case of using module.export, will be using Browserify or any other transpiler to run your code on the browser.
Anyway, you will need a mechanism to communicate modules with the application. We will define a mounting process, in which modules are called to be mounted into the current application. An initialization process but at the module level. We will implements this by calling a pre-defined function (lets call it startUp) in the backend and frontend file entry point. Which means that some information is passed from the application to the module and some information is retrieved from the module.
It will be responsibility of the module to return an object (the facade of the module) that allows interact with it. This object, the module's API, must be totally documented.
The tradeoff in this case will be not expose internal implementation details, but offer a rich interface to interact with the module.
Access dependencies information. When the startUp function is called two parameters must be passed, the application instance and an object with the dependencies specified in the package.json. It will be the responsibility of the module's entry point to preserve this object so inner components of the module will access them too.
This second object will have a property for each module's dependencies, where the name of these properties will be the same as the name of the requested module.
I know this may seems unnatural, why not just requiring the dependencies as in any npm module? To allow extension point during this process, allow setting default values when a module is not present, etc. Believe me, in the context of big business app you never know what will be the next feature.
This information accessed wont be more than the exported interface/API of each of the require modules.
In the case of optional dependencies, if they are meet, they will be accessible as any other dependency (property of the second parameter object), if it's not, the property with the module name must be set to false making explicit that the module was requested but the dependency was not meet.
All the previous has as a consequence that all inter-module communication will be done through decouple mediums.
When module to module communication is require, for example to indicate that an address has been created, an event through the Module's API can be thrown.
Note: As event can become unmanageable I recommend name-spaced all of them in the following way:
moduleName:entity:action
An example could be: address:addressCollection:add to indicate that at item was added into the address collection of the address module.
Finally I would recommend RxJS to handle events, but we will leave for another post.
Benefits
- Easily mocking entire modules.
- The edition on one module not affect other modules.
- Applying an entire refactor of a module, while it preserve the module's API, wont have any particular risk at all.
- Stability and QA coverage could be really measured per module.
Extensibility
By just passing more values in the startUp entry point function we can extend this interface.
Examples
This allows us to define log entry for each module that is requested but not present. Or add logging information of the inter-module communication.
Views
Definition
Views represents the piece of code where almost all of the interaction logic is located. So model them, understand them and interact with them correctly means we are handling correctly a big portion of our application computation.
Let see some definitions of Views:
Backbone: “Backbone views are almost more convention than they are code — they don't determine anything about your HTML or CSS for you, and can be used with any JavaScript templating library. The general idea is to organize your interface into logical views, backed by models, each of which can be updated independently when the model changes, without having to redraw the page. Instead of digging into a JSON object, looking up an element in the DOM, and updating the HTML by hand, you can bind your view's render function to the model's “change" event — and now everywhere that model data is displayed in the UI, it is always immediately up to date.”
Personally I would define a view as the piece of code in charge of handling the interaction between the a user interface3 (generally representing user requests and the output given the user, the DOM, a console or whatever) and a store4 (state container) through the invocation of methods and listening to events in the latter.
If you agree with this definition probably the name Controller rather than View is more appropriate. Particularly if we see other framework that apply the MVC pattern.
Components
From the above definition we can observe that a View only knows about a generic user interface and a state container – Store from now on.
- The generic user interface in our Views is represented by a Template. Functions that takes a context object (state) and returns a representation of it.
Templates are part of views, every view has a default template and in the majority of cases is the view that define what template it use. This not always is the case neither a rule, but is the way I recommend using views and templates.
- It turns out that there are lot of cases where this simplistic interpretation of the reality is not enough to implement all business requirements without code repetition. For this reason is that views need be composed with child-views (e.x Marionette.js composite view)
In this case Views only know about those child views that helps the principal View's purpose. Treating them in a very generic way. Please refer to the View-Child-Views Contract for more information.
- The Store is injected to view (Dependency Injection pattern must be used in this case). This is so because the view must directly interact with store, and in order to avoid coupling the code, this interaction must be done through the store interface.
Contracts
I consider the following list of contracts extremely important. Please read them all in a consciously way.
View-Template Contract
Overview
This contract formalize the most important interaction you can find inside any module, the relation among Views, Templates and Stores. How and what data is shown into the user interface for the final data consumer.
Purpose
- Leave totally clear how developer must handle the View-Template interaction.
- Define the information flow to write into the user interface.
- Define the information flow to read from the user interface.
- Draw the basic structure of a generic Views.
Content
Previous definition of Views focus on what Views are, but not explicit mention on how this interactions are done. For this we will say that a View determines WHAT content is shown and the Template determines WHERE and HOW it is shown.
In this case what, for example, can be what properties of the current user are shown, where indicate in what location it must be presented and how refers to the style applied to it.
This means that for each responsibility there is only one agent in charge of it.
As in this contract we have two components interacting, two aspect can be model.
First, the flow of data from the view to a user consumable representation.
For a view to show/return data must call one single method that its only responsibility is generate an output that describe the View's Store through the help of the View's Template. Just for simplicity lets call this method render. The returned data represent the current state of the Store (not the View's as the View do not have state).
Commonly we use an HTML string to represent this output (obtained from a template), but this MUST be totally irrelevant form the View's perspective as it wont interact with it directly. It could even be an object, a call to an external service or anything else. It will be the underlying framework that in a unified way will handle this.
On the other hand, from this string other intermediate representations can be generated to update/interact with the final user abstraction – DOM in generally. But again, it wont be the View which will know about any existence of the DOM, as by definition the View only knows about a Store and a Template function, nothing else.
In order to avoid errors and unnecessary complexity this flow should be as simple as possible (taking into account that it will be done repetitively along the application, and because it has performance implications). This flow can be represented in the following way:
user interface representation = render(view.getStore(), view.template)
Direct consequences of the previous statements combines sith the View's aim can generate the following pseudo rules that can help us to classify the scope of each component:
- A View is “correctly” developed if the render method is automatically called each time the Store change. Making almost unnecessary to think about this flow.
- The context in which this process take place is totally unknown for the View.
Please notice that I have avoided the mention of particular representations, like jQuery, on purpose. jQuery is just one of many DOM abstractions.
The described interaction denote that the Store is one single unit with the single responsibility of representing certain state. Inside this state I consider EVERY aspect the Template use as an input, if an accordion is expanded or collapsed is part of the Store, the name of the current user shown is part of the store, if there is an error message to show is a property of the Store, and so on. For this single purpose a simple JSON object would be sufficient. But for the sake of simplicity and for detection of changes in the Store I recommend a more elaborated object that could trigger events and track property changes.
While this is correct and architecturally pure, we need to consider the kind of information we want to show. It is simple to realize that it is not the same an address street that the state of a collapsible accordion. For this reason I propose the use of a new Store object containing all the state previously expressed and in which one particular property will be the current View's Model. This give us a granular level of control over the View's State:
- General Store properties: Here all properties that does not have a back-end counterpart will be stored.
This enable the handling of the default user interface state of any View in a generic way. For example if we want that all accordions appears collapsed based on some application-wide condition it will be just a matter of changing the Store factory.5 - Store's Model: This is the property we are now using in Views thanks to Backbone guidelines. Now formalized to only represented back-end information.
Notice that this is just a mechanism that pursuit a way of grouping all the state related with a View. Nothing else than my vision on this particular point.
Whereas this state is represented in one single object, the particular representation can vary.
Please refer to the View-Store Contract for detailed information on that interaction.
To pass this information from the view to the template I would recommend an explicit function in each View, that can be called getUserInterfaceState, which generate the JSON object that represent the state if the view. Please try to compute only once the result of this method, even better return the entire store or a projection of it.
This last observation not just enforce our View definition in which it must be stateless but also make extremely easy to track the View's state.
It is important to note that, as each aspect has one single agent that manage it, transfer the Store from the View to the render process will be done by one single responsible. In this case the already mentioned getUserInterfaceState method.
In this direction of the flow, from the View to the user interface, we have delineated what parts take part of it, what are the responsibilities of each of them and some points showing a possible implementation, but denoting what trigger this process is still missing.
In one sentence:
The user interface must be updated each time the backing Store change.
There is only one case when the View cares about this changes, and it is when it reads input from its user interface. Case in which is the view itself that will apply changes over the Store.
The second aspect we can describe is reading the inputs from the template output. Lets start by pointing that the way in which these requests6 are received should be independent of the user representation choose by the underlying framework.
Ideally we should be able to represent all request information in a way totally agnostic from the source of the input, as the only concern of the View is to update a Store. Update that will be done accordingly to the request intention, without knowing about the input source or type.
Unfortunately, as we currently use Backbone Views, this information came in the form of jQuery event objects7, which are too coupled with the DOM and to jQuery itself.
My intention with this mention to Backbone is not criticize it, but understand what is the cost of using it, what drawbacks it has and clearly visualize what could be a better alternative in the context of this contract.
Any refactor on this point involves extending or modify the default Backbone code, which could be beneficial from a testability perspective, but out of the scope of this proposal.
Assuming we use jQuery events, we will have certain group of View methods in charge of handling requests from the user interface, let call them handler methods. These handlers has as mandatory first obligation; extract all needed information from the passed in event, making sure in this way that the effect of jQuery dies as soon as possible, only being visible in the first lines of these methods.
Even for this cases, Views must not access jQuery directly, only through the View's property $, and only to extract the request properties needed to understand the intention of it.
After this first step is done, handlers will call inner View methods to apply any needed transformation on the recollected input and finally update the Store. In other words, Views only handle the interaction between a user interface and a Store.
It is not a mistake missing to mention neither Templates nor the render flow. Because the process to update the user representation will be triggered when the Store change, without considering who change it. Because of this, the definition (or binding) of “when the X property of the Store change make a render” must be located in one single place/method in the View. I would say in a method called bindEventsOn, executed in the initialization process or when the state of the View is totally formed.
Take in mind that one of the goals of this method is allow the developer to be able to forget to call the render method.
It is hard to define what are the responsibilities of these handlers in a deterministic and concrete way. So let simply agree on the following heuristic:
View handler methods are simple input transformers where its only knowledge is the existence of an input request and an output modification on a Store object.
Consider reading View-Store Contract for more clarification if needed.
Inside this aspect it is important to point the execution order of these eventHanlder methods. From the View angle all event handlers must execute synchronously. Although this seems just the opposite we want to achieve, as View are so related with the user interface, this is correct.
The reason of this is because side effects wont be handle by Views. In order words, if an action require a XHR call, this will be done by the Store (backbone model) and not the View. And if the View is re-rendered during the XHR being processed, the Store will be responsible to have some property to indicate that is prepossessing, so when the View renders will indicate it.
Final join. For the two aspects to work as expected one union point is missing. The Template generate HTML that allow users to interact with, and the View has event handlers to process inputs. But in no point the View indicate to the Template what thing can handle.
For this, the recommendation is that the View in the getUserInterfaceState method also return the list of selectors that can handle. Thereby the Template can change to locate an event trigger element from one place to another.
Finally, a part from this two main aspect I can visualize 2 more miscellaneous considerations.
The name convention that standardize each of the previous mentioned points. The easier to read the code and the more information we can provide in the more consistent way the better. This relieve the developer to innovate with fancy names and overblown ways to split View methods.
My proposal on this:
- bindEventsOn: Only View's method responsible to bind Store events with View render method.
- bindEventsOff: Counterpart of the previous method to unbind events. A good alternative can be one single method called bindEvents that accepts a Boolean parameter indicating if must bind or unbind from Store's events.
- xXXXHandler: All method that are event handlers (these are the ones pointed by the events property of Backbone itself). Example: addToCartHandler.
For more details over name conventions and standardization over classes names, please read Code-Code contract.
State Lifecycle is another aspect that need to be mention. Please notice that in this contract there is not reference to when or how Store are created or initialized, this is on purpose as the View is not responsible for such a task. From the View perspective Stores will exist and share the same lifecycle as the View that contain it.
Benefits
- If we decide to change how our application is being rendered it will be just a matter of changing the user interface used for our templates. For example start using Virtual-DOM, JSX or any other representation of the View state.
- Testability will easier but not complete until jQuery events gets replaced.
- Views are not allowed the access outside its own $el. Because Views MUST NOT know any existence of a DOM or any other concrete browser interface. Even access its own $el should be an exceptional action. This make the View more easy to update.
- The use of jQuery must be reduce as much as possible on Views.
- Tracking changes and debugging errors could be extremely simple and even automated. It would be just a matter of tracing Store changes events.
- Changing a View' State wont require to know how it is implemented or works.
- Enable the handling of the default user interface state of any View in a generic way.
- Views will be light and decoupled of Stores increasing testability and reuse of code.
Extensibility
As this interface will so used along our applications, the extensibility point is particularly important.
I will present a use case as a guide to explain how this interface must be extended. Before you read please notice that could be some parts that are not strictly part of the current interface but do are related with.
The example is: How to add a new Widget into the home view.
The first thing to understand is what our widget really is and what it need.
Our widget will need to render something and probably it will need to interact with the user through the rendering output. In other words, the widget will require a Template and a View.
As in any standard View and Template, the View of the widget will be responsible for handling the interaction with the Store and the user representation and the Template of the widget will be responsible for convert a Store object into a user interface. Please notice the widget is just an ordinary View and Template, and as a such must be defined a standard module.
On the other hand, the first thing the customizer will do will be decide WHERE the widget will be rendered. As we have state before this is a responsibility of the Template of the Home, so in one way or another the template will be edited.
Unfortunately templates are just simple text files that does not allow the installation of plugins, the only way to extend a template is overriding it or editing it direclty.
This point indicate that we need to create an Extension Module for the Home, that will contain the overriding Template.
Knowing that we need to indicate where the widget will be added and that the widget is an ordinary View, we must specify a tag in the template to achieve this (some backbone plugins use data attributes like data-view or data-child).
Although it is not describe in this document, the previous mention attribute change the standard rendering flow by searching for child views inside the View. Let define childViewsContainer property in the View that contains this information. This means that we will need a way extend the default value of this property to add our widget.
For this all Views must provide an extensibility mechanism to add new child view without needing to override nor extend anything. It is nothing else than defining a Plugin in the View.
There is not better place than the Extension Module defined to override the Template, to install this child-views plugin. I would recommend in the front-end entry point of this module.
From all the previous we can conclude:
- We have the widget defined in its own module. Independent of where and how it will be added in the final application. This mean that one team can add new functionality without worrying of breaking any widget.
- The Home module does not suffer any edition, extend or logic override, which make the customizer work safer.
- All the customization are placed in one place. One place to find extra logic, one place to install plugin per module, and one place to override templates.
Leaving aside the fact the template need to be overridden, everything else are benefit of this architecture.
Examples
An example could be a Payment page where all context information is given to the Template through the getUserInterfaceState function and the interaction are handle in standard handle methods.
View-Store Contract
Overview
The intention of this contract is to define the communication of between the View and its state container, the Store.
Purpose
- Trace a line between Views and Stores.
Content
As we expressed before, Views are all about data flows and information transformation, where in one extreme is the user interface and in the other the Store.
This means that Stores does not know the source of the update, which helps them presenting a very simple interface.8 Good Stores will provide methods and properties to the View that update its inner state.
So the keys here are: a) understand that this is an API that alter some state b) make utterly explicit the aim of each action of this interface.
I believe that this can be achievable by stabling the following rules:
- All method must be called in the form of: verb Noun. Where the verb express the action that will be performed, like update, remove, reset, etc, and the Noun is the property or properties where the action will be applied. For instance: removeAddress, changeCountry.
- The Noun of the first point must be a property of the Store.
Clearly this convention is a workaround of the fact that the API produce side effects that are not explicit (on the other hand each of these methods could return a dictionary with the new and old values).
This is an effective and simple approach to update a Store with minimum knowledge about them.
Another tool that can be used by Stores is trigger events for the modified property.
Although previous drawbacks already expressed, the creation of unit tests is achievable.
Please refer to the Stores section for more details on this topic.
Benefits
- As Views will are light, generate traceability over the actions performed by the final user will be much more easier (perhaps extending View.prototype to log each user interface action).
- Stateless Views.
- Stores will behave similar than Backbone Models (use them if you consider appropriate).
- Stronger division between front and back-end thanks that Views wont know the existence of the back-end nor even the network interface.
Extensibility
As this contract first aim is indicate the role of each of the involved agents, the extensibility perspective lose sense. However it is valid to say that any new method over the Store interface is an extension of this contract.
Examples
Given a View that has expanders/accordions that can be collapsed or expanded used to show more details about the current user, changing this state would be a matter of calling a method like: toggleUserInformation().
When the View is being rendered, any Store's property, like userInformationExpanded, would be read and the final user interface update accordingly.
View-Child-Views Contract
Overview
Define the mechanism to compose Views.
Purpose
- Establish the roles of each View involve.
- Model the interaction between Views and Child Views.
Content
As we have claimed before, each agent involved is responsible for one single thing; in this case Views to handle interactions.
From a very practical perspective we can apply this simple principle of single responsibility to a large code base and conclude that each time we need to show/handle certain portion of user interface we must use the same single View.
In fact this observation leads us to very common concepts, which are composition and code re-usability.
In the context of a View this means create Views that handle commonly repeated pieces of code (responsibilities). These Views will be called Child Views (please take into account that this is more a justification for marionette composite view than a new concept).
Any View can be consider as a Child View, as the only condition to be a one is to be used by another View.
The composer View, from now on the Parent View, will be who indicate what are its components, as these are in fact just part of the Parent View that were removed away for the sake of simplicity.
In any case it is worth noticing that Parent Views must not know about internal implementation details of it Child Views. This is so because each View is a “class” and as such encapsulate state and computation from the rest of the system.
To clarify any valid communication between a Parent View and a Child View, this must fulfill the following rules:
- Child Views will not know about it Parent View. One can feel tempted to do this, as a Child View “is part of the Parent View that was removed to avoid duplicated code”. However, if our aim is reach re-usability and maintainability this will fight against us.
- Parent Views will only know how to create new Child Views and anything else about its Child Views. This means the Parent Views will know how to create a Store to each Child View. This should be a simple task like passing a particular Store's property to the Child View constructor, although certain basic computation is understandable.
- When some communication is required, share the Store among child Views. This mechanism of communication place naturally after agree with previous contracts.
In order to have a clear understanding of this topic let see some examples:
Given the scenario of master-details information page, let say an Invoice Details page (master) and the list of its items (details). How the CRUD of items is performed?
Firstly, we identify a Store for the entire page, one Parent View to show all the details of the Invoice and a list of Child View called items.
The simplest cases are those when any edition is performed over the Parent View (master), where the data flow is not other rather than the one defined in View-Template Contract.
For the items the first thing we identify is that every user interface aspect with will be controlled by the item's Template, as it has the responsibility to present the content obtained from the View's getUserInterfaceState method.
By accepting this and understanding the communication rules, it is easy to see that the event handlers for any user interface request from the item will be handle by the item's View. This interaction will ending invoking a method in the Child View's Store.
It is of paramount importance to realize that:
Parent View and Child Views share a common Store.
This conclusion allows us to re-write the communication rules to:
All Parent View – Child View communication will be performed through the shared Stored in a decoupled way. Which allows child views to re-render by listen to those event in the store that they care about.
When the main components are in place, we can pay attention to some technical details:
- Dependencies: If the Child View is internal of the current module, it will be required in the same way any other internal file is requested, by using require.
If the view is an external component (from another module) it will be injected as any other module dependency and the Parent View will be responsible to instantiate the Child View but not know WHAT View is instantiating. - Collection View: Along the description of this contract we have not mention, on purpose, particular responsibilities like iteration. A Collection View must exists to handle the responsibility of iterate over a collection.
Thankfully this View already exists on frameworks like marionettejs. - Constructor and Destructors: From the Parent View perspective its Child Views are always the same instances. Parent Views only instantiate new Child Views when it is first rendered.
This is the reason why views like 'Collection View' are so critical, as they must take into account the details of creating and destroying new Child Views when the passed in collection change.
Again, the Parent View will only see the single instance of the Collection View itself, and the rest of the process will be encapsulated by the Collection View. - Event Handlers: As we have described before Child Views should only attach to the user representation inputs (events). Perhaps there could be some particular case where Child Views can also attach to Store events, but this must be address carefully as it make the Child View harder to be reuse.
Benefits
- Updating component Views (those that are module agnostic) should really easy, as Parent View wont really know what concrete external view are using.
- The memory consume will be minimum as Child Views will be created only once per View showed and only latter re-rendered.
- View code more maintainable.
Extensibility
Adding new child views plays an important part for the work of any dev, for this all Views must offer plugins to add extra child view without needing extend the parent view nor override it.
Examples
An Invoice details Page. This page shows all the details of an Invoice, like in any standard View it show information, and show the list of Items it has. If you want to change the child view used to render the invoice's items, you should only change the injected component view to another that expected the same Store interface.
Stores
Definition
This is the world I use in this post to describe:
“A generic purpose data container.”
We will used it to differentiate between user interface state and Backbone Models, in other words Front-end facing state and Back-end facing state.
This concepts is necessary to maintain View clean and decoupled from the user interface state.
Notice that Backbone.js makes an excellent job by offering Models, Views, Collection and Routers. But we will need to model this difference between front-end and back-end state specially in the case of business apps.
Regarding the user interface state, along the history lot of stateless frameworks that does not take into account the state as a part of it, have been created. Reason why they all end it up falling. Just to mention some: All the XUL family, JSF, ASP.NET Forms, etc.
Components
From its definition any state can be put inside a Store, which in fact is the aim.
But in order to make the state manageable we identify two parts:
- User interface state. Hold values like if an accordion is collapsed or not. These can be any property
- Back-end entities representation. These are our Backbone models.
Contracts
There is no contract Store-Model as Models are part of the Stores. Neither Store-Backend Service as Stores are general-purpose data container, it will be the related Model who knows about a back-end service.
Client-Store Contract
Overview
This contract formalize, from the Store point of view, how to interact with it.
In the following text the term client denote any consumer of a Store.
Purpose
- Define the basic interface any Store must fulfill.
Content
Every Store most present a clear interface that leave totally clear what are the side effects of every action performed. In other words, we need to understand we are modeling a state API.
There will be two possible interactions, read the state and update the state of the Store.
To read any property I recommend be aligned with the Backbone.js API, use get methods. Idem for simple variables updates, use a set method.
For more complex edition, let say, add an item into the cart, the idea will be the same as the one expressed in the View section, use method in the form verb Noun, like addItemToCart or removeAddress. In these cases a property with the name of the noun must exists on the Store.
Notice here that it is not recommended to update internal properties of the Store directly. Although this can seems the right way to do it, this leave Stores without way to know when something change (in other words, use the method / API).
As the Store does not care who is its client it must indicate what are the side effects when an updated is performed.
I would recommend two possible ways, on each invoked method return the list of values altered, or in the same way Backbone.js does, trigger an event for each property's value changed.
The final point to describe is the usage of Stores. In most cases Stores, as a single unit, are single purpose, while Backbone Models are general purpose, as they can be used between modules or many times in the same module (the same back-end entity can be used for many reason in the front-end, for example a product).
Benefits
- Controlled side-effects on the execution.
- Easy to centralize the edition of the user interface state. For instance, show by default collapsed all expander controls.
- Easy to debug applications.
Extensibility
As JavaScript is not a strong type language we can add extra method into the Store freely.
Examples
By using Stores you will be able to use a Store-factory and in this way apply application-wide setting, like in mobile all accordions are collapsed by default.
Store-Factory Contract
Overview
In the context of this new Store entity I can envision the need of create another associated agent, a Store Factory. A Store Factory will be a class responsible to generate new instance of Stores.
Purpose
- Define a generic way to create new Stores.
- Localize the agent responsible for create new Stores.
- Centralize all common and standard user interface state properties.
Content
This contract proposes the creation of a new Store Factory.
A Store Factory will be a Singleton class responsible for create new Stores. The aim is unify all user interface properties along your app. So this Store Factory wont necessary require a Model to create a new Store, anyway it represent an excellent place to inject mock model when necessary (unit test context).
In the context of our business app the vast majority of clients a Store Factories will be Routers, as they are the responsible to instantiate new Views, moment when Stores are required.
It is important to understand that this is a new concept that try to handle part of the problem of State Management, so in this contract there is not implementation details nor low level specifications. However, in my humble opinion the Store API could be something as simple as the following:
Factory.createStore(ViewClass, [Mode], [Extra_Properties_Object]) ::= Store
The Store will have at least one single method responsible to create new Stores, that will accept a View class, which will be mandatory, an optional Model instance and an optional object with extra user interface properties.
Benefits
- Unified mechanism to change cross-module user interface properties.
- A standardization from UX teams (if you have any) on the most common user interface properties could help a lot.
Extensibility
I do not have a strong opinion on this, but a good starting point could be dependency injection container. Take them as an example to register a new association between Views and Stores. Please remember that our unit of work are Modules.
Examples
Stores are created, generally, when Views are created to render some content. In this scenario Routers will be responsible to call this Store Factory and pass the resulting Store to the new View.
For this almost any View called from a Router is a good example, for instance the User Profile page that render the user details accordion and need a Store to save its state.
Models
Definition
Models currently are client side representation of back-end records (tables, documents, whatever you use).
Let see some definitions:
Backbone “Models are the heart of any JavaScript application, containing the interactive data as well as a large part of the logic surrounding it: conversions, validations, computed properties, and access control. You extend Backbone.Model with your domain-specific methods, and Model provides a basic set of functionality for managing changes.”
Based on this Backbone definition and the general idea expressed along this document, I feel attracted by this definition. In the context of our app I would define a Model as: extended client side representation of your back-end records.
Understanding by extended, that this classes posses all the method strictly related with the data they have, for example, the ability to add new items into the Model's Cart (method addItemToCart).
Components
Before listing Model's component, it is important to notice that Collection (backbone collections) are not analyzed in this documents as they only represent a particular use case for a Model, they represent a utility class rather than an architectural choice.
In simple scenarios Models will be only composed why its inner properties, but in more complex cases a Model can be composed by other Models and other Collections.
- Inner Models. In this point Collections are consider.
- Simple properties. Canonical case of attributes (backbone inner property)
Contracts
Take in mind that not Model-View contract will be presented as the state related with a View will be accessed through a Store.
As the my experience indicate, 99% of the time all business app are data centric. For this reason I strongly recommend using any OData-like backend service.
Finally, notice that the bootstrapping of data model is being handle by the Module section of this document.
Model-Service Contract
Overview
Formalize the single data entry point for all you app.
Purpose
- Centralize all network accessed
- Unify client-server communication
Content
Although I assume a OData-like end-point as the main back-end data service, there could be cases where the back-end interaction will be more based on actions than data operations, for which reason this contract try to be as generic as possible.
Based on our Models definition, they are client side only, which means that all the information needed to understand, create or edit a Model must be in the client. For this reason is that the list of properties, default values and the queries applied to obtains this information must belong to the client Model.
Apart from this considerations, having a back-end url, back-end query, and so on, a Model is just Store used with a particular purpose.
I wouldn't recommend any new API on this, just use the defaults property already provided by Backbone and regarding queries use any Odata-like framework.
Finally, I conclude that model are the only agent in all your app that will be able to interact with the network interface. If there is a case where some interaction with the Back-end is needed which is not data-based (for example send an email from the client-side) a new agent to handle this cases should be created.
Benefits
- One place to define data used to populated front-end models.
- Better documentation, as each module define its data structure and default values.
- One place to handle all network requests. This is very important, because it will enable the creation of new features, like client side cache by using service-proxies.
Extensibility
Thanks that all data request specification is in the client side and the back-end is a generic query service, extend it is just a matter of update the front-end model. I recommend add plugins for this, so other devs can execute code on “pre-loading” data and “post-loading” data without need to override any module.
Examples
Search among all user order. In this case the View will fetch the related model and the model by using its internal odata-like query will retrieve the list of user orders.
Code
Definition
This section analyze code as if it were a generic interface, trying to formalize those aspect that affect to all classes, models and so.
This section does not describe style related aspect, as the aim of this section is model the interaction between pieces of code, no how the code looks like.
The main aim is define a standard interface to be used along all your products, so when a new piece of code is released by you company any dev will feel comfortable and know how to update the code base.
Components
Although it can seem silly it is important to understand that the following list helps to or directly define an interface, so they must be treated carefully.
- Generic Classes: Any utility class.
- Predefined Classes (Store/Models, Views, Routers, etc): Any of the previous described components
- Events: These have not been analyzed too much but also represent an important part of any code interface.
Contracts
Code-Code Contract
Overview
This contract formalize the generic API aspect of any piece of code inside your app.
Purpose
- Allow third parties to safely edit our code.
- Provide guidelines on how to define generic code APIs.
Content
Let start by recognizing that each of the previous component define an interface or API. This can seem a bit unclear, as this generic approach is hard to control or automate, but from the consumer of the code there is always an interface to interact with.
Method aims. As in the majority of cases we interact in an imperative way, by calling method, each of them must leave totally clear in its name what its aim, what are the side effect of calling it.
Each method most perform one and only one action, which internally can be decompose into multiples small tasks (composition is an excellent approach).
Please refer to the other sections on this documents to read about the recommend method names in each case.
Method documentation. Some times the method name is not enough to express all that it does, this is why an excellent documentation is mandatory to ALL not auto-generated methods.
Documentation is not just a piece of obvious text above a method name, but a clear and concise description of the action performed, the types accepted and the types returned. Those latter points are extremely important, do not forget we develop in a dynamic type language, so saying that a method returns Object is not very valuable.
Method accessibility. Developer must know what method are safe to use (or less probably that they change in the next release of your app). This will help you a lot when your code is in production and new devs start editing the code base.
I propose use two kind of names:
- Public method: These are the recommended to be used by any dev. So they must be very concrete and simple, and they will be preserve, as much as possible, during different version of your code.
As the aim of these method is present a public API, each of them most allow the installation of plugins where I recommend at least the following two: pre<Method_Name>Execute and post<Method_Name>Execute.
Perhaps throwing pre and post event can also be very useful for devs that want to extend your code.
It is very interesting to notice that a good use of this convention will help a lot to provide a clear semver versioning structure. When any of the public method is edit in such a way that is not legacy compatible any more, a new mayor version of the module must be created.
- Private method: These method are used and controlled by the author of the code. To distinguish these from the public ones, they must start with an underscore, like _validateItemOption for instance.
These method wont offer any plugin at all, and can change or even disappear from one version to another.
A good semver structure on our modules, plus a well followed code standard like this, will make the life of our devs much more easier. Allowing them to know which customization is still valid or not, what code can be edited safe and be able to extend.
Generally, we can see how by defined a short group of clear rules, we can provide a lot of meta-data per method; what is does, how can we edited, how safe it is to edited, etc.
This is very useful, if a Router need to invoke method in a View it will know that must use public methods.
Benefits
- Code easy to extend.
- Customization will be more easy to preserve during different version of you app.
- Meta-data expressed on each method.
Extensibility
Generally all public methods must provide extension point, plugins, where its default execution flow can be edited. This is of paramount important if we want devs extend our code safely and without coupling their code to one concrete version of your app.
When the context justify, the use of events is an excellent options, always namespacing them to identify the module, file, and method.
The idea here is very simple, have decoupled that is easy to update, NOT wrap method and use clear interfaces.
Examples
If a new feature to trace the entire execution of an order wants to be created, thank to this contract it will be easier and clear what and how to trace. It will be just a matter of installing the corresponding plugin just for the public methods on the selected classes.
This trace example can be generalized to almost any meta-feature or aspect that wants to be created cross-module. This things generally are very hard to accomplish lack of a clear and unified API across the code.
Routers
Definition
In the context of this post Routers are being used to link URLs to function execution.
This definition is so generic on purpose, because I don't have any rule nor guide on how Router should behave.
Let see some definitions of Routers:
Backbone: “Backbone.Router provides methods for routing client-side pages, and connecting them to actions and events. For browsers which don't yet support the History API, the Router handles graceful fallback and transparent translation to the fragment version of the URL.”
I personally would define a router as a class responsible for linking a URL with an Inversion Of Control mechanism which is aiming to instantiate new Views.
Components
Based on the Routers definition we can easily observe the following components:
- Views: Router will link URLs to logic and the aim of this logic is show Views. So Views must be known by Routers.
- Store Factory: In order to instantiate a View a Store is needed and so a Store-Factory.
- Models: In order to instantiate a View a Store is needed, and in the vast majority of cases a Model is also required.
Contracts
Router-Navigation Contract
Overview
This contract try to make clear what is the relation between URL and Routers.
Purpose
- Formalize the creation of Backbone Routers.
Content
A good starting point to understand this contract is remembering the way input requests came from the user presentation to the View's handler method.
For Router, the request come from the browser that update its URL (it is irrelevant who trigger this event) which generates the call of a Routers' method. Until here, this is just Backbone specifying URLs in the routes property.
In the same way that Views has a name convention, I propose that all method that are called in a Router when a URL change, navigation method for short, were prefixed with the words navigateTo and follow the structure:
navigateTo <View Type> [option]
The prefix will distinguish the navigation method from any other method in the router, View type will indicate what will be called, for example list, edit, etc. Finally, an optional suffix for those cases where the navigation will show a page with a very particular aim, for example navigateToListClosed to difference between a list of closed invoices from a list of open ones.
To sum up, this is the only place is all the our code base, where the URL should be read.
Benefits
- Router must be easy to unit test.
- It will be easy to know what method are routes handler.
Extensibility
Extending this contract can be seen in two ways; just add a new router to your Backbone.Router or extend the Backbone.Router code to allow a wider API.
Examples
Any standard Backbone.Router formatted as expressed above is a good example.
Router-View Contract
Overview
Formalize how and where Views must be created.
Purpose
- Clarify the process in which new Views are instantiated.
Content
From the reading of the Router definition we can see that it will be the agent responsible to instantiate Views and inject on them all needed information. For this reason is that Routers must apply the Inversion of Control pattern, by injecting into Views, Stores, Model and any other require parameter.
I leave under consideration of the reader evaluate that Router also pass into View the instances of the Children Views, achieving in this way really single one place where Views are created. Having this would allow the creation of Factories Views, usage of Dependency Injection Container, decouple Factories, etc.
Benefits
- Single point where Views (not Children Views) are instantiated.
Extensibility
I am not really sure if it is worthy, with in the methods that handle navigation you could add plugin so external views can intercept this processing and even change the instance of the view that will be instantiated.
Examples
The rendering of any View is a good example for this contract.
Tools
If you are developing a business application you will want to expend the most of your time adding value to your customer, not memorizing all this rules. For this to be true, you will need a nice set of tools to accomplish this.
I wont give any particular recommendation, but take into account yeoman to create generator for your modules and any part of them. Gulp/grunt to define custom tasks that validate the name conventions, JSCS, JSLint, etc, etc.
The only thing to point out; plan in advance the creation of this tools, you will need them.
https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)↩
I would accept an extended JSON format to accepts comments, short-jsdoc is preferred.↩
From now on the term “user interface” will be use, as the initials UI are generally used to refer to some graphical framework, like DOM, GTK or some other.↩
I wont talk about models here, because Models in the business apps are related with the back-end state representation, and in this case a store refer ANY state represented in user interface, like the state of a collapsible accordion. ↩
I am not explaining here how Stores are created, but clearly having a Factory is great idea.↩
Notice the use of the term request and input are interchangeable, and there is not mention to events nor DOM element on purpose.↩
Please revise the last version of Backbone where this hard dependency with jQuery has been removed↩
Consider the difference with the View-Template interface where knowing about jQuery event is mandatory.
Update: This is not longer true with the latest version of Backbone.↩