0.0.7 • Published 10 months ago

@honout/functionality v0.0.7

Weekly downloads
-
License
MIT
Repository
github
Last release
10 months ago

Honout Functionality

Creating a custom functionality

Start by importing the IFunctionality interface from @honout/system

import { IFunctionality } from '@honout/system';

The interface has 3 generic parameters.

  • ExtendableLogic
    • Internal logic interfaces that may be extended
  • LogicIdentifiers
    • Identifiers to the above.
  • Configuration
    • Configuration of this functionality
IFunctionality<ExtendableLogic, LogicIdentifiers, Configuration>;

Next create a class that implements this interface and mark it as injectable. As an example, let´s create a functionality for event handling. First declare an interface for your event handlers.

interface IEventHandler {
    // called when an event is received
    onEvent(message: string): void;
    // the type of event this handler should listen to
    getType(): string;
}

Next, create your publicly extendable logics. In this case, we only want to be able to add event handlers so this interface will be the only logic extension.

type ExtendableLogic = IEventHandler;

Next define your public logic identifiers.

enum MyFunctionalityIdentifiers {
    EVENT_HANDLER = 'IEventHandler',
}

If your functionality may be configured, declare an interface for the configuration also

interface IConfiguration {
    foo: string;
}

Finally we want to create a separate interface that will be used to consume all event handlers and to start actually listening to events

interface IEventManager {
    start(): void;
}

Now that all of our types and interfaces have been declared, let´s piece the logic together. Let´s create a class that implements the IFunctionality interface.

@injectable()
class EventFunctionality
    implements
        IFunctionality<
            ExtendableLogic,
            MyFunctionalityIdentifiers,
            IConfiguration
        >
{
    // Receives any extensions, it is up to each functionality to handle this.
    onLogicExtensions(
        extensions: ILogicExtension<
            ExtendableLogic,
            MyFunctionalityIdentifiers
        >[],
        container: Container
    ): void {}

    // Called with the configuration given to this functionality
    onConfigure(configuration: IConfiguration): void {}

    // Called when it is time for this functionality to bind its´ internal dependencies.
    bindInternals(container: Container): void {}

    // This will be called when all of the above has been resolved
    async start(container: Container): Promise<void> {}
}

Lets start by implementing some logic in these methods. Lets begin with onLogicExtensions

onLogicExtensions(
        extensions: ILogicExtension<ExtendableLogic, MyFunctionalityIdentifiers, IConfiguration>[],
        container: Container
    ): void {
        // we loop through each possible extension even though we only have one in this case, the IEventHandler
        extensions.forEach((extension) => {
            // We use the identifier to verify the type of extension
            if (extension.identifier === MyFunctionalityIdentifiers.EVENT_HANDLER) {
                // And finally we go thorugh all the IEventHandlers and bind them to the di container
                extension.definitions.forEach((definition) => {
                    container
                        .bind(extension.identifier)
                        .to(definition)
                        .inSingletonScope()
                })
            }
        })
    }

Before we implement the bindInternals method, we need to implement the IEventManager since bindInternals will use this dependency. So let´s to that.

@injectable()
class EventManager implements IEventManager {
    constructor(
        // Inject all event handlers
        @multiInject(MyFunctionalityIdentifiers.EVENT_HANDLER)
        // The number of event handlers could be 0, meaning we declare this as optional
        @optional()
        private eventHandlers: IEventHandler[]
    ) {}

    start() {
        // Go through each injected event handler
        this.eventHandlers.forEach((eventHandler) => {
            // Bind the handler to the emitter
            EventEmitter.on(eventHandler.getType(), eventHandler.onEvent);
        });
    }
}

Now we can finally implement the bindInternals method

bindInternals(container: Container): void {
        container.bind<IEventManager>('IEventManager').to(EventManager).inSingletonScope()
    }

And lastly, let´s implement the start method

async start(container: Container): Promise<void> {
    container.get<IEventManager>('IEventManager').start()
}

Let´s use this functionality in an application now. First we need to create an event handler

@injectable()
class MyEventHandler implements IEventHandler {
    onEvent(message: string): void {
        console.log('Message received', message);
    }

    getType(): string {
        return 'message';
    }
}

In order to add this to an application, there is a decorator available from the @honout/system that we can use

import { WithFunctionality } from '@honout/system';

@injectable()
@WithFunctionality({
    functionality: EventFunctionality,
    extend: [
        {
            identifier: MyFunctionalityIdentifiers.EVENT_HANDLER,
            definitions: [MyEventHandler],
        },
    ],
})
class MyApp implements IApplication {}

And that´s it. When the System starts, this functionality will automatically run and starts listening to 'message' events.

existing @honout functionalities