Skip to content

Autowire

Mauro Gadaleta edited this page Jul 28, 2023 · 4 revisions

Defining Services Dependencies Automatically (Autowiring)

Only available for TypeScript

Autowiring allows you to manage services in the container with minimal configuration. It reads the type-hints on your constructor and automatically passes the correct services to each method. Node Dependency Injection's autowiring is designed to be predictable: if it is not absolutely clear which dependency should be passed, you'll see an actionable exception.

Thanks to compiled container, there is no runtime overhead for using autowiring.

⚠️ At the moment, Autowiring only works with private constructor properties.

An Autowiring Example

Start by creating a Base64 transformer class:

export default class Base64Transformer
{
    public transform(value: string): string
    {
        return new Buffer(value).toString('base64');
    }
}

And now a Some client using this transformer:

export default class SomeClient
{
    constructor(
      private readonly transformer: Base64Transformer,
    ) {}

    async execute(value: string): Promise<void>
    {
        const transformedString = this.transformer.transform(value)
        
    }
}

If you're using the default services.yaml configuration, both classes are automatically registered as services and configured to be autowired. This means you can use them immediately without any configuration.

However, to understand autowiring better, the following examples explicitly configure both services:

# config/services.yaml
services:
    _defaults:
        autowire: true
        rootDir: ../src # that is from the file path

Working with Interfaces

You might also find yourself type-hinting abstractions (e.g. interfaces) instead of concrete classes as it replaces your dependencies with other objects.

To follow this best practice, suppose you decide to create a TransformerInterface:

export default interface Transformer
{
    transform(value: string): string;
}

Then, you update Base64Transformer to implement it:

export default class Base64Transformer implements Transformer
{
    transform(value: string): string
    {
        return new Buffer(value).toString('base64');
    }
}

Now that you have an interface, you should use this as your type-hint:

export default class SomeClient
{
    constructor(
      private readonly transformer: Transformer,
    ) {}

    async execute(value: string): Promise<void>
    {
        const transformedString = this.transformer.transform(value)
        
    }
}

Dump a service file from Autowire

If you are transpiling your Typescript may you need to dump the some kind of service configuration file.

import {ContainerBuilder, Autowire, ServiceFile} from 'node-dependency-injection'

const container = new ContainerBuilder(
  false, 
  '/path/to/src'
)
const autowire = new Autowire(container)
autowire.serviceFile = new ServiceFile('/some/path/to/dist/services.yaml')
await autowire.process()

My proposal for load configuration file in a production environment with transpiling/babel compilation:

if (process.env.NODE_ENV === 'dev') {
  this._container = new ContainerBuilder(false, '/src');
  this._autowire = new Autowire(this._container);
  this._autowire.serviceFile = new ServiceFile('/some/path/to/dist/services.yaml');
  await this._autowire.process();
} else {
  this._container = new ContainerBuilder(false, '/dist');
  this._loader = new YamlFileLoader(this._container);
  await this._loader.load('/some/path/to/dist/services.yaml');
}
await this._container.compile();

Exclude folders from root directory

You can also exclude some specific folder from your root directory

# /path/to/services.yml
services:
  _defaults:
    autowire: true
    rootDir:  "../path/to/src"
    exclude: ["ToExclude"]

So, in that case, ../path/to/src/ToExclude will be automatically excluded from container.