Uncategorized

Applying SOLID Principles and Functional Patterns to iOS Networking

functional-swift

At Habitica, Phillip Thelen, Keith Holliday, and I have been talking about removing RestKit and replacing it with several other smaller libraries instead (like Realm, Alamofire, and SwiftyJSON, to name a few). That, in combination with some conversations I’ve had with Brandon Williams about functional programming, have lead me to create a simple scaffolding for building API consumers in iOS apps.

The Problem

I used to write most of my APIs something like this:

class ServerApi {
  func login(...) {
    // setup and then make call using Alamofire or something
  }

  func fetchPosts(...) {
    // setup and then make call using Alamofire or something
  }

  func fetchCommentsForPost(...) {
    // setup and then make call using Alamofire or something
  }
.
.
.
}

This is what I tend to call a ‘horizontal’ approach. That is, every network call is a sibling of every other one within the same class. Let’s take a look at some ways this architecture might be suboptimal:

  1. This way of doing things tacitly implies that these API calls belong together; the logical unit is the entire API spec. But consumers of this class probably don’t need access to every call in order to do their job; how many login pages do you know of that also need to be able to tweet out a link to a user’s post? Most API calls are pretty independent (in that changing one probably won’t affect another), so why keep them all in the same place?
  2. The ServerApi class changes every time an API call is added or removed. Depending on the project, it may also change when one of the calls switches from a PUT to a PATCH, or when the inputs or outputs for an API call change. This is a blatant violation of the Single Responsibility Principle.
  3. These files quickly get very large. I’ve often found myself scrolling through ServerApi classes that are thousands (!!!) of lines long looking for one particular thing.
  4. Reusing networking code between projects can be frustrating. Usually you end up just copying over the whole class and then trying to comb through and delete the calls you don’t need. When you have to go through and strategically delete specific functions from 5000 lines of code, it’s easy to make mistakes one way or another.
  5. Autocomplete can be a nightmare.
  6. It lacks a clarity of purpose, and can be hard to navigate.

Introducing: FunkyNetwork

SOLID Principles

FunkyNetwork is based around the idea that the core unit for networking should be the API call, and not the whole API. As a result, each call is represented as its own class. I call this a vertical approach, because each api call sits in a class that inherits attributes from its super classes. This structure comes with a bunch of advantages:

  1. Reusing code between projects is easy. If you’re like me, you’ve written a bunch of API calls in your time, and with this architecture, it’s simple to leverage that previous work. Bring your LoginNetworkCall class with you to a new project and change only what you need to (or, often, nothing at all!).
  2. When you open up a class, you know exactly what it’s trying to do.
  3. You can use inheritance to easily build relationships between families of API calls (e.g. authenticated calls vs. unauthenticated calls).
  4. Classes generally remain small.
  5. Consumers use only what they need.
  6. Autocomplete for a class is dead simple.
  7. No other calls need change when one call’s spec changes, satisfying SRP.

FunkyNetwork also has a few other nice things that bear mentioning.

Functional Patterns

FunkyNetwork uses functional patterns to keep things simple. This makes it easy to chain calls, link them with other events, increase test coverage, and minimize side effects.

Extensibility

The NetworkCall class handles constructing and executing a request from a server configuration and endpoint specific values. It makes no assumptions about what kind of data gets returned, so, if you need, you can subclass it to handle XML APIs or even website scraping. StubbableNetworkCall adds a lightweight layer on top of that which allows you to easily stub responses for any of its descendants. Finally, JsonNetworkCall handles json parsing for you, so you can feed the result straight into the deserializer of your choice!

Smart Defaults

FunkyNetwork sets things up for you, but allows you to change it. Json headers? Check. Threading? Handled. Stubbing responses? Only when you need it.

Testability

Because this architecture keeps code siloed, it’s already very testable. Because it’s functional, test cases are very easy to write. But what really puts it over the top is stubbing.

FunkyNetwork lets you pass in a simple object containing a response code, fixture file name, plus any headers you want, and uses OHHTTPStubs and existing methods to stub the API call. Why is this great? First, it means you can write expressive tests with less code which cover more edge cases. But second, it makes it easy to run any scenario with or without internet or access to the server. For teams that fly often or work remotely (like mine), this can really increase productivity.

What do you think?

I’m really curious what other devs think of this architecture in general, and my implementation in particular. Please let me know either by commenting or tweeting @elliot_schrock!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s