Network Layers in Swift

How to avoid the Big-Fat-Singleton™ often called NetworkManager and live happily

A new updated version of this article can be found at this link.

As developers there are tons of stuff we write, rewrite and refactor each time we start a new project.
Sometimes there are valid reasons to grapple in these sort of activities (think about protocol oriented programming in Swift vs Obj-C), more often we consciously ignore the objective loss of our time, by continuing having fun doing some we think it’s clean code (…until we starts the next project).

from xkcd

One of these fun activities regards the Network Layer of a software.
99.99% of all apps talk to the internet at some point so this is a crucial point of any software.
During the years I’ve seen and write lots of different network layers; with the exception of some rare cases most of them was implemented as an huge singleton class called NetworkManager (or something like that) where there is the entire knowledge of the program about network stuff.

99.99% of all apps talk to the internet at some point so this is a crucial point of any software.

In my previous work, a big enteprise program — the goal was to finish an the incomplete code and put it in production: I’ve spent more than week moving away from this huge monster (I like to call it FNM: FatNetworkManager) trying to make it “smaller”; the scope was to support at the same time two different user sessions (one for the app itself and another for the watch companion app), so a single entity is not only a design issue but a real problem.

Even if often this singleton approach works without problems, it poorly fails in one of the principles of a good software design: Single Responsibility.

In this article I’ll provide a possible solution to this issue by separating each step of the network workflow.
But, first of all, just few words about SRP.

S in SOLID: Single Responsibility

Regardless of what we consider to be great code, it always requires one simple quality: the code must be maintainable. Any code which is not maintainable and cannot adapt to changing requirements with relative ease is code just waiting to become unmanageable, then obsolete.

SRP is part of SOLID principles (single responsibility, open-closed, liskov substitution, interface segregation, dependency inversion). While we have not enough time to show each one, the first letter, S, represents Single Responsibility Principle and its importance cannot be overstated: in in any code that is poorly written, you can always find a class that has more than one responsibility.

in in any code that is poorly written, you can always find a class that has more than one responsibility

My FNM violates SPR (…practically everywhere): it know everything about the connection (base url, paths of each request), how each request is built and executed, how to parse response data and finally it also manage the internal user session and some user’s profile data (a nightmare!). Everything related to a network connection lives inside this big fat singleton class.
Moreover, while I don’t say singletons are necessarily bad, they also can’t be injected as dependencies, and can’t be easily mocked for testing purposes.

We can do better… how?

As we seen the vast majority of network layers architectures are something that looks like this:

In order to separate responsibility we need to disrupt the monster by creating several states, each which implement a single step of the workflow.
This allows us to separate concerns and create a workflow where we can also inject mocked up data to perform our tests.

We can separate a network process into the following tasks:

  • 1. Build the Request: this object is responsible to create a request along with the common parameters like HTTP Method, Headers, Body and obviously the Path of the request itself.
  • 2. Execute the Request: this part is responsible to execute the request to the underlying layer — usually a network implemented viaURLSession, Alamofire or whatever you want (“usually” because this architecture allows us to even create fake layers which returns mocked data)
  • 3. Parse the Response: in this step we read the raw output and provide an initial representation of the data (or get the error received). Usually this is the point where we check for correctness of the output, the format itself and check for any error which is not available directly inside the HTTP status code (ie. an “error_message” key with the detailed description of the error).
  • 4. Perform Operations: this is the higher level of abstraction. In this class you will perform a single logic operation and transform raw data received from underlying layer to application’s usable entities (ie. from a raw JSON to “User” entity).

Build the Request

The request represent one and only one network operation. Think about it as the atomic unit of our network layer. The role of a Request is to configure the necessary parameters of an HTTP request including at least:

  • The path of the request (ie. /api/v1/loginUser)
  • The HTTP Method used (ie. POST, PUT or whatever)
  • headers to append (ie. Cookies, Authorization or whatever)
  • body of the request (may be a JSON data, XML, a multipart data and so on…)

This is the perfect opportunity to use two highlighted features of Swift: Protocols and Type-Safety.

Our Request object will be a simple protocol which defines the most common properties related to a network request:

Now we are free to implement it as we prefer: for example we may prefer a traditional OOP approach by making a RawRequest class and subclass it for each different request. While this simple approach, I would to avoid to create classes over classes when not needed.
A simpler approach involve the use of Swift’s enumto provide a compile-time check of the params for each call. Moreover it offers the ability to arrange and group requests by context (ie. in a Social app you may want to have a set for user related calls, another to manage posts and so on).
The code above implement a very simple example of the concept:

Execute the Request

As we said, this step is responsible to execute to dispatch a single Request to the underlying network layer (it may be a simple URLSessio or any other lib like Alamofire).
As we done for Request even Dispatcher should be implemented as a protocol; this allows us to replace it with a fake dispatcher which simply return mocked data for our unit tests.

At this time you need to choose your best swiss army knife to manage asynchronous operations (callbacks/completion blocks, promises, rx etc.); here I’ll use my library Hydra which is an implementation of Promises & Async/Await.
Just for brevity I’ve also used URLSession to implement a real use-case for a Dispatcher you can see below:

Our real network Dispatcher maybe implemented like this (please note: we don’t provide here a full featured dispatcher, just a look about how it should be implemented):

Parse the Response

Dispatcher will return a Response object.
In our case Response in an enum which contains the possible returning values of the operation: an error, a plain Data or a JSON structure (is up to you to define what kind of results you are interested in, it’s just an example).
It’s responsible of our Response object to get the result of a request, doing the stuff to validate and parse the data inside and finally return the output value.

This is a simple implementation which take care of the error and status code; your implementation maybe more complex.

As you can see it’s pretty easy to make a new Dispatcher implementation which is able to return mocked data; nothing will change outside the Dispatcher itself and we have a very maintainable code.

Perform Operations

The last level of abstraction is represented by the Operation. Is up to the Operation to execute a Request and transform received Response in something the app can understand. As you can imagine this is the outer level of our Network Layer and this is what View Controllers and other objects of the app will call when they need something from the network.

Operation is defined as a protocol:

It defines:

  • request property which should return the Request associated with the operation
  • a func execute(in:) which is responsible to execute defined Request in passed Dispatcher instance.

Your implementation for login, we call it Login Operation, may look as below:

Conclusion

As you have seen it’s easy to disrupt our big singleton to get a more versatile and testable networking layer where each part is indipendent and respect the SR principle.

Let’s look a summary diagram:

While I know this not the final word about the topic I’ll be more than happy to look at other solutions or something to make the one described below even better.

I’m waiting for your contributon, let’s talk ;-)

Another article about this topic was wrote by Fernando Martín Ortiz: “Isolating tasks in Swift, or how to create a testable networking layer