In a microkernel (plug-in) architecture, what is the difference between the 'core system' and a 'plug-in', and what does each side own?
answer
- core = minimal engine + registry + contract
- plug-ins depend on core, not vice versa (DIP)
- IDE/rule-engine/browser examples
- contract change breaks all plug-ins
basics
~10 sThe 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 sThe 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
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.
Should explain that plug-ins implement a contract the core defines, and that dependency flows from plug-in to core, not the reverse.
Should describe concretely how discovery/registration works (manifest, classpath scan, service loader) and discuss contract stability as an ongoing design concern.
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