/ IOS

Layered Architecture to Design iOS Apps

“If you think good architecture is expensive, try bad architecture.”
- Brian Foote and Joseph Yoder

As application grows in features in complexity, we need a way to manage it. One way of dealing with it is to organize application into layers. In this article lets discuss how layered architecture is applied to iOS apps.

Layered Architecture

Layered Architecture organizes iOS application into a set of layers. A layer is a logical structuring mechanism for the elements that make up your app. Traditionally, it consists of three parts:

Layered Architecture on iOS

User Interface handles interactions between the user and the app.

Data Access exposes the data stored in external system, e.g. CoreData, iCloud, REST API, to the business layer.

Business Logic it the software representation of the business concepts. It constrains the behavior of the app to match with the needs of a specific company.

The layers follow the dependency rule: a higher layer can access any layer below it, but not vice versa. This means that business logic should never call methods from the user interface layer. Such architecture allows to make higher-level details, such as business logic, independent from the lower-level ones.

The closer component to the input and output, the lower level it has. Given iOS app, user interface has the lowest level, and entities have the highest.

After identifying the three primary layers, let’s discuss each in detail.

User Interface Layer

The primary responsibilities of this layer is to display information to the user and handle user commands. The commands will be matched to the actions with the business logic and data. The primary units of this layer are views, presenters, storyboards etc. They are typically organized using any of MV*-patterns (MVC, MVP, MVVM).

Here is how Robert Martin describes the structure of UI layer:

“[User Interface Layer] will wholly contain the MVC architecture of a GUI. The Presenters, Views, and Controllers all belong in here. The models are likely just data structures that are passed … from the use cases to the presenters and views.”
- Robert Martin [1]

Data Access Layer

Data access layer contains communication with external systems such as databases and network. Considering that most iOS apps are thin clients, this often boils down to fetching data from the Web and possibly caching it in CoreData or UserDefaults.

Gateway, Repository and Domain Transfer Object are the primary design patterns applied in this layer.

Gateway encapsulates access to external system or resource. It is used to transform complex API into a convenient one for your app to use. Gateway is client-oriented and not intended for a general-purpose use.

Same as Gateway, Repository encapsulates objects contained in a data source (e.g. Web, database) and operations on them. The distinctive feature is that Repository accepts queries, hence is more general-purpose compared to Gateway. Repository also provides authorization capabilities if any required by the underlying data source.

Gateway and Repository allow to achieve common goal in different ways: they encapsulate data source and how data is queried from it.

Domain Transfer Object (DTO) is an object that contains data without any behavior. The purpose of it is to cross some kind of a boundary and carry the data to its clients. This boundary could be physical, like iOS app and the Web, and logical like different architecture layers.

If you re-read Robert Martin’s quote from the previous section, you’ll discover that “M” from the MVC pattern stands for view model. It is the DTO that aggregates information to be rendered on the screen, not a business logic model.

DTO pattern is extensively used for communication with network. For this purpose, an intermediate Codable structs are created which reflect the structure of request and response.

Business Logic Layer

Business logic is what earns or saves money and intangibles for you or your client. Business logic layer is centered around business rules.

Business rules are operations which implement different aspects of business logic. They fulfill the needs of a company and are the primary reason of why the application is actually written.

Entities are the most important business rules together with the data that they operate. Entity is an abstract notion, which is usually implemented as a class with several fields and methods. In pure functional languages, it can be a set of procedures.

Business rules are described with use cases. Use case defines the behavior of the app in terms of user inputs and matching outputs. For example, what the app should do when user submits a login and a password.

Because of its significance, business logic layer should be designed with special care.

Designing Business Logic Layer

Business logic layer consists of all components necessary to represent and implement a given business concept. Personally, I favor a Clean Architecture solution which defines three units: entities, services and interfaces. Let’s discuss each unit in detail.

Entities

Entities are plain Swift objects that encapsulate critical business rules and data, required to fulfill it. Business rules are the most important part of this definition. A struct with several fields is not enough to make for an entity.

When built solely from entities, the abstractions it introduces could be too weak. This results in business rules leaking to the user interface level.

Weak abstractions are the primary reason for massive view controllers and massive app delegates.

At another extreme, placing all business rules into entities transforms them into the all-purpose classes, named “something-“ + “manager”, “controller”, “engine”, “helper” etc. This happens, because the required interactions might be complicated and affect global state of the app, involve multiple resources, require several responses to a command. No matter how practical entities are designed, they cannot hold all business rules of the app. Some business concepts are naturally described by operations rather than data, which leads us to the notion of services.

Service Layer

Service Layer defines a set of operations available to many kinds of clients, the most significant of which is user interface. These operations should be easy to use and oriented around their clients needs. The primary units of this layer are Service Classes.

Service Class (or service) is a stateless object, which defines a set of operations with data. It has following distinctive properties:

  • Services are client-oriented and are fully defined in terms of their clients’ needs.
  • Services are stateless. However, they can mutate global data, leading to side-effects.
  • Services define their inputs and outputs in terms of entities and language primitives.

Typically, services fulfill following operations:

  • Business logic.
  • CRUD (create, read, update, delete) on entities.
  • Networking.
  • Validations.

Interfaces

Besides entities and services, business logic layer contains interfaces. They abstract operations that will be performed by data access layer.

This gives us three primary units of business logic layer: entities, services and interfaces.

Putting Things Together

Typical iOS application layers are structured as follows:

Layered Architecture on iOS

Next, let’s examine how data flows from layer to layer. It starts by a user interacting with the app. Then the data goes to business logic layer, where it results in CRUD operations on entities. Finally, entities are saved to a backend or a database.

The reversed process is also the case. The data is fetched from the backend or the database, transformed by the business rules and finally presented to the user:

Layered Architecture on iOS

The order of dependencies is different as that of the data. Lower-level layers, which are user interface and data access, should depend on the business logic layer:

Layered Architecture on iOS

Wrapping Up

Layered architecture is widely adopted in software development in general and iOS is not an exclusion. It separates similar concerns into layers and enforces dependency rule upon them. This allows to minimize the impact of change and manage complexity. Eventually, layered architecture addresses stability of your iOS app which is the key success factor.