DEV Community

Cover image for How i fixed the circular dependency issue in my Node.js application
Alisson Zampietro
Alisson Zampietro

Posted on • Updated on

How i fixed the circular dependency issue in my Node.js application

I'm going introduce you in a problem that maybe you've been through and some point in your node.js career.
Usually i split my business logic from anything else in my code (let's name it as a service), been my business layer responsible to trigger the resources that are required to make some action. Sometimes, one item in this business layer needs to use another one in the same layer.


Example:

CustomerService requires UserService to create the login credentialsCustomerService requires UserService to create the login credentials

Both services depending each other and in another moment, UserService will call CustomerService to validate Customer profile.


Failure scenario:

File structure

|--/services/CustomerService.js

const UserService = require('./UserService')

class CustomerService{
    create() {
        UserService.create();
        console.log('Create Customer');
    }

    get() {
       return {
          name: 'test'
       }
    }
}

module.exports = new CustomerService;

Enter fullscreen mode Exit fullscreen mode

|--/services/UserService.js

const CustomerService = require('./CustomerService')
class UserService {
    create() {
        console.log('Create user');
    }

    get() {
        let customer = CustomerService.get();
        console.log({customer});
    }
}
module.exports = new UserService;
Enter fullscreen mode Exit fullscreen mode

|--/index.js

const CustomerService = require('./services/CustomerService');
const UserService = require('./services/UserService');

CustomerService.create();
UserService.get();
Enter fullscreen mode Exit fullscreen mode

So, after implementing this code and run node index.js in your terminal, you gonna get the following error:

Error due circular dependency

You may be thinking: WTF??? But this method does exist!!!!

Yes, that was my reaction. You're getting this error due the circular dependency, which happens when you create a dependecy between two modules, it means that we're importing and using UserService inside CustomerService and vice-versa.

How can we solve this? One possible solution.

We're going to load our modules in a centralized file called index.js, so after this, we're going to import only the index.js file and specify the Objects that we need to use.

Centralized module

Hands on:

1 - Create a index.js file inside the services folder (Attention: it's our crucial code snippet):

|--/services/index.js

const fs = require('fs');
const path = require('path');
const basename = path.basename(__filename);
const services = {};

// here we're going to read all files inside _services_ folder. 
fs
    .readdirSync(__dirname)
    .filter(file => {
        return (file.indexOf('.') !== 0) &&
                (file !== basename) &&
                (file.slice(-3) === '.js') &&
                (file.slice(-8) !== '.test.js') &&
                (file !== 'Service.js')
    }).map(file => {
        // we're are going to iterate over the files name array that we got, import them and build an object with it
        const service = require(path.join(__dirname,file));
        services[service.constructor.name] = service;
    })

    // this functionality inject all modules inside each service, this way, if you want to call some other service, you just call it through the _this.service.ServiceClassName_.
    Object.keys(services).forEach(serviceName => {
        if(services[serviceName].associate) {
            services[serviceName].associate(services);
        }
    })

module.exports = services;
Enter fullscreen mode Exit fullscreen mode

2 - Let's create a Parent class that gonna be inherited by each service that we create.

|--/services/Service.js

class Service {
    associate(services) {
        this.services = services;
    }
}

module.exports = Service;
Enter fullscreen mode Exit fullscreen mode

3 - Let's rewrite ou code to check how it's gonna be.

|--/services/CustomerService.js

const Service = require('./Service')
class CustomerService extends Service{
    create() {
        this.services.UserService.create();
        console.log('Create Customer');
    }
    get() {
       return {
          name: 'test'
       }
    }
}

module.exports = new CustomerService;
Enter fullscreen mode Exit fullscreen mode

|--/services/UserService.js

// now you only import the Service.js
const Service = require('./Service.js')
class UserService extends Service{
    create() {
        console.log('Create user');
    }

    get() {
        // now we call the service the we want this way
        let customer = this.services.CustomerService.get();
        console.log({customer});
    }
}
module.exports = new UserService;
Enter fullscreen mode Exit fullscreen mode

4 - now., let's see how we're going to call the services outside the services folder.

// now we only import the index.js (when you don't set the name of the file that you're intending to import, automatically it imports index.js)
const {CustomerService, UserService} = require('./services/')
// and we call this normally
CustomerService.create();
UserService.get();
Enter fullscreen mode Exit fullscreen mode

PROS:

  • Now it's easier to load another service, without getting any circular dependecy error.
  • if you need to use a service inside other service, you just have to call the property this.service.NameOfTheService.

CONS:

  • It's not trackeable anymore by your IDE or Code Editor, as the import handler is inside our code and it's not straight in the module that you want to use anymore.
  • A small loss of performance due the loading of all modules, although some of them are not used.

If you think that there's something confusing, or impacting the understanding, or that i can improve, please i'm going to appreciate your feedback.

See you guys and thanks a lot

Top comments (3)

Collapse
 
castar2001kr profile image
castar2001kr

you forget the get() method atcustomerservice...

Collapse
 
iamshouvikmitra profile image
Shouvik Mitra

Good one there.
But, I would rather prefer using events in Node.js.

Collapse
 
alissonzampietro profile image
Alisson Zampietro

Hey man! Thank you for your feedback.
How would you implement events in this situation?