Keep a minimal core system, and add all further functionality through plug-ins registered into it.
A monolithic core that directly contains every feature — every file format it can export, every integration it supports — grows without bound, and shipping a fix for one feature means redeploying (and re-testing) the entire application, including features that had nothing to do with the change.
The core system implements only the minimal mechanics — a plug-in registry and the ability to invoke a plug-in at defined extension points. Each feature is a separate plug-in implementing an agreed interface, registered into the core. Plug-ins can be added, removed, or updated independently of the core and each other.
The core doesn't know PDF or CSV export exist — it only knows the plug-in interface every export plug-in agrees to implement.
// Core system: knows only the plug-in contract, nothing feature-specific.
class Core {
#plugins = new Map();
register(name, plugin) { this.#plugins.set(name, plugin); }
export(name, data) {
const plugin = this.#plugins.get(name);
if (!plugin) throw new Error(`no plug-in registered for "${name}"`);
return plugin.export(data);
}
}
// Plug-ins: each independently implements the same interface.
const pdfPlugin = { export: (data) => `%PDF-1.4 ... ${JSON.stringify(data)}` };
const csvPlugin = { export: (data) => Object.values(data).join(",") };
const core = new Core();
core.register("pdf", pdfPlugin);
core.register("csv", csvPlugin);
console.log(core.export("csv", { name: "Alex", age: 30 }));
// Adding a new format later: one new plug-in, zero core changes.