with-factory
with-factory is a utility to define factories with withFactory and invoke them invokeFactory to enable dependency injection at a higher level.

This example creates a sayHello button. When clicked, a hello world! message displays.
index.js
import React from 'react';
import makeSayHello from './sayHello.js';
import makeLog from './makeLog.js';
export default class WithFactoryPlugin {
constructor({ diHelper, invokeFactory }) {
this._diHelper = diHelper;
diHelper.useFactory('log', makeLog);
}
onInit() {
this._diHelper.invoke(this._putLogMessages);
this._diHelper.invoke(this._putExample);
}
_putLogMessages({ gastHelper }) {
gastHelper.putGASTComponent(
'LogMessages',
`
<ul>
<For in="getVM('$.withFactory.log.messages', ['-no message-'])">
<template message="item" index="index">
<li key="index">{message}</li>
</template>
</For>
</ul>
`,
);
}
_putExample({ invokeFactory, uiHelper }) {
const { putComponent, POSComponent } = uiHelper;
const sayHello = invokeFactory(makeSayHello);
putComponent('Example', () => (
<div>
<button onClick={sayHello}>sayHello</button>
<POSComponent componentName="LogMessages" />
</div>
));
}
}
sayHello.js
import React from 'react';
import makeSayHello from './sayHello.js';
import makeLog from './makeLog.js';
export default class WithFactoryPlugin {
constructor({ diHelper, invokeFactory }) {
this._diHelper = diHelper;
diHelper.useFactory('log', makeLog);
}
onInit() {
this._diHelper.invoke(this._putLogMessages);
this._diHelper.invoke(this._putExample);
}
_putLogMessages({ gastHelper }) {
gastHelper.putGASTComponent(
'LogMessages',
`
<ul>
<For in="getVM('$.withFactory.log.messages', ['-no message-'])">
<template message="item" index="index">
<li key="index">{message}</li>
</template>
</For>
</ul>
`,
);
}
_putExample({ invokeFactory, uiHelper }) {
const { putComponent, POSComponent } = uiHelper;
const sayHello = invokeFactory(makeSayHello);
putComponent('Example', () => (
<div>
<button onClick={sayHello}>sayHello</button>
<POSComponent componentName="LogMessages" />
</div>
));
}
}
makeLog.js
function makeLog({ dispatchersHelper }) {
function log(message) {
dispatchersHelper.dispatch(
'updateVM',
'$.withFactory.log.messages',
'(messages = []) => [...messages, message]',
{ message },
);
}
return log;
}
export default makeLog;
package.json
{
"name": "plugin-test",
"version": "1.0.0",
"orion": {
"dispatchersHelper": {
"actionCreators": {
"consumes": {
"@orion/vm": [
"setVM",
"updateVM"
]
}
}
},
"selectorsHelper": {
"selectorFactories": {
"consumes": {
"@orion/vm": [
"getVM"
]
}
}
}
}
}
In software architecture dependency injection is key. It is the first step of the Dependency Inversion, the last step of any SOLID architecture. With dependency injection, each software component defines what it wants to use and uses that. It does not need to know where required things come from, it does not look for it, so it is easier to keep a single responsibility, the S of SOLID. It does not need to know who provides it, so it can be independent and open to change what it injects, the O of SOLID.
What is dependency injection? It is as simple as passing to any software component what this component needs.
// this is dependency injection
function sayHello(log) {
log('hello world!');
}
// how to use it:
sayHello(console.log);
You can use plain arguments, constructors, setters, and equivalents. We use the dio pattern, which implies that it receives an object with all possible injections and takes what it needs:
// this is dependency injection with a DIO
function sayHello({ log }) {
log('hello world!');
}
// how to use it:
sayHello({ log: console.log });
Note that in both cases it is effortless to use and to configure. What if you want to use the error stream instead of the simple log? Alternatively, what if you want to send logs through a socket? Just send the correct instance of the log function. You can reuse existing software.
What if we have not used dependency injection?
// this is not dependency injection
function sayHello() {
// note that here we are using console, which is a global object
console.log('hello world!');
}
Without dependency injection, you have lost all flexibility and reusability. Worst than that, the code is more difficult to understand and difficult to follow. In that case sayHello has a side effect; it changes the state of a global variable.
When to use dependency injection?
The previous case was a simple hello world. Probably something like that does not require dependency injection. However, what if we want to route the log to another channel? What if we want to send the log through a stream to a server?
The implementation with dependency injection allows reusing the same logic in other scenarios. Without dependency injection, it has only one use.
The critical point in the example is console. Without dependency injection, it becomes a global object, and the software becomes dependent. That it is because in that second implementation sayHello is doing two things: 1) looking for the console, 2) doing its job.
The idea of using or not dependency injection is knowing if you have a global variable and if you need to change it. For example, all helpers provided by the framework are unique for the current instance of a plugin, so each time that the plugin starts it provides new instances, so it requires dependency injection. Usually, it is okay to use the global for the console, but better to use an injection if you need to create reports.
Dependency injection and testing
Did you have ever tried to test a hello world? Use the non-dependency injection, how you can test it?
// no dependency injection version (not working)
test('sayHello prints hello world', () => {
sayHello();
expect( ??? ).toEqual('hello world!');
})
How do you know that it is working correctly? A possible solution is:
// no dependency injection version
test('sayHello prints hello world', () => {
const savedLog = console.log;
try {
console.log = jest.fn();
sayHello();
expect(console.log).toHaveBeenCalledWith('hello world!');
} finally {
console.log = savedLog;
}
});
It is a pretty dirty solution. The main problem is that because console is a global variable, mocking log affects everything, not just sayHello. The expect itself may not report correctly; other components used by sayHello may use the mock also accidentally.
An alternative is using the dependency injection version:
// dependency injection version
test('sayHello prints hello world', () => {
const log = jest.fn();
sayHello({ log });
expect(log).toHaveBeenCalledWith('hello world!');
});
It is simple, easy to understand, free of side effects.
Learn more about dependency injection
The framework provides a great and powerful execution environment; it is the di-helper. It allows specifying multiple factories that invoked lazily in an integrated context.
withFactory/invokeFactories
These two artifacts are created to increase legibility and simplicity in the creation and use of factories.
What is a factory?
What if we want to send a sayHello to other components, but we do not want them to know that sayHello requires a log? What if we also want to keep the ability to configure the log and we do not want to use the global console?
That is what factories are for; they create instances of software artifacts with the right injections able to be used in other artifacts.
// factory with the dependency injection
function makeSayHello({ log }) {
function sayHello() {
log('hello world!');
}
return sayHello;
}
// instance the sayHello
const sayHello = makeSayHello({ log: console.log });
// use sayHello
sayHello();
The withFactory is just a decorator. It does nothing else than marking a function instance as factory. The function instance does not change, neither the content.
The instanceFactory is a function injected to the plugin that instances a factory marked with withFactory. If the function does not have the withFactory mark, it returns the same function. The plugin receives a instanceFactory configured with the plugin dio.
The instanceFactories is a function that receives an array or an object and returns a copy with all instantiated.
withFactory
It is a decorator provided by a library that marks a function as a factory.
Usage
withFactory(factory: Function): factory
-
factory Function [REQUIRED]
A factory function that receives the plugin DIO.
-
Returns Function
Exactly the same function instance received unchanged.
Note that this method only marks the function, it does not change it, neither does it change its instance. It only has effect used with instanceFactory or any other function that uses it.
Also note that withFactory is imported from the package @orion/core and it is not provided by the DIO. It allows you to use it in any file directly, without requiring the DIO.
Example
import { withFactory } from '@orion/core';
import increment from './actions/increment';
import makeDecrement from './actions/makeDecrement';
export default class IncrementDecrementPlugin {
constructor({ dispatchersHelper }) {
this._dispatchersHelper = dispatchersHelper;
}
onInit() {
this._dispatchersHelper.register('increment', increment);
this._dispatchersHelper.register('decrement', withFactory(makeDecrement));
}
}
instanceFactory
A function provided directly by the DIO of the plugin that, if it receives a function marked by withFactory, it calls to the factory and returns the instance, otherwise it returns the original Object.
This function is designed to allow the mixing factories with no factories. It does not matter what you have, if it is a factory marked with withFactory then the factory is called. This allows you to export factories or not factories without taking care which are factories or not.
It works with the withFactory because it is not possible to know in advance what is a function factory, or what is a function. So, only functions marked with withFactory are called.
Usage
instanceFactory(factory: Function | any): any
-
factory Function | any [REQUIRED]
A factory function marked with withFactory or any other value
-
Returns any
If the input is a withFactory function the result of calling to it otherwise the same input
Example
import { withFactory } from '@orion/core';
import increment from './actions/increment';
import makeDecrement from './actions/makeDecrement';
export default class IncrementDecrementPlugin {
constructor({ dispatchersHelper, invokeFactory }) {
this._dispatchersHelper = dispatchersHelper;
this._invokeFactory = invokeFactory;
}
onInit() {
this._dispatchersHelper.register('increment', this._invokeFactory(increment));
this._dispatchersHelper.register('decrement', this._invokeFactory(withFactory(makeDecrement)));
}
}
instanceFactories
A sugar tool that instances all factories inside an array or an object. If it is an array it returns a new object with all elements that are withFactory transformed in the result of its invokation. If it is an object, it returns an object with the same keys but values that are withFactory are the results of its invokations.
Usage
instanceFactories(factories: Array|Object): ArrayObject
-
factories Array | Object [REQUIRED]
An object or an array of all factories.
-
Result Array | Object
The same input with instanceFactory applied to each value present.