Extreme Decoupling FTW
After spending years writing fat, ugly classes, I suppose it is inevitable that the pendulum swings the other way and we head towards ‘wat? you got a class for that..?!?!?!?’ territory. For the moment though, the wins are huge and keep coming.
Dependency Injection is just a big word for explicitly passing in stuff that your object is going to depend on. Recently, I’ve been working on an app that automatically goes and make reservations using certain APIs. Testing it is always problematic because one has to constantly stub out the actual API call with something that doesn’t contractually oblige you to several thousand rupees of payments :-) Also, the frontend is being worked on by a separate team and I don’t want the dev/staging server to make actual bookings during development. With DI, dealing with situations like this is easy.
While building the app, I decided that nothing was going to talk to anything without talking to something else first. Here’s a rough sketch of the architecture.
The TripBooker
first calls out to a VendorSelector
service which provides a list of vendors based on any filtering rules that might apply. Then, each vendor is passed into a VendorBooker
service that does the booking with that vendor. It also calls out to the CredentialSelector
service to choose an appropriate set of credentials for the calls. What does the VendorBooker
look like?
Oooh….more indirection. The VendorBooker
creates objects of class FooCustomer
and FooBooking
and uses the class FooBookingResponse
to parse the results from the booking. Internally, FooBooking
calls the wrapper class to the API with the appropriate parameters. FooBooking
is the translator class that translates from a generic Booking
object into one that fits with Vendors::Foo
’s idea of what a booking is. The API wrapper class Vendors::Foo
has no idea about anything that just happened above. It merely accepts some arguments to the constructor and makes appropriate calls to the foo.com API.
So, what does four levels of indirection get you? Easy pluggability. As I mentioned, I’d had to stub out the calls to the foo API during testing and I was about to deploy the app on to a staging server so our frontend team could write code against it, but I didn’t want to actually make bookings. So, I decided that I would have a different setup for development, staging and testing. Basically, during testing, I make VendorSelector
return [:dummy]
as the vendor. Then the class DummyBooking
and DummyBookingResponse
returns a dummy booking without making any API calls.
By being explicit about the dependencies at every step of the way and making sure each class only does one thing, we’ve made life super easy when we need to introduce new behaviour in particular situations.
This is just one of the ways in which DI helps massively. Here’s a flowcharty diagram