Are you kidding me? Aren’t there enough clean architecture patterns already?
Yes, there are. Like VIPER/Riblets, all of them are simply different approaches to the same clean architecture principles, but they only differ in details or approaches to handling some elements. It all started with famous Uncle Bob article, so be sure to check it out. The one we are presenting here is the one we successfully introduced at Tooploox (thanks to awesome Jorge Ortiz workshops, don’t miss his work). It turned out great for our workflow and code quality, and we want to share some of our experience with this series of articles.
Why does architecture matter?
When thinking about our app architecture we shouldn’t think whether to use one, but which one to use. Valid architecture helps to create a structured, easily understandable code, which leads to lower maintenance costs. Having everything modularized makes teamwork much easier and productive, as each person can work independently. It helps in designing solutions and reduces a probability of errors. Instead of having to worry about how the thing would work together or how to connect some parts, you can focus on the actual logic. Finally, it lifts project management to the next level i.e. planning and estimations are more accurate because of the use of proven templates with familiar environments. It also lowers entry threshold for new team members and makes teams more flexible.
So why not simply MVC, MVVM? Why another complex solution?
Don’t get me wrong, MVC and MVVM are valid architectures. They’ve got their pros and cons and could be used to create great solutions, but they do have limitations. In general, MVVM is hard to scale. After all, mobile apps are hardly ever just a few features and a couple of views. When functionality count grows, MVVM starts to be harder to maintain, view models become massive view models so it’s harder to keep everything separated as it should be.
Also, MVC/MVP/MVVM are not the best choice if you care about unit tests as it’s much harder to separate elements and mock dependencies for tests purposes and each component tends to have multiple responsibilities. If you ever worked with MVVM, you’ve probably noticed that it’s not a complete architecture. It lacks some fundamental layers like routing or dependency management and is often completed by introducing coordinators or forces to break encapsulations and make components talk to each other in an unstructured way.
By using clean arch, your code becomes more modularized, better structured and highly scalable. Each element is lightweight and conforms (at least tries to) to single responsibility rule. Layers have good separation through inversion of control, thus components are easily replaceable. Unit tests are trivial to write with such a structure, dependencies can be mocked, and functionalities are simple and easy to unit test. By loosely coupling elements, parallel coding on the same feature is a blast. Just agree on protocols and contracts and do your job. It’s much easier to work on features, as you can wrap your mind around one issue at a time and don’t have to care about anything else.
The other side of the coin.
But it does have some disadvantages. It’s complex, sometimes hard to comprehend at first plus it has quite large entry threshold. It’s also a little bit bloated. You will create a LOT of different protocols, classes, factories, and you’ll have to manage all the files. It’s not trivial nor easy (having in mind lack of proper reflection does not help in dependency injection management with swift). Many of those files/classes are simple pass-through entities, so sometimes it feels like a waste of time, but it has its reasons. It also swims a little bit against frameworks and official guidelines. Apple API has been designed to work with MVC so sometimes you’ll have to get a little bit hacky to make it get along with a clean architecture.
In the end, we found all the effort was worth it, and drawbacks don’t really overshadow the gains we had as a team. There are really not that many resources about clean architecture that would explain everything step by step, with real-life examples. We feel that is one of the reasons why more complex architectures are sometimes ignored in mobile development. We would like to change that.
Let’s get started
The main focus in clean architecture is to separate modules into layers: Connector, Presenter, View, Use Case, and Gateway, which conforms to SOLID principles. Layers are connected using dependency inversion principle, which is abstracted behind protocols. Together, layers form a single module, denouncing most likely a single view with its functionalities. The architecture presented here is a very basic version of it. You can notice lack of router component (routing is handled by either view or connector).
All your UIKIt stuff goes to View, represented by just a View Controller. Keep it as dumb as possible, avoid logic, avoid states, just plain simple views interpreting data passed by a presenter, and informing presenter about events.
In some cases, especially when using Storyboards, view is also handling navigation. It’s a good idea to put all platform-related code here, if possible. In most cases view is strong reference holder for the whole structure.
Think of it as view without UIKit. All the knowledge about data and how it should be displayed sits here. It retrieves events from View and runs business logic (through UseCases). It also decides about transition/routing and handles view updates.
The data provider, the abstraction layer over databases, APIs and services. It grabs the data and passes it to use cases when needed. It could be anything – database, Bluetooth, API calls, Keychain, you name it. In most cases, it’s implemented as a repository pattern.
Use Case (Interactor)
It’s where business logic sits. It communicates with entity gateway, processes data and passes results as a data model to a presenter.
It handles dependencies, composing and setup elements and is responsible at some point for routing. In simplest architecture version, it also handles view transitions, keeping a reference to navigation components. It initializes and configures child connectors.
How it works together
Let’s consider an example of view presenting a list of data. Everything starts with connector setting up components and its dependencies: creating view, presenter, passing gateways and use cases.
After that connector (acting in this case as a router) presents a view, which on show will trigger presenter logic, just get needed data.
Presenter executes use case, which gets data from data gateway, processes it and passes to presenter when done. One Use Case can aggregate many gateway operations – e.g. it can get a list of objects from API call, then fetch details of each object and return to presenter data structure of objects with complete details.
When data is ready, Presenter converts data to be ready for View, e.g. converts dates to string using DateFormatter or converts models to displayable data, etc. This kind of data is then passed to View. View acts as a passive component, only by passing UI events to Presenter and displaying what Presenter has provided.
These are the very basics of clean architecture used here, at Tooploox, to give you some insights about what is the motivation behind all the effort with it. In the upcoming articles, we would like to continue with some real-life examples of modules, show you how to handle some cases like routing, asynchronous calls, view-less modules, or how to introduce reactive programming. Hope you liked it. We are looking forward to your feedback. If you have any concerns/questions about it, just write the comment, we are eager to help 🙂