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 give our horses blinders, by building ignorance and isolation 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?
Robert 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 can 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 an Objective-C 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.
Programming was fun when I was a kid. But working in Silicon Valley, I saw poor code lead to fear, with real human costs. Looking for ways to make my life better, I learned about Design Patterns, Refactoring, and Test-Driven Development (TDD). Programming became fun again! I've now been doing TDD in Apple environments for 18 years. I'm committed to software crafting as a discipline, hoping we can all reach greater effectiveness and joy.
Please log in again. The login page will open in a new tab. After logging in you can close it and return to this page.