Service Container

@fusion.io/container

Fusion Framework was designed with Dependency Injection (DI) in mind. To archive a seamlessly DI, the heart of Fusion Framework is a service container.

Dependency Injection Introduction

In short, Dependency Injection (DI) is a technique whereby passing or injecting objects (the dependencies/services) to another object via constructor or getter methods. Using DI properly, your application will be easier to test and better code reuse more about DI.

For example, a booking service will find a ticket for an user, mark it as booked and send an email to the user about the ticket.

BookingService.js
class BookingService {

    constructor(ticketStore, emailService) {
        this.ticketStore  = ticketStore;
        this.emailService = emailService;
    }
    
    async book(user) {
        const foundTicket = await this.ticketStore.find();
                
        await this.ticketStore.booked(foundTicket, user);
        
        await this.emailService.send(ticket, user.getEmail());
    }
}

In the above example, we have injected 2 services into the BookService. ticketStore for finding an available ticket, and emailService for sending the ticket to the user.

To use this BookingService, we have to initialize the ticketStore and emailService first:

The trade-off of DI is the complexity of initialization steps. Especially when the services are using in a nested form. For example, the ticketStore might need a DatabaseConnection to perform SQL query and emailService might need an SMPT protocol for sending the email:

To create an instance of BookingService, we first need to know how to create a TicketStore service, which in turn needs to know how to create a DatabaseConnection and so on... In real world application, the source code is even more complicated.

The Service Container of Fusion Framework was created to address this problem.

Service Container has another name called Inversion of Control Container or IoC Container.

Fusion Service Container was inspired by Laravel's Service Container.

Service Container

A service container is a simple map object for holding and getting dependencies/services. To use the Service Container, we need to install it from NPM

Binding basics

To bind a service into Service Container, we'll use the .bind() method:

The .bind() method requires 2 parameters: the dependency key helloService and a function returning an instance of the service - we call it as a Factory Function.

After we bind a service, we can retrieve it from the container by calling the .make() method:

We can also call the .make() method inside the Factory Function. By doing so we can instruct the container how to make a service with nested dependencies easily:

In the above example, we have a fooBarService is using the barService as a dependency. And the barService is using fooService as a dependency for itself.

Singleton binding

If your service was bounded to the to the container via .bind() . Every time we call the .make() method, the container will invoke the Factory Function again to get your service instance.

Sometimes, we only need one instance of service or a singleton, and every time we calling make(), we can get back the same instance. We can archive this by using the singleton() method instead of bind():

Auto binding

Thanks to ES6 Map, the dependency key is not limited to string value, but any value can be used. Even a class

Most of the time, you may find your factory function is doing nothing but 2 steps: making dependencies and creating a new service instance with those dependencies:

So instead of doing this again and again, we support an autoBind() method. The above example can be equivalent to:

Similar to autoBind(), we support autoSingleton().

We also support bind(), singleton() functions as ES7 decorators, so you can shorten the above code to this:

Inversion binding

A best practice to approach DI is binding a Concrete service to the Container with an Abstract key. This will help your source code following the Dependency Inversion Principle. Here is a demonstration about it:

First we'll create an Abstract key which the value was SomeService

We'll create MyService as the Concrete service and bind it into the container with the abstract key SomeService

Now, we instruct the Service Container that MyService is a concrete of the key SomeService

By doing so, the consumer code which using the service as dependecy does not need to be aware about the Concrete service - MyService, but the Abstract key - SomeService.

Now, in the main.js we can have the final result like this:

Method Injection

Sometimes, using constructor injection is overhead. You may find your self using just some dependencies in your class method. We can use the inject() decorator for such situation:

When using the .inject() decorator, the dependency you specified will be passed to your class method as the last parameters.

That's all you need to know about DI with @fusion.io/container for now 👍

@fusion.io/container is having zero dependency and can be used in any javascript project.

Last updated

Was this helpful?