architecting web applications - sapient web applications ... naresh bhatia cto, sapient global...
TRANSCRIPT
Naresh Bhatia
CTO, Sapient Global Markets Co-lead Visualization Practice
Experience Trading and Risk Management applications using JavaScript, Java and .NET
Founder Archfirst (http://archfirst.org) A place for software developers to learn and compare technologies through real world examples
JavaScript Space used to be The Wild West
Sliding banners
5000 lines of procedural code in a single file
Complex DOM spaghetti
Recent Advances in JavaScript
• AJAX enables changing of content without refreshing the entire page
• Browsers implement better and faster JavaScript engines
• Rise of smart web “applications” (vs. static web sites) – Gmail
• People start talking about “architecture” of their JavaScript applications – Object-orientation
– Design patterns
– MVC
– In-application messaging
– Modularity
JavaScript is a respectable platform today
Serious JavaScript developers do worry
about architecture these days!
Separate application state from its presentation
Separate models from their views
Get the truth out of the DOM
MVC Philosophy – Get the truth out of the DOM
Doesn’t matter what flavor of MVC you are using
MVC, MVP, MVVM, MV*, MVWhatever
Models
Views
Introducing Backbone – A Lightweight MV* Framework
• Organize your interface into logical views backed by models – Each view responsible for one DOM element
– Each view can be updated independently when the model changes
– Reduces DOM traversal, increasing performance
• See here for an example (slide 8)
• No need to dig into a JSON object, look up an element in the DOM, and update the HTML by hand – Bind your view's render function to the model's "change" event
– Now every view that displays this model is updated immediately on a change
Backbone MVC Example
http://archfirst.googlecode.com/svn/trunk/html/examples/backbone-mvc-example/index.html
Tile
color: F89F1B
Backbone Models
• Models are Backbone objects that contain “attributes”
• Whenever an attribute’s value changes, the model triggers a change event
// Define a model var Tile = Backbone.Model.extend({ }); // Create an instance of the model var tile = new Tile( ); // Set an attribute to fire the change event tile.set({color: 0xF89F1B});
“change” event Tile
color: F89F1B
Updating views from change events
tile
tileRectView
color: F89F1B
el: model:
// 1. Create an instance of the model var tile = new Tile( ); // 2. Create a view, attach it to the model & DOM var tileRectView = new TileRectView ({el: '#tile‘, model: tile}); // 3. (constructor) Bind view to model change event this.model.on('change', this.render); // 4. Change a model attribute tile.set({color: 0xF89F1B}); // 5. Model fires a ‘change’ event // 6. View catches the event & calls render( ) this.$el.css( 'background-color', '#' + color2hex(this.model.get('color')));
1
2
render( )
4
‘change’ event
6
DOM
<div id="tile" ></div>
5
3
Collections
brokerageAccounts
accountTableView
el: collection:
this.$el.empty(); this.collection.each(function(account, i) { var view = new AccountView({model: account}); this.$el.append(view.render().el); }, this);
DOM
<table id="accounts_table"> <thead>...</thead> <tbody></tbody> </table>
initialize( ) render( )
this.collection.bind('reset', this.render);
brokerageAccount
brokerageAccount
brokerageAccount
Composing an interface from multiple views – View Hierarchy
AccountsPage
AccountsTab
UserPageHeaderWidget
AccountTableWidget AccountChartWidget
AccountView
FooterWidget
AccountView
AccountView
AccountView
Inserting pages and widgets dynamically
index.html
<!DOCTYPE html> <html> <head> ... </head> <body> <div id="container"> <!-- Pages go here --> </div> </body> </html>
A typical page
AccountsPage.js
BaseView.extend({ postRender: function() { this.addChildren([ { viewClass: UserPageHeaderWidget, parentElement: this.$el }, { viewClass: AccountsTab, parentElement: this.$el }, { viewClass: FooterWidget, parentElement: this.$el } ]); } }
A typical widget
UserPageHeaderWidget.js
BaseView.extend({ tagName: 'header', className: 'user-page-header', template: { name: 'UserPageHeaderTemplate', source: UserPageHeaderTemplate } });
UserPageHeaderTemplate.html
<div class="user-info"> <span>{{firstName}} {{lastName}}</span> <img src="img/person-icon-small.png" alt="seperator" /> <a class="js-signOut" href="#">Sign Out</a> </div> <h1 class="ir">Bullsfirst</h1>
Classic Server-Side Layered Architecture
User Interface Layer
Accepts user commands and presents information back to the user
Application Layer
Manages transactions, translates DTOs, coordinates application activities, creates and accesses domain objects
Domain Layer
Contains the state and behavior of the domain
Infrastructure Layer
Supports all other layers, includes repositories, adapters, frameworks etc.
In-Application Messaging
• Interactions between table and chart
• If theses widgets interacted directly, the coupling would be too tight
• Use a message bus (a.k.a. pub/sub) to decouple the components
Account : mouseover
How much of your application can you hold in your head at once?
Bullsfirst is about 2000 lines of JS
Have you ever been in Dependency Hell?
<script src="js/form2js.js"></script>
<script src="js/toObject.js"></script>
<script src="js/base64_encode.js"></script>
<script src="js/utf8_encode.js"></script>
<script src="js/format.js"></script>
<script src="js/treeTable.js"></script>
<script src="js/jalerts.js"></script>
AMD and RequireJS to the rescue
AMD: Asynchronous Module Definition (an API for defining and loading JS modules)
RequireJS: A popular implementation of AMD
Modules allow you to break large applications into bite-size chunks
Code reuse & separation of concerns
Complements the MVC pattern
Structure of a Modular App
src |-- js | |-- app | | |-- domain | | | |-- Repository.js | | | |-- BrokerageAccount.js | | | `-- ... | | |-- pages | | | |-- home | | | |-- accounts | | | `-- ... | | `-- widgets | | `-- account-table | | `-- account-chart | | `-- ... | |-- framework | | |-- Baseview.js | | |-- Router.js | | `-- ... | `-- vendor | |-- backbone.js | |-- jquery.js | `-- ... |-- sass |-- img `-- index.html
60+ JavaScript files 2000 lines of code But much easier to find things!
JavaScript and templates (markup)
JavaScript and templates (markup)
Defining a Module
RequireJS loads all code relative to a baseUrl (set to the same directory as the script used in a data-main)
// app/widgets/login/LoginWidget // Defines a widget called LoginWidget define( [], function() { return BaseView.extend({ ... }); } );
Using a Module
Defining dependency on a module makes sure that the module is pulled in before the dependent code.
// app/pages/home/HomePage // Defines the home page that uses the LoginWidget define( ['app/widgets/login/LoginWidget'], function(LoginWidget) { return BaseView.extend({ postRender: function() { this.addChild({ id: 'LoginWidget', viewClass: LoginWidget, parentElement: this.$el }); } ... }); } );
Moving templates into text modules
<td class="name"> {{name}} </td> <td class="market-value"> {{formatMoney marketValue}} </td> <td class="cash"> {{formatMoney cashPosition}} </td> ...
AccountTemplate.html
Using templates defined in text modules
define( ['text!app/widgets/account-table/AccountTemplate.html'], function(AccountTemplate) { return BaseView.extend({ template: { name: 'AccountTemplate', source: AccountTemplate }, ... }); } );
AccountView.js
Optimizing for Production – The Build System
• A web application consisting of hundreds of JavaScript files could have severe performance implications in a production environment
• RequireJS comes with an optimizer that walks the dependency tree and concatenates all the required JavaScript files into one
• The Bullsfirst build system runs the RequireJS optimizer plus other optimizations such as yuicompressor and uglifyjs to reduce the download size of the application – The build system is based on Grunt.js, a task-based command line build tool for
JavaScript projects
– It cuts down application load time to approximately 2 seconds on a 3 Mbps network
Summary
• Use the MVC pattern to enforce separation of concerns
• Compose your application using smaller Reusable Components
• Create a Domain Layer as a central place for views to access information
• Use In-Application Messaging to decouple application components
• Use Modules to break large applications into bite-size chunks
A strong architecture is key to building engaging web applications
References
• Backbone.js – https://github.com/documentcloud/backbone
• RequireJS – https://github.com/jrburke/requirejs
• Bullsfirst is an open-source project under http://archfirst.org – Sharing best practices with the developer community
– Live demo at http://archfirst.org/apps/bullsfirst-jquery-backbone
– Source repository: https://github.com/archfirst/bullsfirst-jquery-backbone
– Discussion/feedback at http://archfirst.org/forums/general-discussion
43