Ever wonder about the inner-workings that make Harvest tick? There are a lot of continual updates to our codebase that our users never see. In the lull before our next update to Invoices Overview, we thought it a good chance to give you a sneak peek into the behind-the-scenes work our developers do to make Harvest run better and faster. In this post, our developer Pez gives you a first-hand glimpse into his work to improve the code for Invoices Overview.
Here at Harvest, we like to ship new features, but also take great care to continually improve existing functionality.
We launched Harvest back in 2005, before Rails had hit version 1.0 and Ruby was still at version 1.8. That means we have our fair share of legacy code. Recently some of the legacy code had started to become a blocker to our aim of continual improvement.
When we decided to update the Invoices Overview screen, we had a choice: develop new functionality on top of the existing code, potentially making the problem worse, or rewrite our code.
We decided to take a step back and re-architect this section of Harvest. This meant we could bring the area up to modern standards with feature parity, and then start implementing a few new features to make the section even more useful.
What’s the Problem with Old Code?
Why is legacy code such a bad thing? If it isn’t broken don’t fix it, right?
While it’s true that features written in legacy code will work, legacy code can eventually become a barrier for improving existing functionality for a few reasons:
- It often has less test coverage, meaning it’s easy to break things without realizing.
- It’s usually harder to understand, having drifted from the originally engineered design.
- The additional complexity makes it harder to debug and more complex to add functionality to.
But why does “legacy code” exist in the first place?
Harvest is an evolving app, whether we’re adding new features, tweaking old ones, or just improving the design. As we evolve, we depart from the original design. Assumptions previously made become invalidated, bugs may be introduced, and the foundations that were originally put in place often end up being used for something other than their original intent. This compounds the complexity and introduces potential problems. Like the one we ran into when we decided to improve our Invoices Overview.
As Lehman wrote in The Law of Increasing Complexity (and referenced by Lehman’s Laws):
As an evolving program is continually changed, its complexity, reflecting deteriorating structure, increases unless work is done to maintain or reduce it. (Meir Manny Lehman, 1980)
Tackling Invoices Overview
When we started working on Invoices Overview, we knew two things: as the gateway into the Invoices section, the Overview had great potential to offer easy access to your invoice data, and that legacy code made it difficult to improve many of the things we wanted to. It was time to do some rewriting.
A Single Page Web App
In a traditional web app, and in some sections of Harvest, the server renders the page and sends the HTML to the user’s browser. It’s then the browser’s job to display the page to you, the user. Javascript is often used to improve the user interface of an application, for example adding an error message if you don’t fill in the necessary items on a form or to provide animation. But any time the information on the page needs to be changed, a full page reload is required, rendering the next page on the server and completely replacing the previous page.
Recently, however, it’s more common for Javascript to be used to make an asynchronous request to update a small part of the page, although changing all information on a page still requires a full page reload.
While web apps enhanced by JS perform most of the rendering on the server side, returning the HTML to the browser, single page web apps do most of their rendering on the client side, in the browser. After the page loads for the first time, any updates in the page are made using Javascript, triggering a re-render on the client side without any additional server requests.
The new Invoices Overview is effectively a stand-alone single page web app within the traditional web app of Harvest—it’s doing most of its rendering in the browser. When we went about removing the legacy code, we decided to rewrite this section using the Backbone.js framework. This framework, per its documentation, gives structure to web applications providing abstractions for models, collections, and views, all of which provide event handling.
We chose Backbone.js for two key reasons. Firstly, it provides a formal structure for building a single page application, and helps prevent the application from turning into an unorganised pile of callbacks tied to concrete DOM elements.
Secondly, it natively supports communication with a RESTful API. This kind of communication helps us separate our new code from the rest of Harvest, and makes server-side changes through a single API endpoint. This additional and almost compulsory structure, with the additional separation of the user interface and database access, will help limit the the speed at which our new code becomes legacy code.
You can find much more about Backbone.js as well comparisons between it and common alternatives online.
So How Does It Work?
When you first load Invoices Overview, the invoices matching your current filters are saved into the page itself in what we call a data-island. This data-island is essentially a block of JSON-encoded data. This is the only part of the UI (apart from the header and footer) that is rendered server side.
As soon as the page has loaded we create a Backbone Collection for all of the invoices held. You can think of this collection as a list of invoices that you can add to or remove from. Then in your browser, we render a row for each invoice in the collection, and configure it to re-render the table whenever the collection’s contents are changed.
Making use of the (as yet unreleased) second version of Harvest’s API, when the tabs or filters are changed, we send a RESTful request to the API to get all invoices that match the current filters. The API searches the database, returning any matching invoices as a JSON encoded array, which replaces the contents of the current Invoices Collection.
Bringing It All Together
Legacy code is just part of the natural evolution of an application. As Harvest has grown, so has our codebase and its complications. With careful design, and by making use of well understood scaffolding such as Backbone.js (MVC) and RESTful API’s, we can ensure that Harvest works its best even in the places that a user might never see. And for you, these under-the-hood improvements set the foundation so we can spend more time building the features we know you’ll love, rather than maintaining our existing code.