Design Patterns (GoF and modern) Module 7 – Modern patterns for resources, runtime and composition
Plugins and the microkernel pattern
A small core with a stable, versioned plugin interface: how cores find plugins in Python, Java, Go, Rust, C++ and TypeScript, and how they contain bad ones.
What you will learn
- Design a small core with a stable, versioned plugin interface
- Discover plugins with Python entry points, Java ServiceLoader and explicit registration
- Compare compile-time registration with runtime loading in Go and Rust
- Isolate third-party plugins as untrusted code, with limits on time, size and privileges
Before you start
What you’ll learn · Before you start
What you will learn
- Design a small core with a stable, versioned plugin interface
- Discover plugins with Python entry points, Java ServiceLoader and explicit registration
- Compare compile-time registration with runtime loading in Go and Rust
- Isolate third-party plugins as untrusted code, with limits on time, size and privileges
Before you start
On this page
A plugin architecture keeps a small core that knows how to run extensions but not what they do, and puts each optional feature in a plugin that the core finds and loads when it is configured. The microkernel pattern applies that idea to a whole system: the core holds only what every installation needs, a registry and one stable interface, while file formats, payment methods or editor commands live in plugins behind it. Martin Fowler’s catalogue sums up the Plugin pattern as linking classes “during configuration rather than compilation”. Reach for a plugin architecture when other people or other teams must add features without editing or rebuilding the core. Skip it when every feature ships with the core anyway: the interface then only adds a layer, and the plugins are just modules with extra ceremony.
How a core finds its plugins
Every microkernel pattern has three parts: the core, a stable plugin interface the core defines, and a way to discover which plugins exist.
The three parts of a microkernel, and discovery
Text description of the diagram
The diagram is a chain of four boxes, from the top.
- Discovery, which can be explicit registration, package metadata, imports or separate processes, finds the plugins.
- The plugins, such as file formats, payment methods and editor commands, implement the plugin interface.
- The plugin interface, version 1.4, consists of a name, an API version and hooks.
- The core, which holds the registry, checks each plugin's version and manages the plugins' lifecycle, calls the plugins only through that interface, never through a plugin's own classes.
Discovery is where languages differ most. The common ways, each with one sentence on what it does:
- Explicit registration: each plugin calls the core’s register function, or the application lists its plugins in one place.
- Python entry points: an installed package names its plugins in its metadata, and the core asks
importlib.metadatafor one group. - Subclass registries:
__init_subclass__adds every subclass of a base class to a registry when its class statement runs. - Java’s ServiceLoader: it finds the classes that implement an interface, listed in
META-INF/servicesfiles on the class path. - Go’s compile-time registration: a plugin package calls
Registerfrominit, and a blank import includes it, as database drivers do. - Go’s plugin package: it loads compiled plugins while the program runs, but only on Linux, FreeBSD and macOS.
- Rust trait objects and Cargo features: they choose plugins when the program is built; loading code at run time
needs
unsafecode. - A separate process or a WebAssembly module: it keeps a third-party plugin’s crash or hang away from the core.
Here is the smallest working core, with explicit registration. The core’s run() never names a plugin; three plugins
register themselves with a decorator:
"""A tiny microkernel: a core that formats text knows nothing about any formatting rule. Rules are plugins that
register themselves with the core's registry, and the core runs the ones a user asks for, by name."""
import re
import unicodedata
PLUGINS = {} # the core's registry: name -> function
def plugin(name):
"""Registers the decorated function as the plugin `name` (explicit registration)."""
def register(fn):
if name in PLUGINS:
raise ValueError(f"a plugin named {name!r} is already registered")
PLUGINS[name] = fn
return fn
return register
def run(text, names):
"""The core: apply the named plugins in order. It never mentions a plugin by name itself."""
for name in names:
if name not in PLUGINS:
raise LookupError(f"no plugin named {name!r} (installed: {', '.join(sorted(PLUGINS))})")
text = PLUGINS[name](text)
return text
# ---- plugins: each could live in its own module; importing the module registers it ----
@plugin("upper")
def upper(text):
return text.upper()
@plugin("ascii")
def ascii_letters(text):
"""Transliterates accented Latin letters to plain ones: é -> e."""
return unicodedata.normalize("NFKD", text).encode("ascii", "ignore").decode()
@plugin("slug")
def slug(text):
return re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-")
title = "Crème Brûlée & Café Menu"
print("installed:", ", ".join(sorted(PLUGINS)))
print("ascii, slug:", run(title, ["ascii", "slug"]))
print("ascii, upper:", run(title, ["ascii", "upper"]))
try:
run(title, ["emoji"])
except LookupError as e:
print("emoji:", e) Output
installed: ascii, slug, upper ascii, slug: creme-brulee-cafe-menu ascii, upper: CREME BRULEE & CAFE MENU emoji: no plugin named 'emoji' (installed: ascii, slug, upper)
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 registry.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Asking for ascii then slug turned “Crème Brûlée & Café Menu” into creme-brulee-cafe-menu, ready for a web
address, and a plugin nobody installed failed with the list of the ones that exist. More plugins mean more registered functions, never a change
to run().
A stable, versioned plugin interface Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- Finding installed plugins with entry points and subclass registries
- Five more languages: run time, class path and compile time
- Third-party plugins are untrusted code
- Check yourself
- Practise: a registry with versions, hooks and isolated failures
- A plugin registry with versions, hooks and isolated failures
- Interview questions
- A desktop editor has 300 third-party plugins built against version 1 of its plugin interface. One hook's signature must change to support a new feature. How do you ship the change without breaking the plugins?
- A command-line tool written in Go needs plugins written by your team and by customers. Compare compile-time registration, Go's plugin package and plugins as separate programs, and choose one for each group.
- A document service lets users upload formatting plugins and runs them on the server against other users' documents. What can go wrong, and how do you run such plugins safely?
- Key takeaways
4 runnable examples (META-INF/services/core.TextPlugin, Main.java, Python, languages_session.py, loader.ts, main.go, main.rs, registry.h, session.py, site/text_extras/__init__.py, untrusted/crash.py, untrusted/greedy.py, untrusted/spin.py, untrusted/title.py) · 1 exercise with sample tests · 3 interview answers · about 19 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
References
- Patterns of Enterprise Application Architecture: Plugin (Martin Fowler)
- importlib.metadata: accessing package metadata (entry points) (Python Software Foundation)
- PEP 487: Simpler customisation of class creation (Python Software Foundation)
- Class ServiceLoader (Java SE 25) (Oracle)
- Go standard library: package plugin (The Go Authors)
- Go standard library: package database/sql (Register) (The Go Authors)
- The Cargo Book: features (The Rust Project)
- subprocess: subprocess management (Python Software Foundation)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress