skip to content

Microkernel / Plug-in

A minimal core plus plug-ins that extend it through a defined contract, as in OSGi, IDEs and rule engines. You will learn plug-in registries and lifecycle, and the trade-off between isolating features and the indirection the contract imposes.

part ofSoftware design & architectureoverview, primer and where to startread it →
on this pageshow

questions

6

In a microkernel (plug-in) architecture, what is the difference between the 'core system' and a 'plug-in', and what does each side own?

level: juniorimportance: must knowfreq 55%

answer

  1. core = minimal engine + registry + contract
  2. plug-ins depend on core, not vice versa (DIP)
  3. IDE/rule-engine/browser examples
  4. contract change breaks all plug-ins

basics

~10 s

The core is a small always-on engine that only knows how to load and talk to plug-ins. Plug-ins are separate, swappable pieces of code that add actual features, each following rules the core sets.

solid answer

~40 s

The core system contains the minimum machinery needed to run: process/lifecycle management, a plug-in registry, and a communication mechanism (interfaces, a service locator, or a message bus). It has no knowledge of concrete business features. A plug-in is a self-contained module that implements a contract the core publishes — an interface, an extension point, or a manifest describing what it provides and requires. The core discovers plug-ins (via config, classpath scanning, or explicit registration), instantiates them, and routes calls through the contract rather than concrete classes. This inversion means the core never imports plug-in code, only plug-ins depend on the core's API. Adding, removing, or upgrading a feature means adding/removing/upgrading a plug-in without touching or redeploying the core.

go deeper

for a junior

Should describe the core/plug-in split in plain terms and give one example (e.g. IDE with language plug-ins) without needing to name specific technologies.

for a middle

Should explain that plug-ins implement a contract the core defines, and that dependency flows from plug-in to core, not the reverse.

for a senior

Should describe concretely how discovery/registration works (manifest, classpath scan, service loader) and discuss contract stability as an ongoing design concern.

for a principal

Should discuss contract governance across a plug-in ecosystem over years — versioned extension points, backward compatibility strategy, and how to prevent the core from re-accumulating coupling as it grows.

## The two halves The microkernel pattern (also called plug-in architecture) splits a system into two categories of component with sharply different responsibilities: a **core system** and a set of **plug-ins**. The core system is deliberately kept small — it implements only the minimal functionality required to run the system: starting up, loading configuration, managing the lifecycle of components, and providing a mechanism through which plug-ins can be discovered and invoked. ## What the core owns Concretely, the core typically owns three things: - **(1) a contract** — one or more interfaces, abstract classes, or schema definitions that describe what a plug-in must expose; - **(2) a registry** — a data structure (in-memory map, service registry, OSGi service registry, or a config file listing implementation classes) that tracks which plug-ins are installed and their metadata (id, version, dependencies); and - **(3) a dispatch mechanism** — code in the core that, when a capability is needed, looks the capability up in the registry and invokes it through the contract rather than calling a concrete class directly. ## What a plug-in owns A plug-in, by contrast, is a self-contained unit of feature logic. It implements the contract the core defines, and it typically ships as an independently deployable artifact — a JAR, a DLL, a bundle, an npm package — with its own manifest describing its identity, version, and the extension points it fills or the services it requires from other plug-ins. Crucially, **dependency direction only flows one way**: plug-ins depend on the core's published API, but the core has zero compile-time knowledge of any specific plug-in. This is the same idea as the **Dependency Inversion Principle** applied at the architecture level — the core defines an abstraction, and concrete implementations (plug-ins) are injected or discovered at runtime rather than referenced by name in the core's source. ## Why the split exists Why does this split exist? The motivating problem is a product with a stable, universally-needed nucleus but a long, variable, and unpredictable list of optional features — the classic examples are: - **IDEs** (a text-editing and project-management core plus language/tooling plug-ins), - **rule engines** (a rule-evaluation core plus domain-specific rule packs), and - **browsers** (a rendering/networking core plus extensions). If every feature were compiled directly into one monolith, every new feature would force a full rebuild and redeploy of the whole application, feature teams would step on each other's code, and users who don't need feature X would still pay its footprint and its bug surface. By moving optional capability behind a plug-in boundary, the core can ship once and stay largely frozen, while plug-ins are built, tested, versioned, and released independently — sometimes even by third parties who never see the core's source code at all (this is how Eclipse and IntelliJ extension ecosystems work, and how enterprise software vendors let customers write custom plug-ins without touching vendor code). ## How it looks in practice The concrete mechanics of 'what the core owns vs. what a plug-in owns' usually look like this in a Java-ish stack: the core defines an interface such as `ExportFormat { fun export(doc: Document): ByteArray }` and a registry `ExportFormatRegistry`. At startup the core scans a plug-ins directory (or reads a manifest, or queries a DI container) for classes implementing `ExportFormat`, instantiates each one, and registers it under an id. When the user asks to export as PDF, the core looks up "pdf" in the registry and calls `.export()` on whatever was registered — it never has a `PdfExportFormat` symbol in its own compiled code. The PDF plug-in, in turn, owns all PDF-specific logic, its own dependencies (e.g. a PDF library), and its own versioning; it can be upgraded or removed without recompiling the core. ## The cost of the split The main cost of this split is indirection and discipline: - **The core's contract has to be designed up front and evolved carefully**, because every plug-in depends on it — a breaking interface change breaks every plug-in simultaneously, which is a much larger blast radius than changing an internal class in a monolith. - **There's also a real risk of the core creeping**: if new plug-in requirements keep forcing new methods onto the core's contract, the core stops being 'minimal' and starts re-accumulating the coupling the pattern was meant to remove. A well-run microkernel system therefore needs governance over the contract itself, often versioned extension points (v1/v2 APIs) so old plug-ins keep working while new capability is added — exactly how the Eclipse and IntelliJ platform APIs evolve over years without breaking the plug-in ecosystem outright.

  • How does the core actually find plug-ins at runtime without knowing their class names ahead of time?
    Typically via a discovery mechanism: scanning a designated directory or classpath for artifacts that declare a specific manifest entry or implement a marker interface, reading a config file that lists implementation class names, or using a service-loading facility like Java's ServiceLoader or an OSGi service registry. The core then reflectively instantiates whatever it finds and registers it against the contract's interface, so no plug-in class name ever appears in the core's own source.
  • What happens if two plug-ins need to communicate with each other, not just with the core?
    The core usually mediates this too, exposing a shared event bus, service registry, or extension-point mechanism so plug-ins can publish capabilities other plug-ins consume, rather than plug-ins referencing each other's classes directly. Direct plug-in-to-plug-in dependencies reintroduce tight coupling and defeat the purpose of the pattern, so mature platforms treat inter-plug-in dependencies as first-class, versioned relationships tracked by the core.

Like a power strip (the core) that only knows the shape of a plug socket — it has no idea whether you plug in a lamp, a phone charger, or a blender; each device (plug-in) brings its own function as long as its plug matches the socket shape (the contract).

saying these in an interview costs you the question

  • Says the core imports and calls concrete plug-in classes directly
  • Can't explain why the core has no compile-time reference to plug-ins
  • Thinks 'plug-in' just means 'separate JAR' with no contract behind it
  • Doesn't mention discovery/registry as how plug-ins get found

context

open as a page

When designing the contract between a microkernel core and its plug-ins (the interface plug-ins must implement), what belongs in that contract and what design choices affect how safely plug-ins can be added later?

level: middleimportance: must knowfreq 65%

basics

~20 s

The contract is the fixed set of methods and data a plug-in must provide so the core can call it generically. Keep it small, stable, and versioned so new plug-ins can be added without changing old ones or the core.

open as a page

What concrete benefits does isolating features as separate plug-ins give you, and what concrete costs — performance, complexity, dependency management — does that isolation impose in return?

level: seniorimportance: must knowfreq 60%

basics

~20 s

Splitting features into plug-ins means one broken or slow feature is less likely to crash everything else, and features can be updated separately. The cost is extra complexity: indirect calls are slower and different plug-ins can need conflicting versions of the same library.

open as a page

Walk through what actually happens, step by step, from a plug-in artifact sitting in a directory to it being registered, activated, and callable by the core — and what has to happen for it to be safely unloaded.

level: middleimportance: should knowfreq 50%

basics

~20 s

The core scans for plug-ins, reads their info, loads their code, calls an init/start hook, and adds them to a lookup table the core uses to find them by name. Unloading reverses this: stop the plug-in, free its resources, remove it from the table.

open as a page

Name and briefly contrast how OSGi, an IDE plug-in system like Eclipse's, and a rule engine each implement the core/plug-in split — what does 'core' and 'plug-in' concretely mean in each?

level: seniorimportance: should knowfreq 45%

basics

~20 s

OSGi is a Java framework where 'bundles' are the plug-ins and a service registry is the core. Eclipse's IDE core is the workbench/editor shell, and plug-ins add languages, tools, and views. A rule engine's core evaluates rules against facts, and each rule is the swappable unit.

open as a page

Beyond the general isolation-vs-overhead trade-off, what specific production failure modes have you seen or would you expect in mature microkernel/plug-in systems, and when would you actively steer a team away from this pattern?

level: principalimportance: nice to knowfreq 35%

basics

~20 s

Plug-in systems can fail in tricky ways: a plug-in crashing the whole app if isolation is weak, version conflicts between plug-ins, and slow startup as plug-ins pile up. Skip this pattern if you don't actually have many independent, optional features.

open as a page