Nearly every app interacts with a server on the web, and often more than one. You’d think that we all take great care of our network layer and make sure it evolves into something robust and reliable.
Hah. Not in my experience!
Despite network layers playing a critical role our apps, they often become big and difficult to maintain. In fact, they grow in ways we don’t expect resulting, in files that can be thousands of lines long.
With that in mind, let’s get back to basics and create a network layer from scratch, using the “Don’t Repeat Yourself” principle to help us along. We’ll create a networking class that interacts with SWAPI, an open API for Star Wars resources 🤓
In this article, we’ll look at:
- How to build a network layer out of small, testable parts.
- How we can apply the DRY principle to inform our refactoring.
- How we can conceptualize our code in terms of domains, and how to build bridges between them.
Should You Use a Networking Library? No.
When building a new network layer from scratch, the first question that often pops into our minds is if we should use a 3rd party library or not.
You’ll notice that in this article, we use Apple’s
URLSession. Not Alamofire. Not RestKit.
In the vast majority of cases, you don’t need a separate networking library.
URLSession is so robust and full-featured that the overhead in maintaining a separate 3rd party library is simply not worth it. I’m sure there are situations where it can be really useful, but it’s been years since I’ve encountered one.
Calling an API Using URLSession.
Alright, let’s create our first request!
SWAPI exposes an endpoint that returns a list of Star Wars characters. The URL for this endpoint is
https://swapi.co/api/people/ and returns a JSON array of characters like Luke Skywalker, C-3P0, etc. Let’s hop right in and see what that looks like.
For many of us, this code is second nature.
(And if it’s not the case for you yet, don’t worry. It will be soon 😉)
Let’s break down what’s going on here:
- We’re defining a method called
getPeoplethat takes a completion handler
- On line 2, we prepare the
- On the line right after, we prepare a
URLRequestusing that URL.
- Then, we prepare a
dataTaskto fetch the JSON data at that URL.
- Finally, we start the data task by calling
For now, we’ve put everything in the same method. We’ll leave it there for now, and if we follow DRY, we should see opportunities to clean things up as we move along.
Creating a Second Request and Cleaning Up
Alright, let’s create a second request. This time, we’ll fetch Starships from the
GET Starships endpoint.
Our starships endpoint look a lot like our people endpoint, so let’s factor out the common part into its own method:
That’s already better. We’ve moved the data task creation to its own method, reducing the amount of duplication. Well done, team!
Thinking About the Bigger Picture
Both of these endpoints support a
search parameter. With this parameter, we can filter the names of the entities that are returned.
We could add a search parameter to each of the methods for these requests, but that’s more duplication. Let’s rethink the problem instead. What abstraction are we missing?
To answer this, let’s think about what exactly a request is? It’s not just a URL, right? It’s also an HTTP method, headers and parameters. What’s more, different requests can have similar characteristics. One of those characteristics could be accepting a
search parameter. This is the type of behaviour we want to capture.
Let’s see if we can model this in a way that helps us. Here’s a protocol called
RequestTemplate represents all the different interesting things a request could need. Now we can redefine our earlier endpoints in terms of
Wow, isn’t that beautiful? We have a concise, focused struct that represents a request. More importantly, we also have a clear way to add new requests. This will help us avoid bloat as our project grows larger.
But we still have some duplication here, don’t we? On one hand we have these request objects that represent requests that we want to perform. But there’s also
URLRequest, which, essentially, represents the same thing. Does it make sense to have both? If so, how do we reconcile the two?
Building Domains and Bridges
I think it’s interesting to think about problems like these in terms of domains. On one side, we have this little cluster of classes we’re building in order to operate on the Star Wars API. Maybe this is something we could pull out into its own module, and open source it.
On the other side, we have this generic framework in
Foundation that provides
URLSession and friends that can operate on any API.
How do we bridge the two? How do we go from our domain of SWAPI, to the generic generic domain of
In this case, we need to build a bridge. Some interesting questions to ask ourselves are:
- What’s the common currency between these two domains?
- What’s the result of one domain that can be handed off to the other domain?
In our example, I’d propose that the “common currency” is the notion of a request, since it exists in both worlds. Our domain has
RequestTemplate, and Foundation has
URLRequest. Therefore, we need to bridge one to the other. Let’s create an extension that provides just that.
Back to Search
Now that we’ve rethought this problem, we can go ahead and get back to search. Let’s define a protocol which encapsulates exactly what we want to add.
If we were to implement this on our requests, the implementation would look the same. Let’s take a shortcut here and define a protocol extension instead. This will allow us to capture the semantics of what it means to be a searchable request.
And now, to add this functionality to
GetStarshipsRequest, we simply need to extend them.
Our grand finale
Now that we’ve implemented search, how do our method signatures change? Does
getStarships still make sense?
The answer, like many things in software development, is “it depends”. Here are a few ways to look at the problem.
If your goal is to encapsulate as much information as possible inside your
SWAPI class, then I suggest to continue as things are now. Your new method signatures would look a bit like this:
However, if you want maximum flexibility, you might want to pull your API boundary away from the user. Concretely, this means having a method that accepts any
RequestTemplate. In doing this, you simplify the code inside
SWAPI, at the cost of pushing more of the burden onto the users of this class. You can imagine the new function signature looking a bit like this below:
Which of these options is best? It’s hard to say! I’d prefer the second approach in most apps. However, if I was creating an open source component, I’d opt for the simplicity of the first.
In this article, we saw how we could apply the DRY principle to create some meaningful feedback about when we needed to refactor our code. We saw how to decompose an API layer into smaller parts, and we saw how we can recombine those parts to create a layer that’s easy to use.
I’ve prepared a little playground in which you can you see all of the steps mentioned in this article. Join the newsletter and it’ll be sent right to ya!