Dependency Injection
Introduction
Dependency injection is a design pattern, that moves the building of object dependencies out of its scope, and provides them by injecting them through many means (e.g by passing them in constructor) and freeing the object from manually building its dependencies. Therefore, achieving separation of concerns, as said object doesn't need to "know" how its dependencies are built.
The broker internally uses the IoC container from the InversifyJS
package, to manage its dependencies.
Currently, it's the only container it works out of a box with, but in the future, it might support
all major javascript DI libraries. But it's possible to use different containers,
see the custom container section.
The use of it is completely optional, if you want to manually build your classes, you're free to skip this part of the documentation.
note
To find out more about the InversifyJS
, head to its documentation.
Installation
Make sure to install InversifyJS
and reflect-metadata
packages.
npm install inversify reflect-metadata
Setup
To make Inversify automatically resolve dependencies between our objects, let's set the autoBindInjectable
property to true
, and pass the container
object to broker options.
- TypeScript
- JavaScript
import { BrokerFactory, Broker } from 'electron-broker';
import { Container } from 'inversify';
import 'reflect-metadata';
const container: Container = new Container({
autoBindInjectable: true,
})
let broker: Broker;
async function createBroker() {
broker = await BrokerFactory.createProcessBroker({
container: container,
});
broker.start();
}
import { BrokerFactory, Broker } from 'electron-broker';
import { Container } from 'inversify';
import 'reflect-metadata';
const container = new Container({
autoBindInjectable: true,
})
let broker;
async function createBroker() {
broker = await BrokerFactory.createProcessBroker({
container: container,
});
broker.start();
}
Binding controllers
Mark the controller with @Injectable()
decorator, to make it resolvable by the container.
- TypeScript
- JavaScript
import { Injectable } from 'inversify';
import { Controller, MessagePattern } from 'electron-broker';
@Injectable()
@Controller('cats')
export default class CatsController {
@MessagePattern('get-breeds')
public getBreeds(): string[] {
return ["Persian", "Maine Coon", "Sphynx"];
}
}
import { Injectable } from 'inversify';
import { Controller, MessagePattern } from 'electron-broker';
@Injectable()
@Controller('cats')
export default class CatsController {
@MessagePattern('get-breeds')
public getBreeds() {
return ["Persian", "Maine Coon", "Sphynx"];
}
}
And provide the controller class reference to controllers
to broker settings. When you provide the broker
with controller class definition, it will attempt to resolve it within the container and will throw an error
if fails to do so.
- TypeScript
- JavaScript
import 'reflect-metadata';
import { BrokerFactory } from 'electron-broker';
import { Container } from 'inversify';
import CatsController from './cats.controller.js';
async function createBroker() {
broker = await BrokerFactory.createProcessBroker({
container: container,
controllers: [CatsController]
});
broker.start();
}
createBroker();
import 'reflect-metadata';
import { BrokerFactory } from 'electron-broker';
import { Container } from 'inversify';
import CatsController from './cats.controller.js';
async function createBroker() {
broker = await BrokerFactory.createProcessBroker({
container: container,
controllers: [CatsController]
});
broker.start();
}
createBroker();
Binding middleware
Mark the Middleware with @Injectable()
decorator, to make it resolvable by the container.
- TypeScript
- JavaScript
import { Middleware } from 'electron-broker';
import { Injectable } from 'inversify';
@Injectable()
export class MessageLoggerMiddleware implements Middleware {
public onRequest(context: ExecutionContext) {
const { eventId } = context.brokerEvent;
console.log(`[LOG] Received message with eventId: ${eventId}`)
}
public onResponse(context: ExecutionContext) {
const { eventId } = context.brokerEvent;
console.log(`[LOG] Sent message with eventId: ${eventId}`)
}
}
import { Middleware } from 'electron-broker';
import { Injectable } from 'inversify';
@Injectable()
export class MessageLoggerMiddleware {
public onRequest(context) {
const { eventId } = context.brokerEvent;
console.log(`[LOG] Received message with eventId: ${eventId}`)
}
public onResponse(context) {
const { eventId } = context.brokerEvent;
console.log(`[LOG] Sent message with eventId: ${eventId}`)
}
}
And pass the middleware class reference to UseMiddleware()
decorator. If the broker fails to resolve the reference
within its container, it will throw an error.
- TypeScript
- JavaScript
import { Controller, MessagePattern } from 'electron-broker';
import { Injectable } from 'inversify';
import { MessageLoggerMiddleware } from './message-logger.middleware.ts'
@Controller()
@Injectable()
export default class PingPongController {
@MessagePattern('ping')
@UseMiddleware([MessageLoggerMiddleware])
public getPong(): string {
return "pong";
}
}
import { Controller, MessagePattern } from 'electron-broker';
import { Injectable } from 'inversify';
import { MessageLoggerMiddleware } from './message-logger.middleware.js'
@Controller()
@Injectable()
export default class PingPongController {
@MessagePattern('ping')
@UseMiddleware([MessageLoggerMiddleware])
public getPong() {
return "pong";
}
}
Accesing BrokerClient
Accessing BrokerClient
instances within any of your classes, is possible by specifying them in their constructors.
Keep in mind that each of those instances will be unique.
- TypeScript
- JavaScript
import { BrokerClient } from 'electron-broker';
import { Injectable } from 'inversifyjs'
@Injectable()
export default class MockService {
constructor(private brokerClient: BrokerClient) {}
public sendSomething(): void {
this.brokerClient.send('something', 'anything');
}
}
import { BrokerClient } from 'electron-broker';
import { Injectable, Inject } from 'inversifyjs'
@Injectable()
export default class MockService {
constructor(@Inject(BrokerClient) private brokerClient) {}
public sendSomething() {
this.brokerClient.send('something', 'anything');
}
}
To use the same instance of BrokerClient
across all of your classes, simply bind the instance to the container,
before calling the start()
method of Broker
class.
- TypeScript
- JavaScript
import 'reflect-metadata';
import { BrokerFactory } from 'electron-broker';
import { Container } from 'inversify';
import CatsController from './cats.controller.ts';
// Token to identify our custom broker client
export CUSTOM_BROKER_CLIENT: symbol = Symbol.for('custom-broker-client');
async function createBroker() {
const container = new Container({ autoBindInjectable: true });
broker = await BrokerFactory.createProcessBroker({
container: container,
});
const customBrokerClient = broker.createClient();
container.bind(CUSTOM_BROKER_CLIENT).toConstantValue(customBrokerClient);
broker.start();
}
createBroker();
import 'reflect-metadata';
import { BrokerFactory } from 'electron-broker';
import { Container } from 'inversify';
import CatsController from './cats.controller.js';
// Token to identify our custom broker client
export CUSTOM_BROKER_CLIENT = Symbol.for('custom-broker-client');
async function createBroker() {
const container = new Container({ autoBindInjectable: true });
broker = await BrokerFactory.createProcessBroker({
container: container,
});
const customBrokerClient = broker.createClient();
container.bind(CUSTOM_BROKER_CLIENT).toConstantValue(customBrokerClient);
broker.start();
}
createBroker();
And inject it to any constructor by use of Inject()
decorator.
- TypeScript
- JavaScript
import { BrokerClient } from 'electron-broker';
import { Injectable, Inject } from 'inversifyjs';
import { CUSTOM_BROKER_CLIENT } from './index.ts';
@Injectable()
export default class MockService {
constructor(@Inject(CUSTOM_BROKER_CLIENT) private brokerClient: BrokerClient) {}
public sendSomething(): void {
this.brokerClient.send('something', 'anything');
}
}
import { BrokerClient } from "electron-broker";
import { Injectable, Inject } from "inversifyjs";
import { CUSTOM_BROKER_CLIENT } from "./index.js";
@Injectable()
export default class MockService {
constructor(@Inject(CUSTOM_BROKER_CLIENT) private brokerClient) {}
public sendSomething() {
this.brokerClient.send('something', 'anything');
}
}
Custom container
WIP