DEV Community

Cover image for Plugin Oriented Design with Halia
Oranda
Oranda

Posted on

Plugin Oriented Design with Halia

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

Halia Logo

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";
}
Enter fullscreen mode Exit fullscreen mode

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";
}
Enter fullscreen mode Exit fullscreen mode

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
  })
}
Enter fullscreen mode Exit fullscreen mode
//  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")
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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 🐠

Doug Image

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)