Dependency Injection Simplified
Tags: ios, swift, programming, functional
Published: 2023-02-09 22:58:15


![](https://cdn-images-1.medium.com/max/2000/1*TdawTrxyveg5NSE9UJZ_IA.png)

In our last session, we have talked about Functional ways to do error handling and looked at how this is a better way to handle errors. To achieve this we introduced some new Data Types like **Optional, Try, Either**. These are Functional Data Types and they helped to solve Exception/ Error handling issues. Today we will talk about simplifying DI in our app.

### What is Dependency Injection?

DI is a concept which makes a class independent of its dependency management. It achieves that by decoupling the usage of an object from its creation. This helps you to follow SOLID’s dependency inversion and single responsibility principles.
To achieve this in Swift one of the most appreciated library is [Swinject](https://github.com/Swinject/Swinject). This library not only provides DI but also have many other features like:-

* Constructor/ Property/ Method Injection

* Object Scopes as None (Transient), Graph, Container (Singleton) and Hierarchy

* Container Hierarchy

* Modular Components

* Thread Safety (but the container is not thread-safe)

So we can see that this framework like many other frameworks is fully loaded. Below is a sample on how to use this library:-

    // 1. Creating a IOC container & Object registeration
    // This container is responsible to hold the logic to
    // create an object and maintain its lifecycle
     
    let container = Container()
    container.register(Animal.self) { _ in Cat(name: “Mimi”) }
    container.register(Person.self) { r in
     PetOwner(pet: r.resolve(Animal.self)!)
    }

    //2. To use the registered object we can resolve the instances
    // using the same instance of container

    let person = container.resolve(Person.self)!
    person.play() // prints “I’m playing with Mimi.”

With all this boiler-plates it makes sense in a complex project where one finds the usability of all or most of the features. But for starter or simpler project we can look into some other approaches too. Let’s see what Functional concepts has to offer to solve the above problem of DI.

### Reader Monads

A Reader, sometimes called the environment monad, treats functions as values in a context. Loosely speaking, it allows you to build a computation that is a function of some context (configuration, session, database connection, etc.), rather than passing the context as an argument to the function.
Reader Monad differs the execution of a function to a moment when we can provide a proper execution context for it. 
A simpler implementation of Reader Monad can be like this:-

    struct Reader<E, A> {
     
     let f: (E) -> A

    static func unit<E, A>(_ a: A) -> Reader<E, A> {
     return Reader<E, A>{_ in a}
     }

    func run(_ e: E) -> A {
     return f(e)
     }

    func map<B>(_ g: [@escaping](http://twitter.com/escaping) (A) -> B) -> Reader<E, B> {
     return Reader<E, B> { e in g(self.run(e)) }
     } 
     
     func flatMap<B>(_ g: [@escaping](http://twitter.com/escaping) (A) -> Reader<E, B>) -> Reader<E, B {
     return Reader<E, B> { e in g(self.run(e)).run(e) } 
     }
    }

With this implementation we can clearly see that Reader is nothing but a wrapper for a function **f: (E) -> A** where **E is the input environment** and **A is the output** expected. Let’s take a practical example to see how Reader Monad can simplify the Dependency Injection.

### Understanding By Example

Let’s look at examples to solve the dependency problem. This example will be injecting a dependency on getting User objects from a repository:

Let’s first define a User Repository which will help to get and find a user:-

    struct UserRepository {
     func get(id: Int) -> User { // get user based on ID }
     func find(username: String) -> User { //Find User based on name }
    }

Now there is a **UserInfo** class which uses the **UserRepository** to fetch specific information related to a user. 
Let’s see how to implement and fulfil the dependency first using our classic way of DI with DI framework like Swinject and later compare it with Reader monad implementation.

### User Info using DI Framework like Swinject

    // Defining UserInfo
    struct UserInfo {
     let userRepo: UserRepository

    init(userRepo: UserRepository) {
     self.userRepo = userRepo
     }

    func getEmailID(withUserId userId: String) -> String {
     return self.userRepo.get(id: userId).email
     }

    func getBossEmailId(forUserName userName: String) -> String {
     // get the currect user
     let user = self.userRepo.find(userName: userName)
     let boss = self.userRepo.get(id: user.supervisorId)

    // return the result
     boss.email
     }
    }

    // Now we need to register User Info before using it
    let container = Container()
    container.register(UserRepository.self) { _ in UserRepository() }
    container.register(UserInfo.self) { r in
     UserInfo(userRepo: r.resolve(UserRepository.self)!)
    }

    // Now using UserInfo
    struct Application {
     let userInfo: UserInfo = container.resolve(UserInfo.self)!

    func sendEmail(forUserId id: String, userName name: String){
     // get user emailID
     let userEmail = userInfo.getEmailID(withUserId: id)
     let bossEmail = userInfo.getBossEmailId(forUserName: name)
     self.sendEmail(to: [userEmail, bossEmail])
    }
    }

In the above example, we can clearly note a few points:-

* There is a lot of boiler-plate to register and resolve dependency

* Application class has no clue what dependency UserInfo class is using

* DI is hidden in this way

* Application struct cannot change the source of getting User data as that is not under its control anymore

Now let’s see how the same can be achieved using **Reader Monad**

    // Defining UserInfo
    struct UserInfo {

    func getEmailID(withUserId userId: String) -> Reader<UserRepository, String> {
     return Reader<UserRepository, String>.unit(self.userRepo.get(id: userId).email)
     }

    func getBossEmailId(forUserName userName: String) -> Reader<UserRepository, String> {
     // get the currect user
     let user = self.userRepo.find(userName: userName)
     let boss = self.userRepo.get(id: user.supervisorId)

    // return the result
     Reader<UserRepository, String>.unit(boss.email)
     }
    }

    // Now using UserInfo
    struct Application {
     func sendEmail(forUserId id: String, userName name: String){
     let userRepo = UserRepository()
     // get user emailID
     let userEmail = userInfo.getEmailID(withUserId: id).run(userRepo)
     let bossEmail = userInfo.getBossEmailId(forUserName: name).run(userRepo)
     self.sendEmail(to: [userEmail, bossEmail])
    }
    }

So let’s compare the above implementation of Reader Monad:-

* There is less boiler-plate now

* Since Reader is a Monad it gets composition for free

* Effects can be tracked as the dependency is now no more hidden and it’s explicit

* Application struct can now inject Mock implementation also and this can ease-out the unit testing

### But I can’t choose!

What to choose is a Big question. There is no rule or guidelines which states what to choose Reader Monad or IOC Containers like Swinject for Dependency Injection. But seeing the strength and weakness of both the approaches we can use the Reader Monad throughout our application’s core and IOC Containers at the outer edges. 
This way we get the benefit of the IOC Containers but we only have to apply it to the boundaries. The Reader Monad lets us push the injection out to the edges of our application where it belongs.

In our next session, we will see more such scenarios to simplify our applications. Stay tuned till then!!!