A Pragmatic Functional Reactive Architecture in Swift

Srđan Rašić
14 min readFeb 19, 2017

--

Over the past year I have been evolving an app architecture where functional reactive paradigm is an equal-class citizen. The architecture is a combination of functional reactive and object oriented paradigms with the clear line of separation between the two. It might be a bit unconventional due to the nature of reactive paradigm, but it has proven to be very simple to use and extend with very little overhead compared to the alternatives like MVVM.

Skip to the last section to find additional arguments on why I am doing this if you need more introduction.

In the examples I will be using Bond and ReactiveKit frameworks, but the same concepts should be applicable with other frameworks. I feel that this architecture has a lot of potential and hope that somebody will explore it with RxSwift or ReactiveCocoa too.

Business vs. Application Logic

We will need these two terms for the discussion that follows, but they are often vaguely defined so let me define what I mean by them before continuing.

Be it a to-do list, a cooking social network or a flight booking app, your app solves some kind of a problem that provides a value to the user. Code that expresses logic of how that problem is solved is the code that I refer to as business logic. It knows about your backend API if you have one and how to solve particular subproblems. It knows how to authenticate the user against the backend given the credentials. It knows how to fetch a list of latest recipes given various filters and sorting options. It tracks the state of to-do tasks and synchronises them with the database. Business logic, like networking and other lower layers, should be platform independent. It should be coded in a way that it could be used on mobile, on TV or on desktop. Even if the app is targeting only one platform, thinking in terms of cross-platform support is beneficial for your code.

On the other hand, application logic is specific to a platform. It builds upon business logic to provide the complete solution for the platform. Application logic would be the code that creates and presents screens, that loads right data into right views and that acts on user input and actions. Application logic is a bridge between business logic and platform-specific technologies. On iOS it knows about UIViewController and how to present it. On macOS it knows about NSViewController, NSWindow and others.

Overview

The architecture I am presenting is focused around three components: View, Interactor and Scene. There will usually be other components like entities, networking services and transitions, but the former three are most important.

A View is just a UIViewController on iOS or NSViewController on macOS. Its only concern is providing the UI. It is similar to the View in MVVM or in VIPER in a way that it implements no application or business logic, but with one significant difference. In this architecture View is a standalone component that does not have any dependencies on other components. It means that it does not hold a reference neither to the Interactor, the Scene nor any other component.

An Interactor represents business logic. It is just a bunch of signals and observers. Its dependencies might be HTTP clients and other services. It is designed to be easily testable. Interactor is not our usual object, rather a purely functional reactive component. The way it is implemented might be a bit strange at first. Usually it has just an initializer and a number of properties that are either signals or observers. It can have convenience methods, but those are always implemented as pure functions.

Finally, a Scene is a component whose main concern is to wire up the other two components. It binds signals from the Interactor to the View and signals from the View to the Interactor. Scene is actually just a function that creates a View for an Interactor and sets everything up.

The architecture makes advantage of reactive paradigm and by doing that makes dependency management trivial. There are no reference cycles nor abstractions behind protocols as neither the View nor the Interactor depend on each other. They are both standalone components and can be used one without the other. Both the Interactor and the View are easy to test because of little or no dependencies. Scene is a bit trickier because it is a function that creates a View for an Interactor. To test the Scene one would have to mock the Interactor and the View, but it is questionable whether testing the Scene is justifiable because it consists mostly of wirings.

There is a demo project that makes use of this architecture. It is a work in progress, but you can already check it out. It is a Gitter client app that can be found on GitHub repo ReactiveGitter.

Let us dive into the details.

Interactor

Business logic almost exclusively operates on data. It fetches data from the backend or loads it from the database, processes it and then produces output useable to the application logic. It also goes other way round. A data or an action is produced by the application logic (e.g. user taps a button), business logic processes that data or action and sends the results to the backend.

Such problem is a perfect fit for functional reactive paradigm because it is all about data flows. Data flows are usually called sequences or streams and are represented by types like Signal in ReactiveKit and ReactiveSwift or Observable in Rx world. Signals can then be transformed into other kinds of signals, they can be manipulated in various ways and multiple signals can be combined or merged into a single signal. Such operations on signals are done by applying reactive operators on them. Reactive operator is just a fancy name for a method on the signal type.

Our business logic will live in Interactors. An Interactor represents the business logic of a singe use case. The name is borrowed from VIPER architecture because it shares some responsibilities with the Interactor from VIPER. We might have an Interactor for authentication, recent recipes list or booking payment. As opposed to VIPER Interactor, our Interactor will be implemented purely in functional reactive fashion. To achieve that, we will implement all the logic by means of signals and their transformations.

An Interactor will usually have dependencies around which the logic is based, for example an HTTP client. All dependencies should be passed in the initializer. Please avoid singletons. Additionally, an Interactor will have inputs and outputs. Inputs will receive data or actions from the application layer and will usually represent user input or user actions. Outputs will be data or events produced by the Interactor, often results of input processing.

Outputs will be represented by signals. Inputs will be represented by observers.

Let us start with an example of how a basic authentication Interactor might look like.

Authentication Interactor

This Interactor handles logic of a typical username and password login screen. It has an observer of credentials on the input and a token signal on the output. We use publish subject as the observer. Whenever we receive a username and a password we make an authentication request to get the token. That is our output.

Notice that we are using safe signals and safe observers here. SafeSignal<T> is just a convenience typealias for Signal<T, NoError> provided by ReactiveKit. Reason behind safe signals and observers is that we want to handle signal errors in the logic component. We do not want the application layer handle them. We will return to errors a bit later, for now it is important to know only that Interactors should not expose failable signals in their interface.

Let us check out another example. Say a screen that shows a list of recipes in certain category that can be sorted by a sort descriptor.

RecipeList Interactor

A pretty similar structure to the Authentication Interactor. On the input we have an observer of sort descriptors. Whenever user selects new sort descriptor we receive an event and flat map it to a list of recipes sorted by the newly selected descriptor. We make use of shareReplay operator so we avoid making new request whenever new sort descriptor is selected.

Notice how we pass the category in the initializer. That is how we pass the entity that the Interactor works on.

Real life Interactors will often be more complex, but they will always boil down to the structure shown in those simple examples. There will be signal and observer properties that represent outputs and inputs and there will be an initializer that establishes relationships between inputs and outputs. Initializer might get long, but it will be declarative and simple to reason about.

All Interactors of the app should be put into a framework, i.e. a separate target. I usually call it Interactors. There are many benefits of splitting your code into multiples frameworks that could be a blog post of its own. One nice thing worth mentioning is that you get a free namespace. It means that you can call your Interactor RecipeList instead of RecipeListInteractor.

View

On iOS, the View will be represented by a UIViewController subclass and a number of UIView subclasses used to make up the user interface. View could be defined in code or it might make use of XIBs or Storyboards. I must note, however, that segues do not play nicely with this architecture. All presentation should be handled in the code. There might be ways to make them work, but I have not even tried because I do not find Storyboards or XIBs useful.

View should be completely agnostic to the application or business logic. Its main concern is user interface. It knows about layouts, theming and animations. It knows nothing about Interactors, Entities or API. You should be able to pull out View code from one app to another and it should compile without issues.

First a little trick. I find it convenient to put all my view controllers into a separate namespace. You can use an empty enum for that. The advantage is that you can drop the ViewController suffix from your view controller names, that you can use autocompletion to give you the list of all your view controllers and also that you can use the same name for both the Interactor and the view controller.

public enum ViewController {}

Then just define your view controllers in a ViewController extension. Following would be an example of authentication view controller we wrote the Interactor for earlier.

Authentication View Controller

Nothing fancy here. Just a UIViewController subclass with a number of subviews. That is how the view controller should look like. It could have addition code that produces cool effects like parallax or spring dynamics, but no logic.

As for our recipe list view controller, it might look something like:

RecipeList View Controller

I know it is trivial, but it needs be clear that a view controller must not reason about logic.

Just like the Interactors, all Views should live in a separate target, i.e. a framework. It will give you a namespace and a clear separation of concerns. I usually call this framework Views. In Views you should never import Interactors, ReactiveKit, API and other non-view related frameworks.

Ok, but who handles the login button action and who calls the login on the Interactor? Let me tell you about the Scene.

Scene

This is probably the most unusual component of this architecture. Here is the blueprint for it.

Scene

Yup, just a method implemented as an extension on the Interactor. The output will be a view controller and optionally a signal or two representing the outcome of the view controller when there is one. Let us see how the Scene for the Authentication interactor might look like.

Authentication Scene

When a function has multiple return arguments in a tuple, it is convenient to name that tuple. I find it natural to call this tuple Scene.

Main responsibility of the Scene is to create and configure a view controller and that is exactly what our Scene is doing. First we instantiate the Authentication view controller, then we make a credentials signal by combining the text signals of username and password text fields. Finally, we feed the latest value of that signal into the Interactor whenever a button is tapped. In other words, we observe credentials from the view controller with the credentials observer in the Interactor. The createScene method then returns the view controller and a signal that will eventually send the token.

It is the responsibility of whomever presented the authentication scene to handle its dismissal and deal with the token.

Let us also see the Scene for our RecipeList Interactor.

RecipeList Scene

This scene does not have multiple return arguments so we can just return the view controller. Again, we start by creating the view controller and observing the signals from the view controller with the observers in the Interactor. Next, we bind the signals from the Interactor to the View. In this case, we bind a signal of recipes to the table view. This example shows the syntax from Bond framework.

Finally, we see an example of how to present another scene, in this case a recipe details screen whenever a recipe cell is tapped. First we create a signal representing the recipe at selected index path and then bind that signal using Bond’s inline bindings to our view controller. We bind it to the view controller because the view controller is the object responsible for presenting another view controllers and because we want the binding to live as long as the view controller is alive. In the binding, we just create RecipeDetails Interactor, create a scene from it and push it on the stack. I do not have an example of how RecipeDetails components look like, but you should already have an idea.

Scenes present another Scenes. That’s their secondary responsibility. One could always pull out the presentation code into a separate component to make Scene lighter. That component could be called Router and would be a collection of pure static functions.

Does implementing a Scene in this way not pollute the Interactor namespace and add platform-specific code to it? No, because all Scenes should be implemented in a separate target, i.e a framework. I usually call it Scenes. If you are writing a universal app, you could have one Interactors framework, but multiple Scenes frameworks, each extending Interactors with different, platform-specific, Scene creation methods. iOS Scenes framework would instantiate UIViewControllers and define flows between them, while macOS Scenes framework would instantiate NSViewControllers and NSWindows and define flows between those. Interactors and other lower layers would be shared between platforms.

Other considerations

Value Type Entities

Entities represent building blocks of our app’s model layer. They store the data that will ultimately work its way out to the user. We operate on entities to derive new data and determine truths. We need them to be reliable and easy to work with. That is why we should model them as value types like immutable structs and enums.

Here is an example of how typical entity should look like. It would have immutable properties that store state and various helper methods, initializers or properties.

I always put all entities into a separate framework. That gives us a Swift module with an implicit namespace, makes code well structured and can improve build times.

Error handling

Most of the errors should be handled in the business logic. Parsing errors, networking errors and simillar are usually the ones that are not recoverable. We do not want Scenes to handle those. All the Scene might be responsible for is presenting the error to the user. That is why the Interactor should never expose failable signal.

In the earlier examples we have used something called SafeClient. It is the type from ReactiveAPI framework (note that it as work in progress) that wraps regular HTTP client. It looks like this:

When we make a request through SafeClient, the SafeClient makes a request by using wrapped Client, but it catches any error that might happen. It then redirects errors to errors signal that can be bound to a UI and potentially offer retry capabilites.

By utilizing SafeClient Interactors need not to worry about network errors unless they can offer a fallback behaviour. In those case they would just use regular Client.

There are cases when you want to handle error on a specific signal in the Scene. In those cases it is useful to think of these errors as logic errors and not signals errors. To propagate those to the Scene, you would still use non-failable signals, but you would map elements into an enum. Let us rewrite our Authentication Interactor to use such approach.

Why this architecture?

Ever since I started working as an iOS developer I have been on a quest to find the architecture that fits best into iOS world. I started with MVC, but after some unhappy codebases I moved to MVVM. At first it made much sense. I was decoupling concerns and trimming massive view controllers down. My controllers became agnostic of business logic and managed much less state. Those niceties however came at a price. I had to implement whole new interfaces and types, I needed new mechanisms of data propagation that meant either a lot of KVO or fancy new stuff like bindings. I was doing much more work but the results were not that much better. I have just introduced another component that holds a lot of state and has to synchronise that state with others. Additionally, I never found satisfying way to model flows (routing), view controller presentations and dependency management. After a while I started looking for something better.

At that time the community was engaged around the idea of clean architecture application VIPER so I gave it a run. It goes even further into separating concerns and has many good aspects but I have also been burned by many of its drawbacks. More of my code ended up being just wirings between various components than actual logic. Distribution of concerns is no doubt a great thing, but if it comes at such huge cost — no thanks. Additionally, although some would argue, it requires significant mental effort to follow the execution path when you have to switch between so many different modules and read through various protocols. Passing didTap callback through three modules, a bunch of weak and unowned references and dozen of protocols per screen just felt wrong.

Later I tried different variations of popular architectures but nothing was really a joy to work with. I felt a bit lost during that period, however at the same time I started exploring functional reactive world. I had a hunch that it was the right way to go, but I was not sure what was the best way to apply it. There were many people out there explaining how MVVM is the perfect way to get benefits of reactive paradigm in iOS world. Initially I agreed, learned and used reactive MVVM and advocated it publicly. It took me some time to realise what I was actually doing though. The truth is, I introduced reactive programming and bindings to my codebase just to solve the issues I caused by fighting MVC.

When my quest started I was just a junior developer. I was quick to point finger at MVC for any architectural issue I stumbled upon. I was ready to burn it just because I did not understand it. Oh yes, I was reluctant to admit that to myself, but it was clear as a day.

It does not mean that I went back to MVC, however I learned that MVC has its advantages and that fighting it so hard might not be a good idea. I learned that reactive paradigm has a huge potential, but that we are using it incorrectly most of the time. Most importantly I learned that there is no single good way of doing things, rather it is all about balancing between various approaches.

The reason MVVM, as well as MVC, VIPER and others do not play so well with reactive programming is that those are object oriented architectures. They focus around objects that encapsulate and manage state and around interactions between those objects. They try to separate concerns into objects, but the more granular they go, the harder to use they become. Patching flaws of those architectures with reactive programming is wrong.

It is my hope that this post will sparkle some ideas and move us one step closer to better functional reactive architectures.

I would like to thank Jakob Vinther-Larsen, Ben De La Haye, Stefanos Marios Chourdakis and Oliver Nielsen for providing me with the constructive feedback, helping me work out the architecture and being supportive colleagues!

Hacker Noon is how hackers start their afternoons. We’re a part of the @AMIfamily. We are now accepting submissions and happy to discuss advertising & sponsorship opportunities.

To learn more, read our about page, like/message us on Facebook, or simply, tweet/DM @HackerNoon.

If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!

--

--