Clean Architecture for MassiveToBe Mobile Apps

Mostafa Mazrouh
Swift2Go
Published in
14 min readOct 6, 2020

Part 1: [iOS ] each layer = separate framework

Photo by Author, edited with Cartoona
  • Team lead: Good morning, developer. My favorite developer. I hope that you had a great weekend. Because you have some bugs and a few new features in your favorite project. A favorite project for a favorite developer.
  • Developer: …, you don’t mean project X, do you?
  • Team lead: As a matter of fact I do. Why do you hate project X so much?
  • Developer: Because it is dirty. It was based on primitive architecture, not suited for a massive app like this one. Every new feature makes it worse. And every bug is a nightmare.
  • Team lead: OK, starting from the next project we will go clean. We will make it very clean for you. But now, we have to.. you know, deal with dirt.
  • Developer: ..eww!

Sounds familiar? you may intend to avoid your previous mistakes starting from your next project. Doing it right from the beginning makes it much easier. Let’s try this together.

The approach:

Clean architecture splits the app into layers, each layer has a specific role. So let’s make each layer a separate framework.

This approach adds more complexity in the beginning, but it pays off as the code base grows. It makes it easier to add/change features and solve bugs because you know where to go. And it increases testability & reusability.

Dependency Inversion, we need to master this specific SOLID principle in order to work between isolated frameworks.

This tutorial consists into 6 sections:

  1. Mastering dependency inversion
  2. Layers of Clean Architecture (3 + 1)
  3. Dependencies of each layer: domain, presentation, data, app
  4. Detailed implementation of each layer: domain, presentation, data, app
  5. Adding a new feature
  6. Hairy questions

1. Mastering dependency inversion

Dependency inversion, sounds cool right? Makes me feel scientific.

You may ask: Uncle Bob, what dependency inversion is? And he may say something like:

First of all, I am not your uncle!

Then, dependency inversion principle is a specific form of decoupling software modules

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions (i.e. interfaces/protocols).
  2. Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.

Then you might ask me: Mostafa, do you have something to say in this regard? And what is the deference between dependency injection and dependency inversion?

Well, consider the following example:

Class A depends on Class B. Changes on B requires changes on A. And this is not good, this dependency makes Class A less reusable.

First: We need to inject A with B rather that initialize B inside A. So we can inject it in the init method, this is dependency injection:

Then: class A needs to depend on abstraction of class B “protocol/interface” rather than class B itself to make A & B loosely coupled.

Now we can inject class A with any class that conforms to BInterface i.e. class B or class C. This is called dependency inversion.

Full Dependency vs Dependency Inversion

So, we are experts now in “dependency anything”, let’s discuss the layers of our clean architecture.

2. Layers of Clean Architecture (3 + 1)

It is very common for a clean architecture to have 3 layers: presentation, domain & data. Here we add another layer: app layer to be the entry point and inject each layer with it’s dependencies.

App Layer: the entry point of the app which contains dependency injection

Separated Layers

Presentation Layer: we put our UI here (Views & View Models)

Domain Layer: contains business logic

Data Layer: gets data from remote or local data sources

Now let’s create these layers in a new project. We use this approach for apps that will be massive over time, so the project name is “MassiveToBe”. Our goal is architecture so we keep the app simple for now, it only shows a list of posts.

We use Typicode,a free service that provides fake json, in this article we use posts endpoint.

Create a new project, name it MassiveToBe or whatever you like, include SwiftUI & include Unit Tests

We need to convert the project to a workspace and include cocoa pods, so close the project, open the terminal, navigate to your project location and:

pod init

then

pod install

Open the new workspace file and let’s create some layers.

In the XCode: File -> New Project, then choose Framework

Name it data with small “d”, because Foundation framework has a Data type, next:

Be very careful here, make sure to save it inside the folder MassiveToBe because it is not the default choice . Also set Add to and Group to MassiveToBe, the click create

Now you should find your new data layer inside the main folder

Notice the data layer appeared in XCode as an independent project/framework outside MassiveToBe project but inside the workspace

Repeat the same steps for presentation and domain, so your XCode looks like this:

We have 5 projects in 1 workspace: 1) presentation 2) domain 3) data

4) Pods: which will contain all cocoa pods libraries

And we have MassiveToBe project which is the entry point to the work space and control every thing. We call it 5) App layer

At this point, we have done all layers we need.

3. Dependencies of each layer

Dependency is a broad software engineering term used to refer when a piece of software relies on another one.

In our case, if a class in presentation layer uses a class in domain layer, so presentation layer depends on domain layer.

Dependency Diagram

Domain Layer: doesn’t depend on any other layer.

Presentation Layer & Data Layer both depend on Domain Layer

Presentation Layer & Data Layer don’t depend on each other

App Layer depends on all of them

Note: In dependency diagram, if we say: AB it means that A depends on B, which means A sees every thing inside B

Now that we have discussed what each layer does and it’s dependencies, let’s do it in XCode.

3.1. domain layer dependencies

As the dependency diagram conveys, domain layer is completely independent. It contains the business logic of the app regardless any thing you use in other layers.

3.2. presentation layer dependencies

Presentation layer depends on domain layer to get business logic from it.

Till now in our project, each layer doesn’t know any thing about the others, let’s introduce domain layer to presentation layer

Go ahead and select presentation layer, general, targets: domain, scroll down to Frameworks and Libraries, press + button and choose data.framework

3.3. data layer dependencies

Just like presentation layer, data layer depends on domain layer only.

Why? Because data layer has to know how to provide data to domain layer so that it can do it’s business logic. That can be done by conforming to abstraction “dependency inversion”. We will take about this in details soon enough.

Now repeat the same steps to add domain layer as a dependency to data layer.

3.4. app layer dependencies

This layer is the entry point of the app, controls every thing, and has Dependency Injection role. So it must see the other 3 layers i.e. depend on all of them.

We call it app layer but it’s name in XCode is “MassiveToBe”. It has the name of the project.

Repeat the same steps to add data.framework, domain.framework presentation.framework to MassiveToBe project “app Layer”.

Note that Pods_MassiveToBe.framework is already add for you when you performed pod install remember?

Hooof, now we have set all dependencies for each layer. Celebrate by doing 7 push ups and then let’s implement each layer and write some code.

4. Detailed implementation of each layer

Domain layer doesn’t depend on any thing else so let’s start with it

4.1. Domain layer implementation:

Dependency diagram: domain layer

Interactor: contains business logic for each specific feature. It is also called Use Case

Entity: a model contains data for the interactor. Data model that suits the business logic.

Domain Repo Interface: this interface/protocol is an abstraction of how the interactor gets data from data layer

To add Post feature to domain layer, we need:

  1. PostInteractor for business logic
  2. PostEntity to encapsulate data
  3. PostDomainRepoInterface to abstract needed data

Now go to your domain layer and add a new group “Posts”, add 3 swift files inside this group: PostInteractor.swift , PostEntity.swift, PostDomainRepoInterface.swift

On the top lift on the XCode, beside Run/Stop buttons you can select from 4 targets a target for each layer. Choose domain layer and build it (command + b) to ensure that every thing is fine.

In PostEntity.swift we need to make a struct that holds needed data for the interactor:

PostEntity conforms to Identifiable protocol so we can use it in a List in SwiftUI

In PostInteractor.swift add the following code:

  1. PostInteractorInterface protocol is not necessary here but we can use it to mock the interactor when we do our unit tests in the future
  2. PostInteractor uses an injected postDomainRepo object which conforms to PostDomainRepoInterface to get data.
  3. getPosts() method doesn’t contain actual business logic, just calling another method for the sake of simplicity. But this is where you apply your business logic. For example you can check if a post contains racist words to ban the post — or feature it if you are racist. Or you can check if you have added any of these posts to your favorites and apply a star to it.

In PostDomainRepoInterface.swift we write our protocol/interface as an abstraction for any one wants to provide data to our interactor.

Command + B to make sure that every thing is working in domain layer. Now our independent domain layer is ready, let’s use it from somewhere else.

4.2. Presentation layer implementation:

SwiftUI has it’s own way of data binding by using “ObservableObject”, “@Published”, etc. So you can say that SwiftUI adopts MVVM (Model View ViewModel) by default rather that MVP or MVC.

In this approach, presentation layer contains a View and a ViewModel for each screen, and considers the Entity from Domain layer as a Model

Dependency diagram: presentation layer

PostView: represents a screen, contains UI components and navigation, shouldn’t contain any sort of logic

PostVM: Post View Model is responsible for converting the Entity in such a way that data is easily managed and presented. It handles all of the view’s display logic.

As the diagram indicates, PostVM has a reference to PostInteractor in the domain layer to get the Entity.

Now add a new group in the presentation layer and name it Post. Add a new swift files: PostVM.swift & a new SwiftUI file: PostView.swift

PostVM.swift looks like this:

Note the following:

  1. import domain so we can access the interactor and the entity.
  2. postInteractor is injected to the the PostVM in the init method as an abstraction, so we can mock it in unit testing.

PostView.swift file be like that:

Nothing fancy here, just showing data from the array posts on the screen. Note that postVM is marked with “@ObservedObject” so it is being injected to the view.

Now our presentation layer is ready, the View can show data from the VM, the VM can access data from the Interactor. But from where does the Interactor get the real data? Yes, from the DataRepo in the data layer

4.3. Data layer implementation:

Dependency diagram: data layer

Here comes the cool stuff. As we already know by now, data layer depends on domain layer & domain layer is completely independent and doesn’t depend on any thing.

When the independent domain layer gets data from data layer, it depends on an abstraction of its own. We already made this abstraction in the domain layer, it is the protocol PostDomainRepoInterface, remember? This is our beloved dependency inversion.

Go ahead to the data layer and create a new group “Post”, add 4 swift files to it: PostModel.swift, PostDataRepo.swift, PostRemoteDataSource.swift and PostLocalDataSource.swift

As the dependency diagram indicates, the interactor in the domain layer depends on the abstraction PostDomainRepoInterface.

In the data layer, we have PostDataRepo that implements this abstraction, so that data and domain are loosely coupled.

PostDataRepo combines data from different data sources.

PostDataRepo has 2 data sources, PostRemoteDataSource & PostLocalDataSource. Coder is just a helper class you can ignore it, it has a default value anyway.

The data source contains the actual implementation of getting data. Like Alamofire, URLSession for remote. And Coredata, Realm for local.

The data repo depends on interfaces of data sources rather than data sources them selves. Another dependency inversion here.

PostModel conforms to Codable for parsing, and contains a DTO (Data Transfer Object) method to convert itself to PostEntity that is needed in the domain layer.

We are not doing the local data source, you got the idea. Yes?

The pattern of data repo and data source is a well known design pattern called: Repository Pattern.

Now we are done with our awesome data layer. It is time to access all of the 3 layers from the app layer. Remember it has the name “MassiveToBe”.

4.4. App layer implementation:

The app layer is the entry point of the app. It contains the Dependency Injection Container & the Environment.

Dependency diagram: app layer

The Environment contains things like base url, end points, certificates, etc.

Create a new group “AppEnvironment”, add a new swift file “AppEnvironment.swift”:

The AppDI prepares all dependencies for each feature separately.

Create a new group “AppDI”, add a new swift files “AppDI.swift” & “PostDI.swift”. So each feature like Post has a separate DI and the AppDI access all of them.

  • Now we harvest our hard work, we import all our 3 layers.
  • PostVM is the only dependency we need for PostView. So postDependencies() method returns PostVM.
  • To create PostVM, it needs PostInteractor which needs PostDataRepo which needs PostRemoteDataSource.

In AppDI.swift:

Here we just return PostVM that we get from PostDI. Again, AppDI is a container uses all DI files.

You can notice that AppDI conforms to AppDIInterface. Where is this interface and why we need it?

Before answering this question, let’s see our Post feature on the screen. So comment this AppDIInterface for now.

Open SceneDelegate.swift file, find willConnectTo() method and paste this code:

Here we create a new PostView, inject it with needed PostVM from the AppDI and we make it our rootView.

No select MassiveToBe target from the top left and run the app. You should see this screen:

5. Adding a new feature:

Say that we want the user to show post’s details when he clicks on a post. We need to add a new feature “PostDetails”.

So we need to add 10 or 9 files:

  1. In presentation layer: PostDetailsView & PostDetailsVM
  2. In domain layer: PostDetailsInteractor, PostDetailsEntity, PostDetailsDomianRepo
  3. In data layer: PostDetailsDataRepo, PostDetailsModel, PostDetailsRemoteDataSource, PostDetailsLocalDataSource if we need it
  4. In app layer: PostDetailsDI

Now we will add a dummy PostDetailsView & PostDetailsVM to show navigation from Post to PostDetails.

Add 2 new swift files in a PostDetails group in presentation layer

PostDetailsView is injected with PostDetailsVM so it is a dependency.

Create PostDetailsDI.swift in the app layer:

PostDetailsVM is the only dependency here.

Now create a new method postDetailsDependencies() in AppDI.swift

Now we want to create PostDetailsView and inject it with PostDetailsVM inside PostView.

Here comes the problem:

We can’t access the app layer from the presentation layer. We can only do the opposite. So how can we solve this problem?

Yes, you are right. Dependency Inversion.

We make an interface/protocol in the presentation layer called AppDIInterface. And the actual AppDI conforms to it. Then we inject the AppDI from the app layer into the presentation layer. OK? let’s do it.

In the presentation layer create a new file: AppDIInterface.swift and:

  • In the app layer, make AppDI conforms to AppDIInterface.
  • We need to inject AppDI into PostView.

In PostView.swift initialize it with AppDIInterface

We can access AppDIInterface in PostView because both are in presentation layer. And we can get PostDetailsVM as a dependency and pass it to PostDetailsView.

From SceneDelegate.swift we inject the actual AppDI:

Now run the app and press on any post, it should show: “My Details”.

6. Hairy questions

A. This is over-engineering, why should I add 9/10 file for each feature?

Well, if you follow MVVM, you can get away with 3 files. View, ViewModel and Model for each feature. And put all your logic in a big messy ViewModel. But as your project grows you will suffer and you know that.

B. I didn’t say I will do every thing in the view model, and you know that, don’t you?

OK, you will follow Repository Pattern, so you will add 4 more files: DataRepo, RemoteDataSource, LocalDataSource & DataModel.

You want to exclude the Domain layer? Is that it? Then you will put your business logic in the ViewModel. This violates Single Responsibility Principle. And this makes the ViewModel bigger that it should be.

C. In a lot of cases, mobile apps don’t need business logic at all. Or we may need Interactor in a few features. Why should I add Interactor/Domain for each single feature including About US and Help?

  1. Consistency: a new developer joining the project, you started working on another feature with your team mate, both of you know what is where.
  2. You are ready for any change request. They may ask you to show About Us for specific type of users i.e. business logic

D. Even if I need all of these files, why should I separate them like this?

Yes, this separation adds more complexity in the beginning. You need to master dependency inversion and take care of access control.

Dealing with dependency inversion on this extreme level gives you easily mocking objects out of the box. When you write your unit tests, you have interface/protocol for every thing.

If a team of developers work on the same project, this separation prevents architecture violation. For example you can’t access the DataRepo from the ViewModel, you must call the interactor. You can’t call any thing from the domain layer, which makes it’s reputability 100% ensured.

This separation also ensures that you can change your data source local((Replace Realm by Coredata) or remote(Replace Alamofire by URLSession) without touching any thing in the Data Repo, Domain Layer, Presentation Layer. You can replace SwiftUI with UIKit without touching Domain Layer or Data Layer

E. You mentioned access control, what is it? and what should I do different?

Access control levels like: public, internal, fileprivate & private.

When you create a class or a function without specifying any of these levels, the default access control in internal which means it can be accessed from inside the framework/module only. We don’t pay attention for this when all our files are in one framework, but this is different.

So you need to mark any class, interface, function with “public” if you want to access it from another layer/framework.

For example: in PostDetailsVM, we had to make init() function public to initialize this ViewModel in the DI.

--

--

Mostafa Mazrouh
Swift2Go

Mobile architect — write about iOS, Android and Automotive