Extension point/extension pattern
Extension Point/extension is a very powerful design pattern that promotes loose coupling and offers great extensibility. There are many use cases in LoopBack 4 that fit into design pattern. For example:
@loopback/boot
usesBootStrapper
that delegates toBooters
to handle different types of artifacts@loopback/rest
usesRequestBodyParser
that finds the correspondingBodyParsers
to parse request body encoded in different media types@loopback/core
usesLifeCycleObserver
to observestart
andstop
events of the application life cycles.
To add a feature to the framework and allow it to be extended, we divide the responsibility into two roles:
-
Extension point: it represents a common functionality that the framework depends on and interacts with, such as, booting the application, parsing http request bodies, and handling life cycle events. Meanwhile, the extension point also defines contracts for its extensions to follow so that it can discover corresponding extensions and delegate control to them without having to hard code such dependencies.
-
Extensions: they are implementations of specific logic for an extension point, such as, a booter for controllers, a body parser for xml, and a life cycle observer to load some data when the application is started. Extensions must conform to the contracts defined by the extension point.
NOTE: Applications can also benefit from the extension point/extensions pattern by separating common functionality and specific behaviors for the business logic.
Helper decorators and functions
To simplify implementations of extension point and extensions pattern on top of LoopBack 4’s Inversion of Control and Dependency Injection container, the following helper decorators and functions are provided to ensure consistency and convention.
@extensionPoint
: decorates a class to be an extension point with an optional custom name@extensions
: injects a getter function to access extensions to the target extension point@extensions.view
: injects a context view to access extensions to the target extension point. The view can be listened for context events.@extensions.list
: injects an array of extensions to the target extension point. The list is fixed when the injection is done and it does not add or remove extensions afterward.extensionFilter
: creates a binding filter function to find extensions for the named extension pointextensionFor
: creates a binding template function to set the binding to be an extension for the named extension point(s). It can accept one or more extension point names to contribute to given extension pointsaddExtension
: registers an extension class to the context for the named extension point
Examples
-
Inject a getter function for extensions
import {Getter} from '@loopback/core'; import {extensionPoint, extensions} from '@loopback/core'; @extensionPoint('greeters') class GreetingService { @extensions() public getGreeters: Getter<Greeter[]>; }
-
Inject a context view for extensions
import {ContextView} from '@loopback/core'; import {extensionPoint, extensions} from '@loopback/core'; @extensionPoint('greeters') class GreetingService { constructor( @extensions.view() public readonly greetersView: ContextView<Greeter>, ) { // ... } }
-
Inject an array of resolved extensions
import {extensionPoint, extensions} from '@loopback/core'; @extensionPoint('greeters') class GreetingService { @extensions.list() public greeters: Greeter[]; }
More usage of these helper decorators and functions are illustrated in the
greeter-extension
tutorial.
Tutorial
The greeter-extension example provides a walk-through on how to implement the extension point/extension pattern using LoopBack 4’s Context and Dependency injection container.