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.
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
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
[:dummy] as the vendor. Then the class
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