Skip to the content.

Locator

The core/locator component is a service locator. This component is central to the core framework. This component has deppandes from many different components in the core libarary, and is referenced in many different documentations of different core components.

The core component is tightly coupled with the locator, the core component is dependent on the locator. The core component has a load method that will eager load specified services from the components configurations.


Example

I feel it is best suited to explain the locator component by explicitly explain the expected workflow. Below you find a defined file direcory, followed by code snippets of each file and explanations of each of these files and there purpose.

app
└── src
│   ├── foobar
│   │   ├── config.js
│   │   ├── index.js
│   │   └── locator.js
│   └── index.js
└── package.json

As we see in the file structure above, we have a simple app with a src folder that contains the source code. The src folder contains a components called foobar. In the root of the foobar components folder there is a config file that defines how the component integrates with the core context.

app/src/foobar/config.js

module.exports =
{
  core:
  {
    locator:
    {
      'foobar' : __dirname
    }
  }
}

Above is a defintion of a component configuration that defines the locatable service foobar. The service foobar is defined by the following simple class.

app/src/foobar/index.js

class Foobar
{
  constructor(console)
  {
    this.console = console
  }

  foo(bar)
  {
    this.console.log(bar)
  }
}

module.exports = Foobar

The code snippet above defines a class called Foobar with the defined dependency console. The Foobar class has a method called foo that accepts an argument called bar, that later is logged to the console when invoked.

As we can see above, the class it self is exported by the NodeJs framework. Note that we are not exporting an instance of the class. The instanciation and construction of the class will be done by the locator, as shown below.

app/src/foobar/locator.js

const
Foobar              = require('.'),
LocatorConstituent  = require('superhero/core/locator/constituent')

class FoobarLocator extends LocatorConstituent
{
  locate()
  {
    const
    console = this.locator.locate('core/console'),
    foobar  = new Foobar(console)

    return foobar
  }
}

module.exports = FoobarLocator

Above you see a locator constituent. The contract is that a locate method must be present in the class. The extention to superhero/core/locator/constituent provides access to the core/locator component through the variable this.locator.

In above example, the instance foobar is created and injected with its dependency, the core/console component, which is located through the locator.

When the locator is being eager loaded by the core context, the locate method above is called to locate the instance foobar, and can be accessed through the locator, in the same way as we above locate the core/console component, or located through the core context, as shown below.

app/src/index.js

const
CoreFactory = require('superhero/core/factory'),
coreFactory = new CoreFactory,
core        = coreFactory.create()

core.add('foobar')
core.load()
core.locate('foobar').foo('Hello World')

Above code is the main script of this example. This is where the core context is created, defined and loaded.

First we create the core instance, followed by adding the component foobar to the core context, followed by loading the core context - which triggers the eager loading process, where the above defined locator constituent is used to create the instance.

In the end, the foobar component is located and used by passing the the string Hello World to the method foo, resulting in a log message in the console.

app/src/package.json

{
  "name": "App",
  "version": "0.0.1",
  "description": "An example app",
  "repository": "https://github.com/...",
  "license": "MIT",
  "main": "src/index.js",
  "dependencies": {
    "superhero": "*"
  }
}

Above is a simple example of the package.json file used in this example application.


Dynamic configuration specification

To ease work with the locator, in the configuration it is possible to make dynamic references. The example below shows a directory with many different services.

app
└── src
    └── domain
        ├── service
        │   ├── foo
        │   │   ├── index.js
        │   │   └── locator.js
        │   ├── bar
        │   │   ├── index.js
        │   │   └── locator.js
        │   ├── baz
        │   │   ├── index.js
        │   │   └── locator.js
        │   └── qux
        │       ├── index.js
        │       └── locator.js
        └── config.js

To include all these services, the configuration in the domain is expected to look something like the example below shows.

module.exports =
{
  core:
  {
    locator:
    {
      'service/foo' : __dirname + '/service/foo',
      'service/bar' : __dirname + '/service/bar',
      'service/baz' : __dirname + '/service/baz',
      'service/qux' : __dirname + '/service/qux'
    }
  }
}

By using an asterix * in the configuration defintion, to specify a dynamic reference, the same configuration could look like the following example shows.

module.exports =
{
  core:
  {
    locator:
    {
      'service/*' : __dirname + '/service/*'
    }
  }
}

Good practice

It is recommended, and by this documentation considered good practice, that you never create a service that is dependent on the core/locator component. The locator pattern should be considered as a framework layer, a layer that you should not be integreated with the projects business logic.

Service locators consists of a larger scope of defined services. If you describe a dependency in your implementations to this larger scope, you are not explicit in your decleration, which is a bad code standard.

This concept is appearent when you work with unit testing. If the unit is dependent on a greater scope that needs to be loaded before you can test the unit, you have defined a much larger test then necessery for the unit.

This concept is also appearent when others read your code, or integrates with it. It is much easier to undersand what an implementation does when an explicit dependency injection is honered.

If we relate to the service locator as a container of the global scope, then creating a dependency to the locator, is the same as creating a dependency to the global scope. It is concidered good practice to isolate your implementations to the local scope the unit operates in to deflect unexpected side effects.