Rich Liferay Applications using Backbone.js and Jersey (Part 2)
In part 1 of this series, we described how Veriday builds rich Liferay portlets using Backbone.js. If you missed the first part or are unsure how to integrate Backbone.js into your Liferay then it will be helpful to read Part 1 first.The approach we describe in part 2 allows your team to be highly efficient and iterative by nature while building Liferay portlets (or any web-based software in general). On Digital Agent, we break our teams into groups of 2-3 developers. Usually the ratio of front-end to back-end is 1:1, but in some cases it could also go to 2:1, depending on complexity.
How does this approach improve our team’s agility and efficiency?
This approach will allow both front-end developers and back-end developers to proceed with their work in parallel with zero time wasted waiting on each other to finish their portion. This allows the other developer to begin working on their section.
JSON as an interface
This requirement is important because it allows the whole team to proceed with their work and tackle each challenge in the most productive way, rather than stitching some scaffolding for the purposes of the development team being able to build.
The first step is for the team to agree on the JSON contract between the front-end team and the back-end team. Here we answer questions such as: 1) what data is needed for this interface and 2) what should the data look like? We always start with what the end product of the front-end experience should be and then work on how to get that data returned in the format that the JSON contract specified.
The Backbone Model & Collection
Below is a code snippet from one of a Backbone model for a “Store”.
define([.. ], function(..){ var Store = BaseModel.extend({ urlRoot: "/stores/", getOwner: function(){ return this.get("owner"); }, ....
From the above model we can see that the end point for this model is “/stores”. The corresponding stores collection is:
define(['models/Store'], function(Store){ var Stores = Backbone.Collection.extend({ model: Store, urlRoot: "/stores/", initialize: function() { ...
The Jersey End Point
The corresponding collection for stores also has the same endpoint “stores”.
@Path("/stores") public class StoreWebservice {
@Resource(name = "storeService") private StoreService storeService;
@GET @Produces(MediaType.APPLICATION_JSON) @PreAuthorize("hasRole('Store Owner')") public List<StoreDto> get(@Context SecurityContext context) { List<StoreDto> result = getStoreService().getAllStores(); return result; } ...
The Jersey web service above defines the corresponding “/stores” endpoint that our Backbone.js model and collections points at. You can also see that the StoreWebservice has access to a “storeService”. This is where different business services can be injected into our JSON API. These other services can also be Liferay services, if needed. A typical pattern we use is to not directly call Liferay services from our web services. We typically wrap Liferay services within our own utility service to ensure Liferay service calls are contained instead of being present all over your application. We also follow this pattern in the front-end where we wrap Liferay Javascript methods with our own JavaScript utility object that contains these calls.
The list of “StoreDTO” that is returned is basically the POJO representation of the Backbone.js model Store.js we showed above. The JSON object behind Store.js and what StoreDTO.java represents is your “JSON as an interface” contract that our front-end and back-end developers agree on before proceeding.
So, how does this increase team productivity?
At this point, our application is nicely broken up into layers in which people can work in without having to wait for others to complete their section. After agreeing on the JSON interface, a typical sprint will progress where the developers working on the back-end can proceed with implementing the new services and data access methods that will extract the required data. The front-end developers will proceed with creating the Backbone.js models, collections (ex. Store.js and Stores.js), Jersey Webservice (ex. Stojrewebservice) and the Java DTOs (ex. Store.java). The front-end developers will even stub out the different methods of the Jersey Webservice, even just hardcode a valid response.
@GET @Produces(MediaType.APPLICATION_JSON) @PreAuthorize("hasRole('Store Owner')") public List<StoreDto> get(@Context SecurityContext context) { List<StoreDto> result = new ArrayList<StoreDto>(); StoreDto store1 = new StoreDto();
store1.setName("My Store"); store1.setAddress("5450 Explorer Drive, Mississauga"); store1.setHours("8am-5pm every day except weekends");
StoreDto store2 = new StoreDto(); store2.setName("New Store"); store2.setAddress("100 Main Street West, Hamilton"); store2.setHours("24/7"); result.add(store1); result.add(store2); return result; }
At this point, our team can proceed with building out their own areas of the application with little dependency on each other’s components, early in on the sprint. We push the integration towards the end of the 2 week sprint where we now have iterated a few times over the front-end and back-end and have ironed out any unforeseen challenges. At this point, what is left is for our developers is to wire up the methods that the front-end team defined, in their Jersey classes, to the actual business services that were implemented.
The approach is not perfect but it definetely helps productivity from day 1. The approach allows us to have developers who are passionate about the front-end focus on the front-end, and those who love working on the back-end focus on the back-end. Even our full stack developers can take full advantage of this approach.
Being able to build applications in this style is also a testament to Liferay’s flexibility. Don’t be afraid to bring your own experience to your Liferay stack!