Posts Tagged - architecture

SOLID Principles in Swift - Open / Closed Principle

Background

In this series of posts we are going to be covering the SOLID principles of software development. These are a set of principles / guidelines, that when followed when developing a software system, make it more likely that the system will be easier to extend and maintain over time. Let’s take a look at the problems that they seek to solve:

  • Fragility: A change may break unexpected parts, it is very difficult to detect if you don’t have a good test coverage
  • Immobility: A component is difficult to reuse in another project or in multiple places of the same project because it has too many coupled dependencies
  • Rigidity: A change requires a lot of effort because it affects several parts of the project

So what are the SOLID principles?

  • Single Responsibility Principle - A class should have only a single responsibility / have only one reason to change
  • Open-Closed Principle - Software should be open for extension but closed for modification
  • Liskov Substitution Principle - Objects in a program should be replaceable with instances of their sub types without altering the correctness of the program
  • Interface Segregation Principle - Many client-specific interfaces are better than one general-purpose interface
  • Dependency Inversion Principle - High level modules should not depend on low level modules. Both should depend on abstractions

In this article we will focus on the Open-Closed Principle.

What does it mean?

So the open-closed principle states:

Software should be open for extension but closed for modification

What exactly does this mean? I think out of all the principles this is the hardest to understand. Mostly due to the fact the explanation leaves far too much open to interpretation. A simple Google search will offer up several examples of how this principle works. In this article I will present my take on the principle and how to build software that will comply with it.

Let’s focus on a type that by design, violates the open closed principle.

Enums

Enums are a very powerful tool in Swift. They are first class types and such can have associated values and conform to protocols for example. However when used at the boundary of a particular system or module they can present a particular problem.

Let’s imagine an analytics system where we can log events. This is a design pattern I’ve seen in many places:

// 1
enum AnalyticsEvent {
    case newsList
    case newsDetail(id: Int)
}

// 2
class AnalyticsController {
    func sendEvent(_ event: AnalyticsEvent) {
        let title = event.title
        let params = event.params
        
        // Send data to analytics network
    }
}

// 3
extension AnalyticsEvent {
    var title: String {
        switch self {
        case .newsList:
            return "News List"
        case .newsDetail:
            return "News detail"
        }
    }
    
    var params: [String : String] {
        switch self {
        case .newsList:
            return [:]
        case .newsDetail(let id):
            return ["id": "\(id)"]
        }
    }
}

Let’s look at what we have here.

  1. The first thing that is defined is an enum that houses all the different analytics events that are available. Some with associated values.
  2. Next we have our analytics controller, this takes an event as a parameter, takes information from the event and would then send that on to our analytics system.
  3. Here we have extended the AnalyticsEvent enum to add 2 variables, one for title and one for params that contain a switch for each of our events.

On the surface or at first look this might appear an ok solution. We have hidden our implementation of the analytics network inside our AnalyticsController and setup a defined set of events that we can support.

The Problem

Now lets look at the problems that this approach causes.

  • What happens if we need to add new events to our analytics system?
  • What if our analytics system was part of a separate package or module?
  • What happens when we have a lot of events?

So first of all, every time we need to add / update or remove any of the events in our analytics system we need to modify the enum. We can’t just implement new events and have them be compatible with the system. Also if the number of events becomes very large then the code will grow large in size. Making it hard to read, maintain and a bit of a mess. Also the enum now has multiple responsibilities, as it covers many events breaking the single responsibility principle.

The second issue which is probably the more important one, is let’s say we are breaking our app down in to separate packages. This Analytics Controller and Event would be in a separate package, what if we wanted to re-use it across different projects? Both of these scenarios become very difficult because we are using an enum that would need to be updated to accommodate events for different apps. The package would need constantly updating as new events were added.

The Solution

So we have identified some issues with the above implementation, how can we change it to make solve these issues we have identified? Look at the new example:

// 1
struct NewsListEvent: AnalyticsEvent {
    var title: String {
        return "News List"
    }
    
    var params: [String : String] {
        return [:]
    }
}

struct NewsDetailEvent: AnalyticsEvent {
    let id: Int
    
    var title: String {
        return "News detail"
    }
    
    var params: [String : String] {
        return ["id": "\(id)"]
    }
}

// 2
protocol AnalyticsEvent {
    var title: String { get }
    var params: [String: String] { get }
}

class AnalyticsController {
    func sendEvent(_ event: AnalyticsEvent) {
        let title = event.title
        let params = event.params
        
        // Send data to analytics network
    }
}

Let’s look at how we have changed the example:

  1. First of all we have now removed the enum. Using an enum as a huge list of possible options is considered a code smell. Especially when it involves something that may change often. If you have a finite number of states that is unlikely to change, that is more suited to an enum than a list of analytics events. We have refactored those enum cases into 2 separate classes now.

  2. We have switched the enum for a protocol that exposes the necessary items required by our analytics controller (we could have potentially done this in the previous example however we would still have the enum).

So what advantages does this have over the previous implementation?

  • With the events now being in separate classes we are now following the single responsibility principle, each event has its own class that can be updated whenever they need to be.
  • Now that we are using a protocol and not an enum, we are now able to add new events to our app without ever having to touch the analytics system. Simply create a new class and make it conform to AnalyticsEvent, and we can use it with the analytics controller.
  • Further to that we could have our analytics system in a separate reusable package, then our client apps could define their own set of events to use with the system.

Our analytics code is now open for extension, but does not need to be modified to support new events. Unlike our enum example.

Read More

SOLID Principles in Swift - Single Responsibility Principle

Background

In this series of posts we are going to be covering the SOLID principles of software development. These are a set of principles / guidelines, that when followed when developing a software system, make it more likely that the system will be easier to extend and maintain over time. Let’s take a look at the problems that they seek to solve:

  • Fragility: A change may break unexpected parts, it is very difficult to detect if you don’t have a good test coverage
  • Immobility: A component is difficult to reuse in another project or in multiple places of the same project because it has too many coupled dependencies
  • Rigidity: A change requires a lot of effort because it affects several parts of the project

So what are the SOLID principles?

  • Single Responsibility Principle - A class should have only a single responsibility / have only one reason to change
  • Open-Closed Principle - Software should be open for extension but closed for modification
  • Liskov Substitution Principle - Objects in a program should be replaceable with instances of their sub types without altering the correctness of the program
  • Interface Segregation Principle - Many client-specific interfaces are better than one general-purpose interface
  • Dependency Inversion Principle - High level modules should not depend on low level modules. Both should depend on abstractions

In this article we will focus on the Single Responsibility Principle.

Problem

The first principle in the list is the Single Responsibility Principle. This principle is defined as follows:

A class should have only one reason to change

This means a class should be responsible for only one task, not multiple. Let’s take a look at an example and how we can refactor it using the principle.

struct SomeNews: Codable {
    let id: Int
    let title: String
}

class NewsDatasource {
    func getNews(completion: @escaping ([SomeNews]) -> Void) {
        // 1. Create request
        let url = URL(string: "SomeNews/URL")!
        let request = URLRequest(url: url)
        
        // 2. Fetching data
        let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
            
            // 3. Parsing data
            guard let data = data,
                  let news = try? JSONDecoder().decode([SomeNews].self, from: data) else {
                completion([])
                return
            }
            
            completion(news)
        }
        
        dataTask.resume()
    }
}

This looks like a fairly simple news datasource / service that is fetching some news items from the web. However if we take a closer look we will see it’s responsible for more than one task.

  1. Creating the URLRequest that is used to fetch the news articles
  2. Fetching the data using a URLSession
  3. Parsing the data

Already that is 3 different responsibilities this class has. They may seem fairly straight forward in this example but imagine how this could get out of hand quickly in a larger codebase. Let’s cover some of the scenarios.

  • Is this example the news request is simple. However what if the request was more complex, what if we needed to add headers etc to that request? All that code would be in this class.
  • What if we wanted to change the request used to fetch the news? We would have to make a code change here. Or what if we could fetch news from more than one API? How would we do that in the current structure?
  • Once the request has been made we are using a JSONDecoder to decode the response. What if the response comes back in a different format? What if we wanted to use a different decodable for the response?
  • What if the news request can be used in multiple places?

As we can see from the above list, there are several scenarios that would require a code change of this class. If we recall what the single responsibility stands for:

A class should have only one reason to change

There is also a side effect of this which isn’t as obvious, that is testability. Let’s look at some examples:

  • How would we test changes the URLRequest? If did indeed change the URLRequest or it was being generated differently, how would we test that?
  • How do we test how our class handles responses from the server? What happens if we get an error for example?
  • How do we test our decoding code? How can we be sure that it is going to handle incorrect data correctly? How does our news datasource handle decoding errors?

If we look at the code in the example we can see that in would be impossible to write unit tests covering any of the above scenarios. Let’s have a look at how we can break this class down into single components, allowing us to make changes only in one place and at the same time improving testability.

Breaking it down

URL Builder

Let’s start by breaking this class down into separate classes, each with one responsibility. First of all let’s take out the building of the URLRequest and put it in another class.

class NewsURLBuilder {
    private let hostName: String
    
    init(hostName: String) {
        self.hostName = hostName
    }
    
    func getNews() -> URLRequest {
        let url = URL(string: "\(hostName)SomeNews/URL")!
        let request = URLRequest(url: url)

        return request
    }
}

Great, now we have a class that’s only responsibility is to build and return a URLRequest. In a more complex system this class might need ids, user tokens etc in order to configure the request. In the scenario where we need to change how news is retrieved we only need to modify this one class in order to make that change. We can also change the hostname based on the environment such as dev, test and prod.

The other benefit of doing this is we can now write unit tests to make sure that the URLRequest is being built correctly. Let’s do a small example now:

class URLBuilderTests: XCTestCase {

    func testURLBuilder() throws {
        let builder = NewsURLBuilder(hostName: "http://mytest.com/")
        let request = builder.getNews()
        
        XCTAssertEqual(request.url?.absoluteString, "http://mytest.com/SomeNews/URL", "Request URL string is incorrect")
    }

}

Our URL builder isn’t particularly complex so doesn’t need many tests. But at least here with it being in a separate component we can test the construction and make sure it’s being created correctly. We could expand this test to test other elements of the request if needed, or if we needed different parameters to build the request.

Parser

Next lets take the parser and put that into it’s own class.

class NewsParser {
    private let decoder: JSONDecoder
    
    init(decoder: JSONDecoder) {
        self.decoder = decoder
    }
    
    func parse(data: Data) -> [SomeNews] {
        return (try? decoder.decode([SomeNews].self, from: data)) ?? []
    }
}

Here we can see we have taken our decoding code and put it into a separate class. This class has one reason to change, it only needs to be changed if the parsing needs to be changed! Also like our URL builder class we can now test the decoding to make sure we get the results we are expecting:

class NewsParserTests: XCTestCase {

    func testCorrectData() throws {
        let correctJSON = """
        [
          {
            "id": 1,
            "title": "Test Article 1"
          },
          {
            "id": 2,
            "title": "Test Article 2"
          }
        ]
        """
        
        let data = correctJSON.data(using: .utf8)!
        
        let parser = NewsParser(decoder: JSONDecoder())
        let news = parser.parse(data: data)
        XCTAssertFalse(news.isEmpty)
        XCTAssertEqual(news[0].id, 1)
        XCTAssertEqual(news[0].title, "Test Article 1")
    }

    
    func testInCorrectData() throws {
        let incorrectJSON = """
        [
          {
            "id": 1,
            "title": "Test Article 1"
          },
          {
            "id": 2,
        ]
        """
        
        let data = incorrectJSON.data(using: .utf8)!
        
        let parser = NewsParser(decoder: JSONDecoder())
        let news = parser.parse(data: data)
        XCTAssertTrue(news.isEmpty)
    }
}

So what have we done here. We have created a couple of unit tests for our parser.

  • The first one supplies the parser with some correct JSON data and checks that the news objects we receive are correct and have the right data.
  • The second test sends some incorrect data to the parser and tests that we receive an empty array as expected

I’m aware that we aren’t handling errors in this example, this has been done to try and keep things as simple as possible

Putting it all together

Now that we have separated out these components into separate pieces, lets see how our datasource looks now.

class NewsDatasource {
    private let requestBuilder: NewsURLBuilder
    private let parser: NewsParser
    
    init(requestBuilder: NewsURLBuilder, parser: NewsParser) {
        self.requestBuilder = requestBuilder
        self.parser = parser
    }
    
    func getNews(completion: @escaping ([SomeNews]) -> Void) {
        // 1. Create request
        let request = requestBuilder.getNews()
        
        // 2. Fetching data
        let dataTask = URLSession.shared.dataTask(with: request) { [weak self] (data, response, error) in
            
            // 3. Parsing data
            guard let self = self,
                let data = data else {
                completion([])
                return
            }
            
            completion(self.parser.parse(data: data))
        }
        
        dataTask.resume()
    }
}

Now if we look here we can see that we have swapped out the code for building the request and parsing the data for our separate classes. Now our example is following the single responsibility principle. We have 3 components now:

  1. A component to build our request
  2. A component to execute the request
  3. A component to parse the data we get back from the request

So what have we gained:

  • We now have test coverage of our components (we could update the NewsDatasource to have tests too but that is a bit more advanced and out of scope of this article)
  • We have the ability to re-use these components in other parts of the app or in other apps if we need to
  • If we need to make changes, each component is only responsibility for one thing, so we can update and test each change in turn. Rather than making multiple changes in one place and not be able to test them!

Feel free to download the sample and play around with the tests yourself!

Read More

Using protocols, ObservableObject and @Published property wrappers in SwiftUI

Background

As I’m sure any iOS developer now knows, the future of iOS app development is SwiftUI. Apple’s new UI development language is now on it’s 2nd major release. While my own personal feeling is that the framework is not quite ready for prime time (much like when Swift first arrived. It’s missing some fairly key features) and we are perhaps a version or 2 away from it realistically being an option for being used to build a complete app. There is no denying that it is the future and when it works well, it makes building UI a lot easier.

As SwiftUI is the future, I’ve been investigating how teams might migrate their existing architectures across to the new technology. There a number of challenges presented by migrating to SwiftUI we will discuss below. As the title suggests we will be exploring how to use a presenter to control a SwiftUI view. It doesn’t matter which architecture you are using as such, whether it’s VIPER, MVVVM, VIP, MVP etc. As long as the logic and state of the view has been abstracted away from the view itself so it can be properly unit tested.

Example

List Item View

Let’s start by creating an example in SwiftUI. We will create a simple list view to display some news for example. Let’s create a simple list view first of all:

// 1
struct ListItemViewModel: Identifiable {
    let id: Int
    let title: String
    let subTitle: String?
    let image: String
}

// 2
struct ListItemView: View {
    let viewModel: ListItemViewModel
    
    var body: some View {
        HStack() {
            Image(viewModel.image)
            VStack(alignment: .leading) {
                Text(viewModel.title)
                    .font(.headline)
                viewModel.subTitle.map({
                    Text($0)
                        .font(.subheadline)
                })
            }
        }
    }
}

// 3
struct ListItemView_Previews: PreviewProvider {
    static var previews: some View {
        ListItemView(
            viewModel: ListItemViewModel(
                id: 1,
                title: "Test Title",
                subTitle: "Test Subtitle",
                image: "first"
            )
        )
    }
}

This is quite a straight forward view, but let’s step through it.

  1. First of all we define our model for the view. We have an id so that we can conform to Identifiable. This allows SwiftUI to uniquely identify each model in the view and helps with performing things like animation and reordering. We also have a title, optional subTitle and an image string. Hopefully nothing here is too scary.
  2. Now we define the view inself. Views in SwiftUI are simple structs that conform to the View protocol, rather than subclasses of UIView like they used to be in UIKit. Its a simple Hstack with an image view then 2 labels stacked on top of each other. See the screen grab below.
  3. Finally we have the preview code to inject an example model to use in the preview.

SwiftUI ListItemView

List View

Now that we have the items in our list, lets create a simple list view that displays those items.

// 1
struct ContentView: View {
    let listItems: [ListItemViewModel]
 
    var body: some View {
        List(listItems) { item in
            ListItemView(viewModel: item)
        }
    }
}

// 2
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        let items = [ListItemViewModel(id: 1, title: "test", subTitle: "test sub", image: "first"),
                    ListItemViewModel(id: 2, title: "test2", subTitle: "test sub1", image: "first"),
                    ListItemViewModel(id: 3, title: "test3", subTitle: "test sub2", image: "first"),
                    ListItemViewModel(id: 4, title: "test4", subTitle: "test sub3", image: "first"),
                    ListItemViewModel(id: 5, title: "test5", subTitle: "test sub4", image: "first")]
        
        ContentView(listItems: items)
    }
}

Ok so what do we have here:

  1. A simple ContentView who has an array of list item view models and a body. The body lists out the content of our list using the ListItemView we created earlier. Simple
  2. Here we have some test data to show that our list is working. If we preview this view we will see something like this:

SwiftUI ListView

That’s wonderful, however it is not particularly dynamic. This is where a presenter or view model would come in. If we look at the description of MVP or MVVM we will see they have a similar role:

The presenter acts upon the model and the view. It retrieves data from repositories (the model), and formats it for display in the view.

There are further abstractions layers (such as interactors and use cases). However we are less concerned with them in this discussion and more on the relationship between the view and the presenter who is holding the state and logic of the view.

Abstracting the state

So at the moment we have a pretty stateless SwiftUI view that simply displays a hardcoded list. Now let’s attempt to abstract the list items away into another object that is injected into the view. This object would be responsible for fetching our items and loading them for the view.

This is where an ObservableObject comes in.

When your type conforms to ObservableObject, you are creating a new source of truth and teaching SwiftUI how to react to changes. In other words, you are defining the data that a view needs to render its UI and perform its logic. SwiftUI uses this dependency to automatically keep your view consistent and show the correct representation of your data. We like to think of ObservableObject as your data dependency surface. This is the part of your model that exposes data to your view, but it’s not necessarily the full model.

So lets update our example to move our list of items into a separate class that conforms to this protocol.

final class ListPresenter: ObservableObject {
    @Published var listItems: [ListItemViewModel] = []
}


struct ContentView: View {
    @ObservedObject private var presenter: ListPresenter
    
    init(presenter: ListPresenter) {
        self.presenter = presenter
    }
 
    var body: some View {
        List(presenter.listItems) { item in
            ListItemView(viewModel: item)
        }
    }
}

The @Published property wrapper here works with the ObservableObject to notify SwiftUI whenever the value changes in order to trigger an update.

We also see the @ObservedObject property wrapper. This causes this view to subscribe to the ObservableObject that’s assigned to it, and invalidates the view any time the object updates.

This is great and allows us to inject an object from outside of the view who can manage the fetching and supplying to the view, whenever the listItems var updates, the view will automatically update!

De-coupling dependencies

Now there is one problem here, can you see it? This view has a dependency between the view itself and the presenter class. Now if you are following the SOLID principles for example, and like to separate dependencies between your classes and layers we will need to remove the dependency between the view and presenter.

To do this lets change the ListPresenter class to be a protocol instead:

final class ListPresenterImp: ListPresenter {
    @Published var listItems: [ListItemViewModel] = []
}

protocol ListPresenter: ObservableObject {
    @Published var listItems: [ListItemViewModel] { get }
}

struct ContentView: View {
    @ObservedObject private var presenter: ListPresenter
    
    init(presenter: ListPresenter) {
        self.presenter = presenter
    }
 
    var body: some View {
        List(presenter.listItems) { item in
            ListItemView(viewModel: item)
        }
    }
}

This looks like it should be a straight forward change… Wrong! You will now start seeing errors galore. The primary cause coming from the decleration of our new protocol:

Property ‘listItems’ declared inside a protocol cannot have a wrapper

The problem here being exactly as the error states. We cannot use property wrappers inside protocols! That is going to cause a bit of a problem as we now can’t make use of the nice integration with SwiftUI via @Published properties, or so it seems…

Let’s take a step back for a moment, what exactly does the @Published property wrapper actually do? The @Published property wrapper essentially provides a publisher that the SwiftUI system can subscribe to in order to listen to updates to the value. This is in fact an implementation detail. One of the key points of protocol oriented programming is to abstract the implementation of functions are variables away from the dependency so that it is unaware of the inner workings. By trying to apply a property wrapper to the protocol we are trying to enforce how that variable should implemented under the hood. When infact should the implementing class of our protocol wish to, they could create their own custom implementation of the wrapper.

Fixing the errors

Ok so let’s start by removing the @Published property wrapper from our protocol:

final class ListPresenterImp: ListPresenter {
    @Published var listItems: [ListItemViewModel] = []
}

protocol ListPresenter: ObservableObject {
    var listItems: [ListItemViewModel] { get }
}

struct ContentView: View {
    @ObservedObject private var presenter: ListPresenter
    
    init(presenter: ListPresenter) {
        self.presenter = presenter
    }
 
    var body: some View {
        List(presenter.listItems) { item in
            ListItemView(viewModel: item)
        }
    }
}

Great! However there are now a bunch of different errors occuring… The key one that we need to pay attention to is this one:

Protocol ‘ListPresenter’ can only be used as a generic constraint because it has Self or associated type requirements

Right, so we have solved the riddle of @Published but this has now surfaced another problem. In order for our ListPresenter protocol to be compatible with the ObervedObject property wrapper in the view, it must extend ObservableObject. Now the problem here is that the ObservableObject uses an associatedtype. Which means if we wish to use it or hold a reference to it we must do type erasure (for more info read my previous post on type erasure) or use a generic constraint.

The simplest solution is for us to use a generic constraint on the view. View the code below:

final class ListPresenterImp: ListPresenter {
    @Published var listItems: [ListItemViewModel] = []
}

protocol ListPresenter: ObservableObject {
    var listItems: [ListItemViewModel] { get }
}

struct ContentView<T>: View where T: ListPresenter {
    @ObservedObject private var presenter: T
    
    init(presenter: T) {
        self.presenter = presenter
    }
 
    var body: some View {
        List(presenter.listItems) { item in
            ListItemView(viewModel: item)
        }
    }
}

So what has changed here. You will now notice that we have added a generic type T to our view. We have also added a generic constraint when implementing the View protocol which signals that the init and body implementations here are only when type T is a ListPresenter. Now in this instance that works fine as we only intend to use this view with our ListPresenter class. This removes the errors and the code now compiles. Let’s update the code and run a little test to make sure we are still getting all the reactive goodness of SwiftUI.

final class ListPresenterImp: ListPresenter {
    @Published var listItems: [ListItemViewModel] = []
    
    init() {
        Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false) { (timer) in
            let items = [ListItemViewModel(id: 1, title: "test", subTitle: "test sub", image: "first"),
                        ListItemViewModel(id: 2, title: "test2", subTitle: "test sub1", image: "first")]
            
            self.listItems = items
        }
    }
}

protocol ListPresenter: ObservableObject {
    var listItems: [ListItemViewModel] { get }
}

struct ContentView<T>: View where T: ListPresenter {
    @ObservedObject private var presenter: T
    
    init(presenter: T) {
        self.presenter = presenter
    }
 
    var body: some View {
        List(presenter.listItems) { item in
            ListItemView(viewModel: item)
        }
    }
}

We have updated our list presenter implementation class to update our list items after 5 seconds. Nice and easy. If we initialise our view with a presenter with 5 items as below, then after 5 seconds our list should reduce to the 2 items as set in the timer.

let items = [ListItemViewModel(id: 1, title: "test", subTitle: "test sub", image: "first"),
                    ListItemViewModel(id: 2, title: "test2", subTitle: "test sub1", image: "first"),
                    ListItemViewModel(id: 3, title: "test3", subTitle: "test sub2", image: "first"),
                    ListItemViewModel(id: 4, title: "test4", subTitle: "test sub3", image: "first"),
                    ListItemViewModel(id: 5, title: "test5", subTitle: "test sub4", image: "first")]
        
let presenter = ListPresenterImp()
presenter.listItems = items
let contentView = ContentView(presenter: presenter)

Now let’s run this as part of an app and see what happens:

Presenter Demo

So as you can see, after 5 seconds the list of items is reduced after 5 seconds to 2 items, proving that our implementation works and we are still able to hook into the nice secret sauce that combine and swiftUI expose to us to allow us to update our views. I’ve seen some rather crazy implementations and workarounds on Stack Overflow. Hopefully this implementation is a little nicer!

Download the sample project to run it for yourself (Xcode 12.4)

Read More

Repository Pattern in Swift

Background

All apps developed require data of some description. This data is stored somewhere, could be on the device itself, in a remote database/service or a combination. Let’s take a look at the most common sources of data:

Each of these methods saves data in a different format. Now I’m sure you will have used at least one of these methods in your apps at some point to retrieve / save data.

When not using the repository pattern it is quite common to access and use these elements directly, either in your ViewController or in some other part of your app depending how it is structured.

The problem

What’s the problem with this approach? Your app becomes difficult to maintain. Now if you only have a small app with a few screens then this isn’t much of a problem as there are only a few elements to change.

However, what if you are working on a large app with several developers and lots of code? You could have NSManagedObjects or Codable objects littered throughout the codebase for example. What happens if you wish to remove Core Data? Perhaps move to realm? You would need to modify all of the classes in your codebase where you had used your Core Data objects.

Similarly, if you are using Codable objects directly from your JSON response. What happens when your backend team changes the API or you switch to a different API provider? The structure of the data may change which means your Codable objects might change. Again you will need to modify a large number of classes if you are working on a large app.

We can also apply this to the other options such as accessing data from 3rd party frameworks. If we use the objects returned from the framework directly, they will all need changing if we change provider or the SDK changes.

There is also the question of query language. Web services use headers and URLQueryItem, Core Data uses Predicates and so on. Every entry point to query the data must know and understand the underlying query language in order to get the information it once. Again, if this changes we need change every query point to the new format.

Let’s have a look at the diagram below:

Core Data Example

Here we have an app structure that is making use of Core Data. There is an object that is being used to access the stack that returns some data. Let’s say for this example that it is news articles. These new articles must inherit from NSManagedObject to be used in Core Data. Now if our data layer is returning NSManagedObjects to the rest of our app structure we now have a dependency between Core Data and the rest of the files in our app. If we wish to move to Realm for example, or switch to using some other form of data store we would need to modify all the of files in the app. The app in this example is only small, imagine having to do that for a much bigger app!

Domain Objects and the Repository

This is where Domain Objects come in. Domain Objects are value objects that are defined by your application. Rather than using objects and structures defined outside of the app, we define what we want the objects to look like. It’s then up to the repository to map between the data storage object / structure to these value objects.

When we do this, it means any changes to the data access layer, as we discussed earlier such as data structure changes or changes in provider don’t impact the rest of the app. The only part of the app that needs to be updated is the repository and it’s mapping to the domain objects.

The below quote summarises the idea of the pattern:

Repositories are classes or components that encapsulate the logic required to access data sources. They centralize common data access functionality, providing better maintainability and decoupling the infrastructure or technology used to access databases from the domain model layer.

Let’s have a look at our previous example but modified to use the a repository and domain objects:

Core Data Example 2

So what is the difference here? As you can see the Core Data stack is still returning NSManagedObjects, however the repository is converting that to a domain object. This object doesn’t inherit from NSManagedObject, also it’s structure and attributes are defined by the app rather than what is in the data store.

Now if we wanted to move away from Core Data to something else the only classes that need to be changed are the Core Data stack and the repository. The rest of the app does not need to be changed as we can map the new data stores type to our domain objects using the repository.

Example

To show a small working example we are going to use a couple of Free Public APIs (highly recommend this resource if you are looking to build a demo app or experiment). We will use 2 APIs that returns users. However they return them in a different format.

https://jsonplaceholder.typicode.com/users/1

https://randomuser.me/api/

As we have done in previous blog posts we are going to use QuickType to generate our Codable objects from our JSON response. We will start with our first request.

// MARK: - User
struct User: Codable {
    let id: Int
    let name, username, email: String
    let address: Address
    let phone, website: String
    let company: Company
}

// MARK: - Address
struct Address: Codable {
    let street, suite, city, zipcode: String
    let geo: Geo
}

// MARK: - Geo
struct Geo: Codable {
    let lat, lng: String
}

// MARK: - Company
struct Company: Codable {
    let name, catchPhrase, bs: String
}

This structure will allow us to decode the response from the first request. Let’s make a simple example that takes the response and outputs some data. We will be using code from our Simple JSON Decoder to process the output so feel free to read up if the code you see doesn’t make sense.

let url = URL(string: "https://jsonplaceholder.typicode.com/users/1")!
// 1
let task = URLSession.shared.dataTask(with: url, completionHandler: { (user: User?, response, error) in
	// 2
    if let error = error {
        print(error.localizedDescription)
        return
    }

    // 3
    if let user = user {
        print(user.name)
        print(user.address.street)
        print(user.address.city)
        print(user.address.zipcode)
        print(user.address.geo.lat)
        print(user.address.geo.lng)
    }
})
task.resume()

So let’s step through what’s happening here:

  1. First of all we are making the request using our Simple JSON Decoder to return our new User type.
  2. Output any errors
  3. So here we are outputting the name, address and location of the user we get back. Super simple right now.

Managing change

Now let’s say we change provider. Maybe our backend team changes the API, or we switch data provider or from 2 different data provider SDKs. In our example we will switch from the first url (https://jsonplaceholder.typicode.com/users/1) to the second (https://randomuser.me/api/).

The first thing we will need to do is change all of our codable objects as the structure of the response is different. Let’s use QuickType again to give us the new structure:

// MARK: - Users
struct Users: Codable {
    let results: [Result]
    let info: Info
}

// MARK: - Info
struct Info: Codable {
    let seed: String
    let results, page: Int
    let version: String
}

// MARK: - Result
struct Result: Codable {
    let gender: String
    let name: Name
    let location: Location
    let email: String
    let login: Login
    let dob, registered: Dob
    let phone, cell: String
    let id: ID
    let picture: Picture
    let nat: String
}

// MARK: - Dob
struct Dob: Codable {
    let date: String
    let age: Int
}

// MARK: - ID
struct ID: Codable {
    let name: String
    let value: String?
}

// MARK: - Location
struct Location: Codable {
    let street: Street
    let city, state, country: String
    let postcode: Int
    let coordinates: Coordinates
    let timezone: Timezone
}

// MARK: - Coordinates
struct Coordinates: Codable {
    let latitude, longitude: String
}

// MARK: - Street
struct Street: Codable {
    let number: Int
    let name: String
}

// MARK: - Timezone
struct Timezone: Codable {
    let offset, timezoneDescription: String

    enum CodingKeys: String, CodingKey {
        case offset
        case timezoneDescription = "description"
    }
}

// MARK: - Login
struct Login: Codable {
    let uuid, username, password, salt: String
    let md5, sha1, sha256: String
}

// MARK: - Name
struct Name: Codable {
    let title, first, last: String
}

// MARK: - Picture
struct Picture: Codable {
    let large, medium, thumbnail: String
}

Now this is more complicated that it needs to be for our example but I’m leaving it here as an extreme example of how different things can be. As you can probably tell the structure and types have change dramatically from our first example. So let’s try and output the same data from this example in our previous example. We can ignore the request part and just focus on the data output so we can see the differences:

// Request 1 output
if let user = user {
    print(user.name)
    print(user.address.street)
    print(user.address.city)
    print(user.address.zipcode)
    print(user.address.geo.lat)
    print(user.address.geo.lng)
}


// Request 2 output
if let user = users?.results.first {
    print("\(user.name.first) \(user.name.last)")
    print(user.location.street.name)
    print(user.location.city)
    print(user.location.postcode)
    print(user.location.coordinates.latitude)
    print(user.location.coordinates.longitude)
}

As you can see from even this simple example. We would have to change 7 lines of code, just to produce the same output. Now imagine this change happening on a much bigger project! There could possibly be 100s of lines of code that would need updating, all because the API response has changed.

Repository Pattern

Here is where the repository pattern comes in. We can create a user repository that fetches the user and converts it to our domain object. That way we don’t need to update the output.

First thing to do is design our domain object that will represent a User in our system. Now all we are doing in this simple example is outputting a few attributes so let’s design our object with just those attributes as we don’t need the rest.

struct DomainUser {
    let name: String
    let street: String
    let city: String
    let postcode: String
    let latitude: String
    let longitude: String
}

Here we have a nice simple representation of our User object. There is no need to consider any of the other possible attributes returned from the API. We aren’t using them in our application and they will just sit around taking up valuable memory. You will also notice that this object doesn’t conform to Codable or subclass NSManagedObject. This is because DomainObject should not contain any knowledge about how they are stored. That is the responsibility of the repository.

To design our repository we can make use of Generics and Protocols to design a repository we can use for anything, not just our DomainUser. Let take a look:

protocol Repository {
    associatedtype T
    
    func get(id: Int, completionHandler: (T?, Error?) -> Void)
    func list(completionHandler: ([T]?, Error?) -> Void)
    func add(_ item: T, completionHandler: (Error?) -> Void)
    func delete(_ item: T, completionHandler: (Error?) -> Void)
    func edit(_ item: T, completionHandler: (Error?) -> Void)
}

protocol CombineRepository {
    associatedtype T
    
    func get(id: Int) -> AnyPublisher<T, Error>
    func list() -> AnyPublisher<[T], Error>
    func add(_ item: T) -> AnyPublisher<Void, Error>
    func delete(_ item: T) -> AnyPublisher<Void, Error>
    func edit(_ item: T) -> AnyPublisher<Void, Error>
}

Here we have different functions for all of the operations we can do. What you will notice is that none of these functions specify where or how the data is stored. Remember when we talked about different storage options at the beginning? We could implement a repo that talks to an API (like in our example), one that stores things in Core Data or one that writes to UserDefaults. It’s up to the repository that implements the protocol to decide these details, all we care about is that we can load and save the data from somewhere.

See it action

Now we have defined what the repository pattern is, let’s create 2 implementations. One for our first request and one for the second. Both should return domain objects, rather than the type returned from the request.

// 1
enum RepositoryError: Error {
    case notFound
}

struct FirstRequestImp: Repository {
    typealias T = DomainUser
    
    // 2
    func get(id: Int, completionHandler: @escaping (DomainUser?, Error?) -> Void) {
        let url = URL(string: "https://jsonplaceholder.typicode.com/users/1")!
        let task = URLSession.shared.dataTask(with: url, completionHandler: { (user: User?, response, error) in
            if let error = error {
                completionHandler(nil, error)
                return
            }

            guard let user = user else {
                completionHandler(nil, RepositoryError.notFound)
                return
            }
            
            // 3
            let domainUser = DomainUser(
                name: user.name,
                street: user.address.street,
                city: user.address.city,
                postcode: user.address.zipcode,
                latitude: user.address.geo.lat,
                longitude: user.address.geo.lng
            )
            
            completionHandler(domainUser, nil)
        })
        task.resume()
    }
    
     // 4
    func list(completionHandler: @escaping ([DomainUser]?, Error?) -> Void) {}
    func add(_ item: DomainUser, completionHandler: @escaping (Error?) -> Void) {}
    func delete(_ item: DomainUser, completionHandler: @escaping (Error?) -> Void) {}
    func edit(_ item: DomainUser, completionHandler: @escaping (Error?) -> Void) {}
}

struct SecondRequestImp: Repository {
    typealias T = DomainUser
    
    func get(id: Int, completionHandler: @escaping (DomainUser?, Error?) -> Void) {
        let url = URL(string: "https://randomuser.me/api/")!
        let task = URLSession.shared.dataTask(with: url, completionHandler: { (users: Users?, response, error) in
            if let error = error {
                completionHandler(nil, error)
                return
            }

            guard let user = users?.results.first else {
                completionHandler(nil, RepositoryError.notFound)
                return
            }
            
            // 5
            let domainUser = DomainUser(
                name: "\(user.name.first) \(user.name.last)",
                street: user.location.street.name,
                city: user.location.city,
                postcode: "\(user.location.postcode)",
                latitude: user.location.coordinates.latitude,
                longitude: user.location.coordinates.longitude
            )
            
            completionHandler(domainUser, nil)
        })
        task.resume()
    }
    
    func list(completionHandler: @escaping ([DomainUser]?, Error?) -> Void) {}
    func add(_ item: DomainUser, completionHandler: @escaping (Error?) -> Void) {}
    func delete(_ item: DomainUser, completionHandler: @escaping (Error?) -> Void) {}
    func edit(_ item: DomainUser, completionHandler: @escaping (Error?) -> Void) {}
}

There’s quite a bit of code here so let’s step through it.

  1. First of all we have defined a new error to send back if we don’t receive any user info from the API.
  2. This is the same call we made in our example before.
  3. Now here we are taking the returned Codable User and converting it to your new DomainUser class.
  4. We aren’t implementing the other functions in this example so just leaving them empty for now to remove errors.
  5. This struct is the second request we are making, and again here we are mapping our Users Codable type from the second request to our DomainUser.

Now that we have made our two repositories, let’s show how we can quickly switch between them without breaking / changing code.

let repository: FirstRequestImp = FirstRequestImp()
repository.get(id: 1) { (user, error) in
    if let error = error {
        print(error)
    }
    
    if let user = user {
        print(user.name)
        print(user.street)
        print(user.city)
        print(user.postcode)
        print(user.latitude)
        print(user.longitude)
    }
}

Here is our example from earlier in the article but updated to use our new repositories. Here we go and fetch the user and print their details, the same as before. Now below we can switch to our second request and see how that will work.

let repository: SecondRequestImp = SecondRequestImp()
repository.get(id: 1) { (user, error) in
    if let error = error {
        print(error)
    }
    
    if let user = user {
        print(user.name)
        print(user.street)
        print(user.city)
        print(user.postcode)
        print(user.latitude)
        print(user.longitude)
    }
}

Now notice how the only part we changed was the implementation class? The rest of the code remained the same even though where the data was coming from has changed and is coming back in a completely different structure. Now imagine we are using this repo in many places to fetch user details. We can quickly switch between different data sources without changing the code that uses it. The only changes we have to make are to the repo and to the data mapping code. So only one change rather than a change in every single class that uses these objects.

Conclusion

So let’s recap what we have discussed here:

  • First of all we discussed the problem of using data storage classes throughout your codebase. Especially on large projects if you need to switch data source / structure.
  • We then discussed how using the repository pattern and mapping to domain objects rather than using data storage classes can make your code easier to change in the future.
  • We worked through some examples of how changing API structures can impact your code.
  • We then implemented a basic repository pattern with mapping to domain objects to show how doing this can make updating your project easier.

Finally let’s discuss the pros and cons of the approach:

Advantages

  • Code is easier to change if you need to switch data source or structure
  • Separates concerns of where / how data is stored away from the rest of your app

Disadvantages

  • Adds more code and complexity
  • Need to write mappers for each object to domain objects
  • Not really needed on smaller solo projects

Feel free to download the playground and play around with the examples yourself

Read More