Objects are like horses. The less they know about their chaotic surroundings, the easier it is to control them.
We don’t want our objects to be spooked when there’s a lot going on. So let’s build ignorance into our systems.
But how? We typically write mobile apps with these things at the center:
These frameworks bore into our apps like warts with deep roots. Web-centric, UI-centric, and persistence-centric knowledge spreads through our apps, making our code tightly coupled. And then we wonder why it’s hard to write unit tests!
Is there an alternative?
Uncle Bob Martin’s Clean Architecture offers a different way. Instead of being at the core, things like web communication and even the UI can become plug-ins. They live at the edges of the app. Use cases and business logic become the center of the app.[This post is part of the series TDD Sample App: The Complete Collection …So Far]
Let’s explore just one facet: communication with web services. And let’s only look at sending the requests; we won’t worry about handling responses for now. A naive approach for the Marvel Browser sample app looks like this:
The Marvel Comics API is out in the cloud. The Marvel Service class handles all communication with it.
The app calls Marvel Service, so there’s an arrow there. But why did I draw another arrow, from Marvel Service back to the rest of the app? Because in my experience, it’s typical for web service classes to know about the app’s model classes.
For example, we might represent Marvel comic characters with a Character class. If the Marvel Service knows about Characters, that’s a dependency. Thus, the arrow going from Marvel Service back to the rest of the app.
Can we get rid of that arrow?
Let’s not pass Characters, or any model object at the core of our app, to the Service. Let’s not have the Service poke around in the Character to extract any data it needs to make a request. Instead, the caller should give the Service what it needs, and no more. We can do this with a Request Model:
A Request Model encapsulates everything needed for a particular request. The caller creates it, and passes it to the Service. By making it a Value Object, the Request Model contains no references to the app’s model objects.
The Service is now ignorant. It knows what it’s told, and that’s all.
That’s a big step, but we’re not done. The “rest of the app” still has a dependency on the Marvel Service. This makes it hard to change anything about the way we fetch the data. In particular, it complicates unit testing. If only there were a way to reverse that arrow’s direction, but keep the Service ignorant about most of the app.
The classic way to invert a dependency is to extract a protocol. (If you’re an Android developer, when you see “protocol,” think “interface.”) We’ll call it the Marvel Gateway. In the following diagram, it’s marked with <P> to show that it’s a protocol:
The Marvel Service will (eventually) implement the Marvel Gateway protocol. The protocol acts as a contract. Thanks to Dependency Inversion, the rest of the app can now be ignorant. It knows about the contract, but not about any class that satisfies that contract.
We just derived our way to the heart of Clean Architecture! Anything that implements the protocol can be plugged in. The Marvel Gateway serves as a Boundary. Everything to the left of the dotted lines can be sliced off and replaced without affecting the core of our app. This makes it easy to replace the Marvel Service with whatever we want to use for unit tests.
Separating things in this way may look like more work. But ignorance is bliss! Loose coupling brings huge benefits at a low cost.
Next time, I’ll show you how AppCode can help us quickly create a Value Object to serve as our Request Model.
Have you felt the pain of objects that know too much? How has tight coupling slowed you down? Leave a comment below.
Jon is a consultant on Clean Code for iOS, focusing on Test Driven Development, unit testing, refactoring, and design. He's been practicing TDD since 2001. You can learn more about his background, or see what services he can bring to your organization.
Please log in again. The login page will open in a new window. After logging in you can close it and return to this page.