Backbone Repository Pattern
Backbone.js does a great job of providing structure to complex front-end applications, but oftentimes we find we need to do more to further abstract domain logic so it does not depend on the UI layer or the backend. In this article, we talk about how to apply the repository pattern to encapsulate interactions with the backend.
Stubbing Backbone's fetch()
When we need to test a part of our application that fetches data from a server, we will want to avoid making that request so as not to slow down our tests; and because that endpoint might not be implemented yet. Besides, we are focusing on unit tests, and they should only test individual indpendent units. If our test makes an external request, then it stops being a unit test and starts to venture into the realm of integration or functional tests.
Let's consider the following code:
loadingThis method calculates the total rate based on how many hours worked and the hourly rate. The hours is fetched via
an XHR request. Testing this can be a little challenging, but not impossible. We can use sinon to stub
the collection's fetch
function:
While this will work, there are a couple things that should bother us. First, the RateCalculator is tightly coupled to TimeEntries collection; but most importantly, the RateCalculator is doing too much. It is retrieving the data AND calculating the rate. We can address this by 'injecting' the TimeEntries into the calculate method:
loadingThis looks much simpler. Even our test looks better.
loadingIf we step back for a moment, we can see that we might stumble on the same problem again. Consider testing some part of our application that uses RateCalculator. We might have some code similar to this:
loadingWhen we try to test this we might have a situation similar to our first test:
loadingIf we need to use RateCalculator in other parts of our application, we will duplicate this in many places and lose track of it. Why is this bad again? Because it couples our application to Backbone and creates some unmaintainable code. It will be difficult to change this if we ever decide that maybe Backbone is not the right choice, or if our TimeEntries is a more complex collection
We can solve this by injecting the collection, like we did in the first scenario, and it is the route I would take; but we will reach a point where passing the collection like a hot potato will not save us, and we will have to deal with this situation head on.
And what about situations where we would want to trigger an action when a new set of collections is fetched? Or maybe we have a real-time dashboard that needs to get updated on near real time every time some event happens somewhere else (even on a remote computer or server)? How do we handle error messages for all xhr requests?
What about scenarios where the xhr response does not fit nicely with our collections and entities? For example, suppose we need to request data about 'things to do' and the structure of that data returned is something like:
loadingRepository Pattern
If we step back for a bit, and imagine our application as a vulnerable kernel that needs to be shielded from any outside infection, then we can see how allowing direct calls to Backbone.Collection#fetch() deep in our application exposes it to external factors. Changes to the Backbone API can require massive changes deep in our application's kernel.
We can take some inspiration from the 'Hexagonal Architecture' to help us come up with a 'cleaner' solution. In order to access any external system (like xhr requests), we need to add an interface for it.
We might want to start by defining the interface:
loadingOur repository makes provisions for cases where the url does not fit nicely with Backbone's expectations. Backbone expects all urls to be the same and the only thing that changes is the http verb. There might be cases where that is not possible.
Fetch API
Now we can specify how we will fetch data from the server:
loadingThis test only needs to ensure that an xhr request is made with jquery. It is testing the repository's internal implementation, which consequently makes it very fragile. However, an object like this lives in the periphery of an application and for them these kinds of tests are common. We will later see how it makes our domain logic much cleaner. In the meantime, we can make this test pass:
loadingOur fetch function could have also used a backbone collection. The clients using the repository won't know nor need to know how it is implemented. But this still looks a little wonky. We haven't gotten anything benefits from it... yet.
Now lets consider a scenario where we'd like to promisify
our interface:
This is a contrived example, but bear with me for a while. We return an array of 5 items and we need to tell
mocha that this is an async test. We do this by passing done
as an argument to the test and then invoking
done()
to notify mocha that the async test has finished. We can then make assertions on the value of the
promise. Our specs now start to look more like production code. The only inconvenience at the moment is
creating some fake data. But we can live with that. Much better than coupling all our tests (and production
code to jquery). And we don't need to change anything in our code to make this pass because $.ajax() returns
a promise.
This is okay for very simple cases, but what if we need to return something with behavior (an instance of an object):
loadingImplementing this wouldn't be so difficult:
loadingWe introduced the Q library for dealing with promises (a matter of personal preference). Our clients don't need to know what library is being used, they just need to know its a Promises/A+ compatible library. So we should be able to substitute the Q library for Bluebird if we need to and any client of this class won't need to change.
Notifications
We can now even start introducing custom events that our application can listen to. An event for fetching data, or for creating a new entity, deleting one or updating one; can be triggered for any part of our application to listen to:
loading loadingNow any part of our application can listen directly for any events triggered by the repository and execute a function accordingly. This enables us to update different parts of our UI independent of each other. We can also test those parts our application pretty easily by just triggering the event without needing to wire up the entire repository:
loadingIn some cases, we might only need to create some fake time entries, but that is to be expected.
Handling Network Failures
This strategy can also enable us to handle network errors in a generic manner. If our app is a single page app that is expected to be kept running on the browser for long periods of time (even days or weeks), whenever the network is down or an xhr request failed for some other reason, we should be able to notify the user. Although we can do this with Backbone by overriding sync, it feels like a hack and I am always wary of overriding third party libraries.
To handle network failures for all xhr requests we would either need to implement that code in every repository, or use inheritance (or mixins also). We'll use inheritance in this case since it is something only repositories will need to do:
loadingWe can handle errors with promises by passing in a function as an error handler to the then() method. We could also have an application event bus which we can notify of any errors from within this error handler:
loadingConclusion
We can use the Repository Pattern to decouple our application from any framework specific code that can make our application hard to test. As a secondary benefit, our objects become more focused by having only one primary reason to change if all they do is just concern themselves with fetching and sending data. Promises can also be used to make our code more readable when dealing with async operations. They also help us deal with error handling in an elegant manner. Finally, the use of a messaging pattern and an event bus helps us keep the components of our application decoupled by enabling us to send messages to remote objects without having to hold a direct reference to them. We can then test these objects in isolation without having to wire up a large object graph.