Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Advanced
  • 30 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Go 1.27.1, Clang 21.0.0 and Rust 1.99.0
  • By MySmartCoPilot
Free intro, Pro: 7 of 30 minutes free
Free: How a core finds its plugins. With a pass: the other 7 sections and 3 interview answers, about 23 minutes.

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.

Discovery finds the plugins, the plugins implement one versioned interface, and the core calls them only through it.Discoveryregistration, metadata,imports or processesPluginsfile formats, payment methods,editor commandsPlugin interface, version 1.4name, api, hooksCoreregistry, version check,lifecyclefindsimplementthe core calls pluginsonly through it

The three parts of a microkernel, and discovery

Text description of the diagram

The diagram is a chain of four boxes, from the top.

  1. Discovery, which can be explicit registration, package metadata, imports or separate processes, finds the plugins.
  2. The plugins, such as file formats, payment methods and editor commands, implement the plugin interface.
  3. The plugin interface, version 1.4, consists of a name, an API version and hooks.
  4. 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.metadata for 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/services files on the class path.
  • Go’s compile-time registration: a plugin package calls Register from init, 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 unsafe code.
  • 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 core that knows no plugin by name Python · core/registry.py
"""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

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.

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

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.