DEV Community

tareksalem
tareksalem

Posted on

Typescript FP Dependency Injection Is Easy!

Doing Dependency injection using a framework or library is something easy to do, there some frameworks supports dependency injection out of the box either for frontend or backend, for example Nestjs supports dependency injection for backend development, and angular supports it with the same pattern for frontend development, and there are many libraries help you to do dependency injection.
But with all these packages and libraries they support a one pattern for dependency injection which is using classes, I think there is a clear reason behind that, dependency injection was driven from OOP programming languages like Java or C# and came to typescript/javascript with a similar syntax, and we as a js/ts developers didn't notice that Javascript doesn't support only one paradigm, it supports many paradigms, it supports OOP and Functional Programming!

The Story started when I joined a company was using FP like paradigm for building all backend functionalities using functions, but the code had a very clear problem, "The Code doesn't do dependency injection"
That's why I said it uses FP like because it doesn't follow all functional programming principles, the developers where just writing functions and use their dependencies directly inside the functions.
You may ask yourself what is the problem with this pattern? lemme list some problems we faced with this pattern

  • hard to test because to test one function you need to mock all the dependencies it needs, and we were using jest so we had a lot of jest.mock("../filepath") which make things hard to do different mocks for the same dependency
  • the code has side effects because all the functions are impure functions
  • hard to change the code base because if the function depends on another function and we want to replace this function with another one, we need to update all code parts that uses it, which means alot of refactoring just to change one function signature, that's why we need to use Inversion of control principle and dependency injection pattern so we will be able to depend on interfaces and types instead of concrete implementation, so anytime we want to replace the concrete implementation with another function we can change that function only!

Dependency Injection in Javascript/typescript

Doing dependency injection in javascript/typescript can be implemented easily if you are using classes with the help of some libraries like:

  • inversify
  • nestjs
  • typedi

and other libraries, but when it comes to functional programming, there is no many libraries doing that for functions, some people thought the dependency injection is applicable only in OOP, I wanna say that's wrong, it's applicable also in functional programming, there are many ways you can do that, I suggest to you read this article on Medium%20those%20dependencies%20as%20arguments.)
It has very nice information, but for now I will give you some ways you can do the dependency injection in pure javascript without any other dependencies

One of the ways you can do that is using Higher order function, but before giving an example, lets first define what is higher order function?

In a nutshell the higher order function is a function that accepts functions as parameters and returns another function.

In nodejs all of us know the concept of callbacks, basically the function that accepts a callback is considered a higher order function

fs.readFile('input.txt', function (err, data) {
   if (err) return console.error(err);
   console.log(data.toString());
});
Enter fullscreen mode Exit fullscreen mode

as you see, the file system readFile method is a higher order function because it takes a callback as an argument and execute it.

In the same way, the Array functions like map, reduce, forEach and others are higher order functions, they accept a function to be passed as a parameter and execute them.

The other type of higher order function is the function that doesn't accept a function as an argument but it returns a function, you can think about it like a closure, which means a function scoped inside another function, and this is what we need to do dependency injection in our functions.

The concept is very easy, in OOP we have a class like this

class SomeClass {
  constructor() {}

  getUser(userId: string) {
    this.httpService.get('pathHere')
  }
}
Enter fullscreen mode Exit fullscreen mode

we have getUser function, this function depends on something called httpService and the way to pass this service to the class is to inject it inside the constructor, so we can do something like this:

class SomeClass {
  constructor(httpService: HttpService) {
    this.httpService = httpService;
  }

  getUser(userId: string) {
    this.httpService.get('pathHere')
  }
}
Enter fullscreen mode Exit fullscreen mode

In that way you can thing about the higher order function as a constructor in OOP, it knows how to return the inner function and knows what dependencies it needs so basically you can do something like this:

function getUser(httpService: HttpService) {
  return async (userId: string) => {
    const result = await httpService.get('pathHere')
  }
}
Enter fullscreen mode Exit fullscreen mode

Now you are doing higher order function, and you also do dependency injection, so anytime you want to consume getUser function you can pass the dependency it needs and it returns a function you can execute it, so for example you consume it like this:

async function main() {
  const httpService = new HttpService();
  const user = await getUser(httpService)("1234")
}
Enter fullscreen mode Exit fullscreen mode

We created an instance from the httpService and passing it to getUser higher order function and as you know, it returns another function so we pass the userId to that function.This is pretty awesome, because now you can test getUser function in an isolated way without knowing anything about httpService, so you could have unit testing like this:

describe('getUser Test Suite', () => {
   const mockedHttpService = {
     get: jest.fn()
   }
   const getUserFunc = getUser(mockedHttpService)
  it("testing getUser", async () => {
     mockedHttpService.get.mockReturnValue({ data: { userId: "1234" } })
     const user = getUserFunc("1234")
  });
});
Enter fullscreen mode Exit fullscreen mode

You can pass anything in place of the httpService, and you can control how it acts without knowing anything about the concrete implementation of it, and you don't need to use jest.mock('modulePath') to mock specific module!

Is that enough to do dependency injection in real projects?

In real projects this pattern is good but it has one downside, which is passing the dependencies from one function to another. For example suppose getUser function is called from another function, now you need to inject the needed dependencies to that function and inject them in the outer function until you reach the project entrypoint, you will have something like this:

A function that fetches the user from an http service based on the username

// getUser.ts
function getUser(httpService: HttpService) {
  return async (username: string) => {
    const result = await httpService.get('pathHere')
  }
}
Enter fullscreen mode Exit fullscreen mode

That function is called inside another function that says the user exist or not

// checkUserExists.ts
function checkUserExists(httpService: HttpService) {
  return async (username: string) => {
    const user = await getUser(httpService)(username)
    return user ? true : false;
  }
}
Enter fullscreen mode Exit fullscreen mode

and that function is used inside createNewUser function that checks the user exist or not before creating it

// createNewUser.ts
function createNewUser(httpService: HttpService) {
  return async ({username}: {username: string}) => {
    const userExists = await checkUserExists(httpService)(username) 
    (username);
    if (userExists) {
      throw new Error("Duplicate user")
    }
   // create the user here
  }
}
Enter fullscreen mode Exit fullscreen mode
function main() {
  const httpService = await new HttpService();
  await createUser(httpService)({ username: 'John' })
}
Enter fullscreen mode Exit fullscreen mode

You may observed you need to pass the dependencies from the outer level to the inner level, this is may work in small scale, but it will be very hard to manage the flow of dependencies in large scale. also you need to know if the httpService is instantiated before or not, should be instantiated as singleton or transit

What to do to manage the dependencies?

There are two ways to do that:

  • compile time
  • runtime

The compile time dependency injection is used to manage the dependencies and pass all the needed dependencies before the program run, this is commonly used in compiled languages like golang.

The runtime dependency injection is used to do the injection of the dependencies at runtime, so when the application is instantiated then the dependencies will be started to be resolved and cached somewhere and injected in the functions/classes that depends on it. This pattern requires an important something called DI container you can consider it as a place where you put all the created dependencies inside it and start pick from it to resolve the functions/classes that use it.

InjectX

InjectX is built for dependency injection in functional programming, it's designed for that purpose, it uses the higher order functions paradigm to inject the needed dependencies automatically at runtime.

InjectX has three main components:

  • di container: a pool has all the resolved dependencies
  • InjectIn: a function that used to tell InjectX to inject the needed dependencies inside the higher order function
  • Bind: a function used to bind a dependency to the container.

Install InjectX

npm i injectx
Enter fullscreen mode Exit fullscreen mode

You can also see the full documentation from here

Let's take the same example we had above. You will have a higher order function like this:

// getUser.ts
function GetUser(httpService: HttpService) {
  return async (username: string) => {
    const result = await httpService.get('pathHere')
  }
}
Enter fullscreen mode Exit fullscreen mode

as you can see I just changed getUser to GetUser because each module I will export two functions

// getUser.ts
export function GetUser({httpService}: {httpService: HttpService}) {
  return async (username: string) => {
    const result = await httpService.get('pathHere')
  }
}
export const getUser = InjectIn(GetUser)
Enter fullscreen mode Exit fullscreen mode

Let's explain what we are doing here, I export two functions here, the first one is the higher order function which expects some dependencies to be injected in and this function takes the dependencies as an object. The second exported function which is getUser is the resolved function. InjectIn basically takes the higher order function and passes the needed dependencies to it and returns the inner function of that higher order function with resolving all the needed dependencies, so you can use getUser function anywhere without the need to pass the needed dependencies

so you can have something like this

import { getUser } from './getUser'
async function main() {
  const user = await getUser("username")
}
Enter fullscreen mode Exit fullscreen mode

As you can see, no need to pass httpService dependency to the function directly because it's injected using InjectIn function. Now all you need before calling getUser is to bind the HttpService to the container, and to do that you can use this:

import { GetContainer } from 'injectx';
const httpService = new HttpService();
GetContainer("default").Bind(httpService);
Enter fullscreen mode Exit fullscreen mode

We talked about the container before, it's a place where you put the dependencies inside it and start picking what you need from it right? InjectX allows you to create multiple containers, why multiple containers and not only one container?
Suppose you have multiple modules like the following:

  • orders module
  • catalog module
  • auth module

and you want to separate between the dependencies of each module so your dependencies will be organized, then you can create multiple containers, each container is related to one module and you put that module dependencies inside the related container.
To do that you use GetContainer function which is a function exported from injectX, it asks injectX to get a container with specific name, if the container does not exist, then it will create a new one with the specified name
Then you can access another method called Bind which means you are telling inectX I want to append a dependency to that container so it will be available to the higher order functions.

If you are interested to know more about dependency injection in functional programming you can comment bellow and wait for the next part.

Thank you for reading

Top comments (8)

Collapse
 
robkane1 profile image
Rob Kane

This seems like a lot of drama, simply to avoid mocking the exported httpService. Have I misunderstood the problem you are trying to solve?

If you want to use real functional programming, then you should probably use a real functional programming language that natively supports pipes, streams, pattern matching, comprehensions and all the fun stuff that goes with it. Dependency injection is a workaround that allows you to use composition and polymorphism at runtime in OOP. This isn't (normally) something that you really need to do in FP because it is all just composition. Dependencies are always injected by nature of that composition.

I have probably just misunderstood what you are trying to achieve here.

Collapse
 
tareksalem profile image
tareksalem

Thanks for these information, I wanna mention that I am new to functional programming and just trying to apply it using javascript and go because I see those languages can support multiple paradigms and not just built for specific one. Yes you are right, dependency injection is a way to apply composition and this could be applied using pipes but at the end you still need to group some functions toghether that form a bigger implementation, and grouping them together could be applied using dependency injection. This topic is not just for unit testing, however it's an important thing and helps a lot to do right unit testing, but there are other sides also I found it useful for.
After some searches I found that other functional programming languages use the concept of DI, so it's not limited to OOP design, also it's a way to apply Inversion of control principle and dependency inversion principles which are also not limited to OOP, they could be used in any language.
I want to understand for example if I have the following components:

  • getProductFromCatalog
  • calculateTaxes
  • calculateTotalPrice
  • placeOrder
  • doPayment

and each step from these steps depend on config function to get the needed configurations and depend also on httpService to get the httpService instance, how this will be implemented without dependency injection? with keeping in mind the separation of concern?

Collapse
 
robkane1 profile image
Rob Kane • Edited

Ah, I see. This is probably why you are struggling to grok this a little bit. Whilst Javascript and go support functional programming, they probably aren't the best place to learn. They each have their own ways of doing things.

You are right that you can use dependency injection in any language, but why would you? You can paint a ceiling with a squirrel, but you probably don;t need to. What is the problem that you are trying to solve?

With a more functional language, you would literally start with the initialiser at the beginning of the chain and pass that through the pipe. Each stage of the pipe would be preconfigured with the relevant config through partial application or module configurations to create a concrete runtime implementation to reduce side effects.

Separation of concerns means that each of those stages needs to be responsible for dealing with their own worlds. The client of those modules/methods shouldn't be sticking its nose in and mixing stuff up! It should worry about its own problems.

In Elixir for example, you could probably just have something like this

def make_purchase(product_id) do
  product_id
    |> Products.getProductFromCatalog
    |> Taxes.addTaxToProduct
    |> Orders.generateOrderForProduct
    |> Payments.payForOrder
end
Enter fullscreen mode Exit fullscreen mode

Each of these would deal with handling their service calls and their error handling and can be testing individually.

There are many ways of handling this for testing. For example, you could:

  • Define a default behaviour, which then is overidden in your test module.
  • Read an environment specific service from the environment inside the module
  • use partial application during setup
  • use a mocking library

And so on.

Of course, you could also use optional dependency injection with defaults. Like this:

# inside Products module
def get_product_from_catalog(product_id, product_service \\Services.product_service) do
  product_service.getProduct()
end
Enter fullscreen mode Exit fullscreen mode

And you wouldn't even need to change the shape of your processing stream.

The problem is that you wouldn't do any of this in reality, because you would use a GenServer to send events that are then consumed by these modules, are processed and then fire off other events to the next stage in the journey. You would then fire a success or failure message event back to the client which is subscribed to the events related to the transaction. That is the joy of concurrent, distributed processing. This flow or processing stream wouldn't exist in the first place. You would simply test that given a product id, this function sends an event to the products module. eg Products.send(:buy_product, product_id, transaction_ref). Done. The end.

But the most important thing to ask right at the beginning is - why have you approached this problem with an acceptance criteria of using dependency injection? Instead of trying to force things from elsewhere into a new environment, reframe the thinking to fit the constructs of the language or the programming style.

When in Rome, do as the Romans do!

Thread Thread
 
robkane1 profile image
Rob Kane • Edited

Forgot to add... In a world like JavaScript, it probably makes more sense to just write some classes for this kind of thing. It'll be much easier to read, understand and test. It suits the constructs of the language better. I know classes aren't cool these days, but there is a time and a place for everything!

In Go, you would create an interface for the http service and then implement that interface using a mock service when you call your function in your test suite.

Thread Thread
 
latobibor profile image
András Tóth

I agree with you. JavaScript is neither Object Oriented nor Functional. You can kinda do both of them.

My rule of thumb in JS is:

  • Does it have a state that I need to carefully maintain? Then make it into a class. If you write, let's say, a video game, a perfect candidate is to create a "Protagonist" class that contains its health, abilities, skills, level, experience points, etc. of the protagonist.
  • Is it a "transforming" piece of code that gets an input, does some transformation and then outputs something without saving any state? Then make it into a standalone function. This is how you should handle requests on a backend: for each one of them they do validation, they query something and then they "forget" that the request ever existed.

Also I'm totally fine with writing this kind of code:

dbConnection.getUser({ id: 1234 });

// or
getUser(dbConnection, { id: 1234 });
Enter fullscreen mode Exit fullscreen mode

For me both of them are clear:

  • the first one I like better, I think it's just easier to read/understand
  • if you use TypeScript it can make sure you won't misremember the order getUser({ id: 1234 }, dbConnection)
Thread Thread
 
robkane1 profile image
Rob Kane • Edited

Indeed. Use the right tool for the job. The only thing I would maybe change about that second one:

export const createUserConnection = dbConnection => ({ id: id }) => // do stuff

export const getUser = createUserConnection(dbConnection)

// later 
getUser({ id: 1234 })
Enter fullscreen mode Exit fullscreen mode

Simply because then you don't have to pass the connection in every time you want to fetch a user

Thread Thread
 
tareksalem profile image
tareksalem

This is basically the dependency injection no? You are injecting dbConnection. Can u tell me from where you read dbConnection? From the global scope ? Or injected in outer function?
For both cases, this article is written to solve these troubles

Thread Thread
 
robkane1 profile image
Rob Kane

This is just currying. Where you get the initial argument from is mostly irrelevant. Whilst you could argue that this fits the definition of Dependency Injection, a functional purist (which I am not) would tell you it is not dependency injection, it is simply the only way to do it.

In (true) functional programming, which is based on Lambda Calculus, functions must:

  • Be pure (always return the same result given the same argument)
  • Be unary functions (only take one argument)

Currying (named after Haskell Curry - mathematician) is the way to collect arguments and satisfy to that definition. You can also use partial application where relevant.

If you reference something outside of the outer function scope, it isn't pure, because that thing could change independently of the function and then your results would be different. It has to be passed in during evaluation. Everything needs to be "injected", so the concept of dependency injection is not needed.

I have probably confused things by using Elixir examples, for which I apologise. Because Elixir, does not support currying natively. Why? Because it is a functional language second and a distributed computing language first. It doesn't want you passing around bits of enclosed scope. It wants you to send a message to a different process (actor) with the values it needs to do its thing independently of the process that called it. Which is how it can be distributed across cores and machines. Instead, you can use partial application to provide an evaluated value. Elixir also gives the illusion of currying when using pipes via syntactical sugar and partial collection, but the compiler fixes that afterwards. Without that sugar, pipes would be messy as hell!

Unsurprisingly, in Haskell - everything is curried. You always pass 1 argument at a time and even when you do something like getUser dbConnection userId it might look like you are calling one function, but you are calling 2, in sequence.

In OOP DI needs to be called DI, because it differentiates it from inheritance and module encapsulation. In proper FP, it is simply the only way to do it.