Building apps with Plugin Oriented Design (POD) has a lot of advantages (discussed in my recent blog post)
Today, I'd like to introduce "Halia", an extensible TS / JS Dependency Injection Framework built to manage "Plugins" in your apps.
Building a Pluggable app has its advantages, but it can be challenging to stay organized when you're dynamically importing code that changes other code.
For example, if you build a feature that depends upon another feature (or multiple), you'd likely end up "splitting and spreading" the feature across the app.
Halia can help with this:
Halia - Extensible TS / JS Dependency Injection Framework
With Halia, your app's core is packaged as a Plugin, and features are packaged as Plugins. Each Plugin can then be extended by other Plugins.
We’re also building tools to dynamically load plugins from a URL, activate plugins dynamically, user level managment (like a plugin store), cross-stack and cross-eco plugins, and contracts (for building re-usable plugins).
Halia is responsible for managing this tree of plugins. Let's see how it works with an example:
Example
You have a duck that everyone loves:
// duck-app.ts
export const getDuck = () => {
return "Quack";
}
Everyone except Paul. Paul wants a special 🦄 Disco Duck 🦄, so you make an update:
// duck-app.ts
import { Paul } from 'client-list';
import { config } from 'config';
export const getDuck = () => {
if (params.client === Paul) {
return "Michael Quackson";
}
return "Quack";
}
While the code works for Paul, it's become more complex, harder to read, and coupled with the "client" concept.
Instead, we can use a Halia Plugin to encapsulate and inject this feature:
// duck-app-plugin.ts
import * as DuckApp from './duck-app';
export const DuckAppPlugin: HaliaPlugin = {
id: "duckApp",
name: "Duck App Plugin",
install: () => ({
setGetDuck: (getDuck) => DuckApp.getDuck = getDuck
})
}
// disco-duck-plugin.ts
import { Paul } from 'client-list';
import * as config from 'config';
export const DiscoDuckPlugin: HaliaPlugin = {
id: "discoDuck",
name: "Disco Duck Plugin",
dependencies: [DuckAppPlugin.id],
install: ({ duckApp }) => {
if (config.client === Paul) {
duckApp.setGetDuck (() => "Michael Quackson")
}
}
}
Then we can build the stack and invoke the code:
// main.ts
import { HaliaStack } from Halia;
import { DuckApp } from './DuckApp';
import { DiscoFeature } from './DiscoFeature';
const buildApp = async () => {
// Initialize the Stack
const appStack = new HaliaStack();
// Register Plugins
appStack.register(DuckApp);
appStack.register(DiscoFeature);
// Build the Stack
await appStack.build();
// Call the Method
const duckApp = appStack.getExports(DuckApp.id);
duckApp.logNoise();
}
buildApp();
With this, the original code is left in-tact and de-coupled.
If Paul longer wants the 🦄 Disco Duck 🦄 we just don't register the Plugin. If he needs an additional change, we have a namespace dedicated to his unique needs.
This is a simple example that can be solved in other ways. However, it demonstrates the general idea, and as features become more complex, we've found this pattern helps to keep things organized.
Conclusion
For more info on Dependency Injection Frameworks (like Angular, Nest, and Halia) see our article:
Dependency Injection with Doug the Goldfish 🐠
I hope you enjoy the package and the associated concepts.
Cheers,
CR
For more articles like this, follow me on: Github, Dev, Twitter, Reddit
Top comments (0)