Hexagonal Architecture for iOS

An architecture pattern that focuses on the logical parts that are inside and outside of the app

Oleksandr Stepanov
Better Programming

--

Photo by Antoine Merour on Unsplash

This is the first part of a series about the Hexagonal Architecture pattern adopted to iOS development. It covers theoretical aspects mainly. The next part will cover the practical side, along with a sample project.

When we talk about architecture for an iOS app, we usually mean UI an architectural approach like MVC, MVVM, VIPER, etc. But an app architecture is not only about UI. Every such pattern has a part called Model or Entity in its abbreviation, which is responsible for domain or business logic, third-party dependencies integration, system and framework interaction, etc. This is where Hexagonal Architecture may help to make things clear.

Originally, this architecture was explained by Alistair Cockburn as Ports & Adapters Architecture due to the naming of the main components. The term Hexagonal Architecture became popular due to the schematic representation of how the components are connected.

The Hexagonal Architecture is an architectural style that moves a developer’s focus from conceptual layers to a distinction between the inside and outside parts of the app. The inside part is the domain or business logic. The outside part is UI, network, sensors, data storage, etc. The connection between the inside and the outside part of an application is realized via ports and their implementation counterparts, called adapters.

Definitions

Domain model

A domain model is a conceptual model, a representation of a meaningful logic that is required to be implemented in the software. It’s essentially the business logic of the application in a conjunction with data models.

Ports

A port is a consumer-agnostic entry and exit point to/from the domain model. It is the medium through which business logic is accessed. In Swift, ports are the protocols that correspond to use cases in the application. Ports are consumed by adapters.

Adapters

An adapter is a bridge between the domain model and the service needed by the application. They act as a layer whose purpose is to perform communication between various external actors and business logic in a way that both remain independent.

In the Hexagonal Architecture, all external actors interact with the ports through the adapters. One adapter could be a BLE service responsible for interaction with the Core Bluetooth iOS framework. Another could be a controller-like function that interacts with remote storage.

Types of Ports and Adapters

  • Primary or driving adapters are the ones to start some action on the domain model. In MVP, a presenter will be such an adapter to interact with the domain model, i.e., the business logic of the application. In MVVM, it’s a view model; in VIPER or RIB, an interactor. The role the port plays, in this case, is as the protocol of a domain model class from the core. In other words, these adapters interact with it not directly but through a protocol.
  • Secondary or driven adapters represent the connections to the back-end tools like the database, network API, sensors, etc. They interact directly with the native or specific API and are called by the domain model through the appropriate port. An example of a secondary port is an interface to store model objects. This interface simply declares methods to retrieve, update, and remove object(s) from storage. It tells you nothing about the way these objects are stored.
Hexagonal architecture scheme representation

As you can see, the hexagonal architecture promotes the separation of concerns by encapsulating logic in different layers of the application. This enables a higher level of isolation, testability, and control over your domain-specific code. Each layer of the application has a strict set of responsibilities and requirements. This creates clear boundaries of where certain functionality should be and how those layers should interact with each other.

In fact, the domain model could be not just one class, but a set of classes, each responsible for its own piece of the application functionality. These classes are normally singletons and may be interconnected with each other directly or through an app coordinator, whose goal is to instantiate and hold the references, and to orchestrate them.

Domain model details

The Benefits

  • External services independency. You can develop the inner core of an application before having to think about what type of data storage you are going to use. By defining the ports and adapters for your storage, you are free to use any technology implementation.
  • Separation of concerns. The outer layers typically change more frequently than business logic. For example, the UI or third-party API typically evolves faster than the business rules of the application. This separation enables you to quickly iterate on the outer layers without touching the inner layer that must remain consistent.
  • Ability to cover the business logic with unit tests. Now when your application domain model, which encapsulates business logic, is agnostic to the outer dependencies, it’s much easier to test it by mocking its adapters.
  • The adapters are replaceable. The aim of the adapters is to abstract the actual implementation of the outer services and dependencies. This abstraction allows the application to receive requests and send responses to various outside technologies without having to know the actual implementation. It makes it possible to replace an adapter with a different implementation that conforms to the same interface.
  • Good maintainability level. Maintainability is the absence of technical debt, i.e., changes in one area of an application do not directly affect others. Adding features does not require large code-base changes. The benefits mentioned above altogether provide a high level of maintainability.

The Disadvantages

  • The only drawback, to my mind, is the number of entities like classes and protocols one should define to implement this architecture in a project. Obviously, in a no-architecture approach, a view controller may interact with Realm, Firebase, or Core Bluetooth directly. However, this design has neither of the benefits mentioned above.

A skeptical reader may say that a presenter, view model, or interactor is actually the place for an app business logic. And that is partially true, but normally a good engineer will not place direct interaction with third-party or native frameworks there. One would rather put it on another level, some kind of service, in order to reuse its functionality, like an API service, Firebase service, Bluetooth service, etc. And then these services become in place of a domain model, while an intermediate layer between it and a view becomes an adapter. To cover this layer with unit tests, one would need to mock these services and thus connect with them through a protocol, i.e. port.

These services may interact with the outer layer directly, but then they become not testable as well if these connections are not abstracted and can not be mocked. Finally, adding ports and adapters on this side leads us to the topic of this story.

As a conclusion, I would like to quote Robert C. Martin with his definition of an architecture from his book “Clean Architecture”:

“The architecture of a software system is the shape given to that system by those who build it. The form of that shape is in the division of that system into components, the arrangement of those components, and the ways in which those components communicate with each other.”

--

--