Files

11 KiB

Microkernel (Plugin) Architecture Pattern

Problem

An application needs to support many optional features, extensions, or variations, but embedding all of them into the core creates a monolith that is hard to maintain, test, and deploy. Adding new features requires modifying and redeploying the entire system. Different customers need different feature sets.

Solution

The Microkernel pattern splits the system into two components:

  • Core System — Minimal functionality that defines the application's identity. Contains the plugin registry, lifecycle management, and core abstractions.
  • Plugins — Independent modules that extend the core with additional features. Each plugin implements a known interface and is registered with the core at startup or runtime.

The core is stable and rarely changes. New features are added by writing new plugins, not by modifying the core.

When to Use

  • Product-based applications that need per-customer customization.
  • IDEs, editors, browsers, and other tool platforms.
  • Workflow engines where processing steps vary.
  • Applications with a stable core but frequently changing peripheral features.

When to Avoid

  • When the "core" would be trivially small and most logic lives in plugins.
  • High-performance systems where plugin dispatch overhead matters.
  • When the plugin interface is unstable and changes frequently.

Pseudocode

INTERFACE Plugin:
    METHOD name() -> string
    METHOD execute(context) -> result

CLASS Core:
    plugins = {}

    METHOD register(plugin: Plugin):
        plugins[plugin.name()] = plugin

    METHOD execute(plugin_name, context):
        plugin = plugins[plugin_name]
        RETURN plugin.execute(context)

    METHOD list_plugins():
        RETURN KEYS(plugins)

Python

from abc import ABC, abstractmethod


class Plugin(ABC):
    @abstractmethod
    def name(self) -> str: ...

    @abstractmethod
    def execute(self, data: str) -> str: ...


class UpperCasePlugin(Plugin):
    def name(self) -> str:
        return "uppercase"

    def execute(self, data: str) -> str:
        return data.upper()


class ReversePlugin(Plugin):
    def name(self) -> str:
        return "reverse"

    def execute(self, data: str) -> str:
        return data[::-1]


class WordCountPlugin(Plugin):
    def name(self) -> str:
        return "wordcount"

    def execute(self, data: str) -> str:
        count = len(data.split())
        return f"Word count: {count}"


class CaesarCipherPlugin(Plugin):
    def __init__(self, shift: int = 3) -> None:
        self._shift = shift

    def name(self) -> str:
        return "caesar"

    def execute(self, data: str) -> str:
        result = []
        for ch in data:
            if ch.isalpha():
                base = ord("A") if ch.isupper() else ord("a")
                result.append(chr((ord(ch) - base + self._shift) % 26 + base))
            else:
                result.append(ch)
        return "".join(result)


class TextProcessorCore:
    """Core system with plugin registry."""

    def __init__(self) -> None:
        self._plugins: dict[str, Plugin] = {}

    def register(self, plugin: Plugin) -> None:
        self._plugins[plugin.name()] = plugin
        print(f"  [Core] Registered plugin: '{plugin.name()}'")

    def unregister(self, name: str) -> None:
        if name in self._plugins:
            del self._plugins[name]
            print(f"  [Core] Unregistered plugin: '{name}'")

    def list_plugins(self) -> list[str]:
        return list(self._plugins.keys())

    def execute(self, plugin_name: str, data: str) -> str:
        plugin = self._plugins.get(plugin_name)
        if not plugin:
            return f"  [Core] ERROR: Plugin '{plugin_name}' not found"
        result = plugin.execute(data)
        return result

    def execute_pipeline(self, plugin_names: list[str], data: str) -> str:
        """Run multiple plugins in sequence."""
        result = data
        for name in plugin_names:
            result = self.execute(name, result)
        return result


def main() -> None:
    core = TextProcessorCore()

    print("--- Register plugins ---")
    core.register(UpperCasePlugin())
    core.register(ReversePlugin())
    core.register(WordCountPlugin())
    core.register(CaesarCipherPlugin(3))

    print(f"\n--- Available plugins: {core.list_plugins()} ---")

    text = "Hello World from Microkernel"

    print(f"\n--- Execute individual plugins on: '{text}' ---")
    for name in core.list_plugins():
        result = core.execute(name, text)
        print(f"  {name}: {result}")

    print(f"\n--- Pipeline: uppercase -> reverse ---")
    result = core.execute_pipeline(["uppercase", "reverse"], text)
    print(f"  Result: {result}")

    print(f"\n--- Pipeline: caesar -> uppercase ---")
    result = core.execute_pipeline(["caesar", "uppercase"], text)
    print(f"  Result: {result}")

    print("\n--- Unregister 'reverse', try executing ---")
    core.unregister("reverse")
    print(core.execute("reverse", text))
    print(f"  Available: {core.list_plugins()}")


if __name__ == "__main__":
    main()

Output:

--- Register plugins ---
  [Core] Registered plugin: 'uppercase'
  [Core] Registered plugin: 'reverse'
  [Core] Registered plugin: 'wordcount'
  [Core] Registered plugin: 'caesar'

--- Available plugins: ['uppercase', 'reverse', 'wordcount', 'caesar'] ---

--- Execute individual plugins on: 'Hello World from Microkernel' ---
  uppercase: HELLO WORLD FROM MICROKERNEL
  reverse: lenrekorciM morf dlroW olleH
  wordcount: Word count: 4
  caesar: Khoor Zruog iurp Plfurnhuqho

--- Pipeline: uppercase -> reverse ---
  Result: LENREKORCIM MORF DLROW OLLEH

--- Pipeline: caesar -> uppercase ---
  Result: KHOOR ZRUOG IURP PLFURNHUQHO

--- Unregister 'reverse', try executing ---
  [Core] Unregistered plugin: 'reverse'
  [Core] ERROR: Plugin 'reverse' not found
  Available: ['uppercase', 'wordcount', 'caesar']

Go

package main

import (
	"fmt"
	"strings"
)

// --- Plugin interface ---

type Plugin interface {
	Name() string
	Execute(data string) string
}

// --- Plugins ---

type UpperCasePlugin struct{}

func (p *UpperCasePlugin) Name() string            { return "uppercase" }
func (p *UpperCasePlugin) Execute(data string) string { return strings.ToUpper(data) }

type ReversePlugin struct{}

func (p *ReversePlugin) Name() string { return "reverse" }
func (p *ReversePlugin) Execute(data string) string {
	runes := []rune(data)
	for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
		runes[i], runes[j] = runes[j], runes[i]
	}
	return string(runes)
}

type WordCountPlugin struct{}

func (p *WordCountPlugin) Name() string { return "wordcount" }
func (p *WordCountPlugin) Execute(data string) string {
	count := len(strings.Fields(data))
	return fmt.Sprintf("Word count: %d", count)
}

type CaesarCipherPlugin struct {
	Shift int
}

func (p *CaesarCipherPlugin) Name() string { return "caesar" }
func (p *CaesarCipherPlugin) Execute(data string) string {
	result := make([]rune, len([]rune(data)))
	for i, ch := range data {
		if ch >= 'A' && ch <= 'Z' {
			result[i] = rune((int(ch-'A')+p.Shift)%26) + 'A'
		} else if ch >= 'a' && ch <= 'z' {
			result[i] = rune((int(ch-'a')+p.Shift)%26) + 'a'
		} else {
			result[i] = ch
		}
	}
	return string(result)
}

// --- Core ---

type TextProcessorCore struct {
	plugins map[string]Plugin
	order   []string
}

func NewTextProcessorCore() *TextProcessorCore {
	return &TextProcessorCore{plugins: make(map[string]Plugin)}
}

func (c *TextProcessorCore) Register(p Plugin) {
	c.plugins[p.Name()] = p
	c.order = append(c.order, p.Name())
	fmt.Printf("  [Core] Registered plugin: '%s'\n", p.Name())
}

func (c *TextProcessorCore) Unregister(name string) {
	if _, ok := c.plugins[name]; ok {
		delete(c.plugins, name)
		var newOrder []string
		for _, n := range c.order {
			if n != name {
				newOrder = append(newOrder, n)
			}
		}
		c.order = newOrder
		fmt.Printf("  [Core] Unregistered plugin: '%s'\n", name)
	}
}

func (c *TextProcessorCore) ListPlugins() []string {
	return c.order
}

func (c *TextProcessorCore) Execute(name, data string) string {
	p, ok := c.plugins[name]
	if !ok {
		return fmt.Sprintf("  [Core] ERROR: Plugin '%s' not found", name)
	}
	return p.Execute(data)
}

func (c *TextProcessorCore) ExecutePipeline(names []string, data string) string {
	result := data
	for _, name := range names {
		result = c.Execute(name, result)
	}
	return result
}

func main() {
	core := NewTextProcessorCore()

	fmt.Println("--- Register plugins ---")
	core.Register(&UpperCasePlugin{})
	core.Register(&ReversePlugin{})
	core.Register(&WordCountPlugin{})
	core.Register(&CaesarCipherPlugin{Shift: 3})

	fmt.Printf("\n--- Available plugins: %v ---\n", core.ListPlugins())

	text := "Hello World from Microkernel"

	fmt.Printf("\n--- Execute individual plugins on: '%s' ---\n", text)
	for _, name := range core.ListPlugins() {
		result := core.Execute(name, text)
		fmt.Printf("  %s: %s\n", name, result)
	}

	fmt.Println("\n--- Pipeline: uppercase -> reverse ---")
	result := core.ExecutePipeline([]string{"uppercase", "reverse"}, text)
	fmt.Printf("  Result: %s\n", result)

	fmt.Println("\n--- Pipeline: caesar -> uppercase ---")
	result = core.ExecutePipeline([]string{"caesar", "uppercase"}, text)
	fmt.Printf("  Result: %s\n", result)

	fmt.Println("\n--- Unregister 'reverse', try executing ---")
	core.Unregister("reverse")
	fmt.Println(core.Execute("reverse", text))
	fmt.Printf("  Available: %v\n", core.ListPlugins())
}

Output:

--- Register plugins ---
  [Core] Registered plugin: 'uppercase'
  [Core] Registered plugin: 'reverse'
  [Core] Registered plugin: 'wordcount'
  [Core] Registered plugin: 'caesar'

--- Available plugins: [uppercase reverse wordcount caesar] ---

--- Execute individual plugins on: 'Hello World from Microkernel' ---
  uppercase: HELLO WORLD FROM MICROKERNEL
  reverse: lenrekorciM morf dlroW olleH
  wordcount: Word count: 4
  caesar: Khoor Zruog iurp Plfurnhuqho

--- Pipeline: uppercase -> reverse ---
  Result: LENREKORCIM MORF DLROW OLLEH

--- Pipeline: caesar -> uppercase ---
  Result: KHOOR ZRUOG IURP PLFURNHUQHO

--- Unregister 'reverse', try executing ---
  [Core] Unregistered plugin: 'reverse'
  [Core] ERROR: Plugin 'reverse' not found
  Available: [uppercase wordcount caesar]
  • Strategy — each plugin is a strategy; the core selects and executes the appropriate strategy for each operation.
  • Factory — a plugin factory or registry creates and manages plugin instances.
  • Observer — the core can notify plugins of lifecycle events via the observer pattern.
  • Pipe and Filter — microkernel is about pluggable features; pipe and filter is about sequential data transformation steps.