DEV Community

Cover image for Javascript: # How to Solve Async IoC Paradox
Zied Hamdi
Zied Hamdi

Posted on

1

Javascript: # How to Solve Async IoC Paradox

How to Solve the IoC Infinite Dependency Paradox in JS and TS

In the world of advanced software development, managing dependencies efficiently is crucial. This often involves using Inversion of Control (IoC) containers like Awilix to handle the instantiation and management of objects. While IoC containers are powerful tools for dependency management, one common challenge is ensuring that certain objects, like your IoC container itself, remain singletons, meaning that there is only one instance throughout your application's lifecycle.

The IoC Paradox

Imagine you're building a complex application using an IoC container to manage dependencies, and you need to create an IoC container with various registrations for your services and components. Ideally, you want to create this container only once and use it consistently throughout your application to ensure that the same instances are injected wherever needed. However, there's an issue: you need to initialize the container asynchronously, as many operations like database connection pool is started as asynchronous operations.

The paradox is that you cannot use IOC for that.

The Wider Problem

The IoC paradox is not limited to Awilix; it extends to many IoC libraries. The challenge lies in handling asynchronous calls during registration or resolving of dependencies. Some libraries may not provide straightforward solutions for this, leaving developers to devise their own strategies.

The Initial Solutions

Solution 1: Export the Container

One approach is to export the container instance from the module where it's created and import it wherever needed. However, this will still require you to call the container initializer async method somewhere in your app, and nothing will prevent other developers from calling it again.

Solution 2: Initialize then container and Store in global

Another approach is to initialize the container only once and put it in the global context. While this centralizes container management, it still requires an initial call somewhere in an invasive place in your program to initialize the container. Then storing the value in the global scope is a bad practice for many reasons that I will not detail here. But just think about anyone overriding your instance for example.

Solution 3: Use Dependency Injection

Dependency injection can be employed to pass the container to components and services where it's needed. While this approach maintains the benefits of IOC, it requires an initialization outside the original container file, as it has to be called in async mode (which is not supported by the IOC containers). So we are getting back to the polluting place where we do routines that have to be done before anything relying on the container can load.

An Elegant Solution: The Singleton Pattern

To address these issues, we introduce an elegant solution: the Singleton Pattern for IoC containers. This pattern ensures that the container is created and initialized only once while providing a clean and efficient way to access it without manual storage.


typescript
import { asFunction, asValue, createContainer } from "ioc-library";

export class IoCContainerSingleton {
  private static container: IoCContainer<object>;

  private constructor() {
    // Private constructor to prevent external instantiation
  }

  public static async getInstance(): Promise<IoCContainer<object>> {
    if (IoCContainerSingleton.container) return this.container;

    // Create and initialize the container here...
    // Add your registrations and initialization logic

    return this.container;
  }
}

> Once this is done, it makes no sense to store the singleton in the container as you need an instance of the initialized container to be able to resolve the dependencies in it, and to access that you will anyway need to call this singleton's getInstance() method.

From this point, you will need a singletong implementation for each environment you'd like to run in. eg. test and production. But all the code is isolated in a dedicated file that has only this mission.

Enter fullscreen mode Exit fullscreen mode

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (0)

Speedy emails, satisfied customers

Postmark Image

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay