Code that's easier to understand, maintain, and extend—that's the promise of Object-Oriented Programming. But the reality for many iOS developers is that our objects are bloated. They know too much and do too much. …What if our code has hidden objects, waiting to be found?
Each hidden object could provide a new abstraction, a new tool. They could make the code more manageable. Is there a way to discover these hidden objects? Domain-Driven Design (DDD) provides a way.
The Book
Disclosure: The book links below are affiliate links. If you buy anything, I earn a commission, at no extra cost to you.
Eric Evans' Domain-Driven Design is a book with many facets. One of my big takeaways is that our code often contains concepts that are hidden. The book's challenge to me is to recognize implicit concepts and make them explicit.
I stumbled upon this in the TDD sample app. While applying a principle from the book to the Fetch Characters Marvel Service, I uncovered a useful class.
Canceling a Data Task
In an earlier article, we TDD'd the requirement that the URLSessionDataTask receive a resume message. Right now, the code (including a spike test) looks like this:
func fetchCharacters(requestModel: FetchCharactersRequestModel) {
guard let url = makeURL(requestModel: requestModel) else {
return
}
let dataTask = session.dataTask(with: url) { data, response, error in
print("error: \(String(describing: error))")
print("response: \(String(describing: response))")
let str = String(data: data ?? Data(), encoding: .utf8)
print("data: \(String(describing: str))")
}
dataTask.resume()
}
But one of the user stories of the Marvel Browser is “User navigating away cancels current request.” So we need to hold on to this data task so we can send it a cancel message.
Easy. Put it in a property, right? Then the Service will have two methods, fetchCharacters and cancel.
I’ve done this before in production code, and started down this same path. But as I took another look, something started to bother me.
Services According to Domain-Driven Design
In the Domain-Driven Design book, Eric Evans writes the following about Services.
A good Service has three characteristics.
- The operation relates to a domain concept that is not a natural part of an Entity or Value Object.
- The interface is defined in terms of other elements of the domain model.
- The operation is stateless.
Let’s see. Number one, check. Also number two, since fetchCharacters takes a request model Value Object.
Number three. Stateless. This would no longer be true if I tracked the data task in a property. But I need this stateful information. If the Service doesn’t keep track of the state, what does?
Then it hit me. I need a new object.
- I need state.
- But the state shouldn’t be intrinsic to the Service.
- So the state should be extrinsic, passed into the Service.
Let’s Draw a Sequence Diagram
At this point, I started to get confused about what the domain model objects were, and how they interacted. I decided to make a diagram.
In school, they taught me to use CRC cards to help flesh out Object-Oriented Designs. But from reading Practical Object-Oriented Design in Ruby by Sandi Metz I learned about Sequence Diagrams, which I see as a rigorous descendant of CRC cards.
With CRC cards, you simulate message-sending by talking. With Sequence Diagrams, you draw the communication. So here we go:
My simple little Service has a surprising number of collaborators! At first glance, it may look overcomplicated. But the diagram helped me understand the Fetch Characters request. Every object plays an important role and is there for a reason.
NetworkRequest is the newly uncovered Entity. On any system that uses Foundation, it’s a wrapper for a URLSessionTask. But the idea is portable. We could use this term across client platforms of all types. This would help us create a Ubiquitous Language that we share across teams. And that would bring a host of benefits.
Benefits of Stateless Services
Why is it important to keep a Service stateless? Consider what would have happened if I’d kept the data task as a property.
I could have one Fetch Character request, at most.
Now I don’t know if I’ll ever need more than one active request. But there’s no such restriction on the actual service. Modeling it that way would have added a limitation that doesn’t reflect reality.
People seem to have given up on the idea that OOP creates reusable objects. But one thing preventing object reuse is creating objects that know too much. A stateless Service can be used in a greater variety of situations.
Domain-Driven Design Leads Us to Better Code
We all have hidden objects in our code, waiting to be found.
Just by following a single part of Domain-Driven Design, I uncovered a hidden Entity. Making it extrinsic improved my Service. Now I have new objects with tighter focus, which makes them both more reusable.
What other improvements lie ahead, waiting to be refactored? I’m betting the patterns and mindset of Domain-Driven Design will lead me to better models.
What about you? Have you made any Domain-Driven Design-inspired changes in your iOS code? Even if you can’t share the details, let us know in the comments below.