d9e5668cec
Fixes and improvements from exhaustive audit: Consistency fixes in SKILL.md: - 'Pipe & Filter' → 'Pipe and Filter' (one stray '&' found and corrected) - 'Singleton for factory instance' → clarified to 'register factory as singleton-scoped via DI container' (less misleading wording) - Documentation Format section updated with note that SKILL.md itself is the authoritative source for related-pattern combinations Coverage fix — Related Patterns sections: - Added '## Related Patterns' to ALL 94 pattern files (was 0/94) - Each section lists 3–6 related patterns with relationship descriptions - Covers: why they're related, when to prefer one vs the other, and which are often confused SOLID principles → Creational → Structural → Behavioral → Architectural → Concurrency → Functional → Resilience → Data Access → Messaging → Testing → Error Handling → Microservice — all 13 categories covered Code verification: - Python: 0 failures (all 85 testable blocks pass) - Go: 0 failures (all 76 testable blocks pass) - JavaScript: 0 failures (all 78 testable blocks pass) - All 239 code blocks verified correct after edits Final skill state: - 108 files, 36,524 lines across 13 reference categories - 94/94 pattern files have Related Patterns sections - 2,815-line SKILL.md with 67 decision trees, 23 scenarios, 0 broken references, 0 naming inconsistencies
11 KiB
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]
Related Patterns
- 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.