Definition
- Core (microkernel) – The minimal functionality needed to run the application plus the machinery to discover, register, and invoke plugins.
- Plugins – Independent components that each implement a well-defined plugin contract and provide one specific behavior.
- Plugin contract – An interface the core defines; the core never contains feature-specific logic, only the contract.
- Dynamic discovery – Java's
ServiceLoader, classpath scanning, or runtime JAR loading lets plugins be added without recompiling. - Isolation – A buggy plugin can be disabled without affecting others; sandboxing prevents one plugin from crashing the core.
Think of the core banking engine at National Bank of India as a sturdy base that knows how to identify a customer, record a ledger entry, and run whatever product rule it is handed. By itself it offers no products. The actual products (home loan, car loan, credit card, fixed deposit) are plugins. Each follows the same standard contract. When the bank launches a new product, it writes a new plugin and drops it into the core's slot; the core is never rewritten.
Understanding the Concepts
What are the two parts of the microkernel?
The core system contains the minimal functionality and the machinery to discover, register, and invoke plugins. It is deliberately small and stable. The plugins are independent components that each implement a well-defined contract.
package com.bank.core;
public interface ProductPlugin {
/** Does this plugin handle the given product type? */
boolean supports(String productType);
/** Compute the terms for an application. */
ProductTerms computeTerms(LoanApplication application);
/** Human-readable name for logging and audit. */
String getName();
}
How does the core use the plugin contract?
The core holds a registry and delegates to the matching plugin. No giant switch statement.
package com.bank.core;
import java.util.ArrayList;
import java.util.List;
public class ProductEngine {
private final List<ProductPlugin> plugins = new ArrayList<>();
public void register(ProductPlugin plugin) {
plugins.add(plugin);
System.out.println("Registered plugin: " + plugin.getName());
}
public ProductTerms process(LoanApplication application) {
for (ProductPlugin plugin : plugins) {
if (plugin.supports(application.getProductType())) {
return plugin.computeTerms(application);
}
}
throw new IllegalArgumentException(
"No plugin found for: " + application.getProductType());
}
}
How do you implement a concrete plugin?
Each product is a plugin implementing the contract.
package com.bank.products;
import com.bank.core.ProductPlugin;
import com.bank.core.ProductTerms;
import com.bank.core.LoanApplication;
import java.math.BigDecimal;
public class HomeLoanPlugin implements ProductPlugin {
@Override
public boolean supports(String productType) {
return "HOME_LOAN".equals(productType);
}
@Override
public ProductTerms computeTerms(LoanApplication app) {
BigDecimal rate = new BigDecimal("8.50"); // 8.5% annual
int tenureMonths = 240; // 20 years
BigDecimal emi = calculateEmi(app.getAmount(), rate, tenureMonths);
return new ProductTerms(rate, tenureMonths, emi);
}
private BigDecimal calculateEmi(BigDecimal principal, BigDecimal annualRate,
int months) {
// Simplified EMI calculation
BigDecimal monthlyRate = annualRate.divide(BigDecimal.valueOf(1200), 10,
java.math.RoundingMode.HALF_UP);
return principal.multiply(monthlyRate); // simplified
}
@Override
public String getName() { return "Home Loan Plugin"; }
}
package com.bank.products;
import com.bank.core.ProductPlugin;
import com.bank.core.ProductTerms;
import com.bank.core.LoanApplication;
import java.math.BigDecimal;
public class FixedDepositPlugin implements ProductPlugin {
@Override
public boolean supports(String productType) {
return "FIXED_DEPOSIT".equals(productType);
}
@Override
public ProductTerms computeTerms(LoanApplication app) {
BigDecimal rate = new BigDecimal("7.25");
int tenureMonths = 12;
BigDecimal maturityAmount = app.getAmount()
.multiply(BigDecimal.ONE.add(rate.divide(BigDecimal.valueOf(100))));
return new ProductTerms(rate, tenureMonths, maturityAmount);
}
@Override
public String getName() { return "Fixed Deposit Plugin"; }
}
How do you discover plugins dynamically?
Java's ServiceLoader discovers implementations on the classpath without recompiling the core.
package com.bank.core;
import java.util.ServiceLoader;
public class PluginDiscovery {
public static void loadPlugins(ProductEngine engine) {
ServiceLoader<ProductPlugin> loader = ServiceLoader.load(ProductPlugin.class);
for (ProductPlugin plugin : loader) {
engine.register(plugin);
}
}
// META-INF/services/com.bank.core.ProductPlugin contains:
// com.bank.products.HomeLoanPlugin
// com.bank.products.FixedDepositPlugin
//
// Adding a new product: write the class, add to the services file.
// Core is never touched.
}
How do you put it all together?
Register plugins and process applications.
package com.bank;
import com.bank.core.*;
import com.bank.products.*;
import java.math.BigDecimal;
public class BankingApp {
public static void main(String[] args) {
ProductEngine engine = new ProductEngine();
engine.register(new HomeLoanPlugin());
engine.register(new FixedDepositPlugin());
LoanApplication app = new LoanApplication(
"CUST-RAJESH-001", "HOME_LOAN", new BigDecimal("5000000"));
ProductTerms terms = engine.process(app);
System.out.println("Product: " + app.getProductType());
System.out.println("Rate: " + terms.getRate() + "%");
System.out.println("Tenure: " + terms.getTenureMonths() + " months");
System.out.println("EMI: " + terms.getAmount());
}
}
Registered plugin: Home Loan Plugin
Registered plugin: Fixed Deposit Plugin
Product: HOME_LOAN
Rate: 8.50%
Tenure: 240 months
EMI: 35416.6666666666
Where does the microkernel architecture fit?
| System | Core | Plugins |
|---|---|---|
| IDE (IntelliJ, VS Code) | Editor, file management | Language support, debuggers, themes |
| CMS (WordPress) | Content engine, routing | Content types, workflows, widgets |
| Payment processor | Transaction engine | Card, UPI, net banking, wallet |
| Rules engine | Rule evaluator | Domain-specific rules |
| Banking product engine | Ledger, customer, KYC | Home loan, FD, credit card plugins |
What are the trade-offs and senior nuances?
Designing the plugin contract well is the most important and hardest decision. Versioning requires care.
package com.bank.core;
public interface ProductPlugin {
boolean supports(String productType);
ProductTerms computeTerms(LoanApplication application);
String getName();
// Contract evolution: add default method for backward compatibility
default boolean isEligible(String customerId) {
return true; // existing plugins do not break
}
}
package com.bank.core.antipattern;
// WRONG: changing the plugin contract without a default method
// All existing plugins must be updated simultaneously.
// Defeats the purpose of the architecture.
What are common interview follow-up questions?
How would you handle plugin dependencies (plugin A needs plugin B)? How do you sandbox third-party plugins? How do you test plugins in isolation? What is the difference between microkernel architecture and microservices? How do you handle cross-plugin communication?
Visual Overview
┌──────────────────────────────────────────────────────────┐ │ MICROKERNEL / PLUGIN ARCHITECTURE │ ├──────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────────────────────────┐ │ │ │ CORE │ │ │ │ ┌────────────────────────────┐ │ │ │ │ │ Plugin Contract (Interface)│ │ │ │ │ │ - supports(type) │ │ │ │ │ │ - computeTerms(app) │ │ │ │ │ └────────────────────────────┘ │ │ │ │ ┌────────────────────────────┐ │ │ │ │ │ Plugin Registry │ │ │ │ │ │ - register(plugin) │ │ │ │ │ │ - findFor(type) │ │ │ │ │ └────────────────────────────┘ │ │ │ │ ┌────────────────────────────┐ │ │ │ │ │ Engine (dispatch + lifecycle│ │ │ │ │ └────────────────────────────┘ │ │ │ └──────────────────────────────────────┘ │ │ │ │ │ │ │ │ ┌──────▼───┐ ┌───▼────┐ ┌──▼─────┐ ┌▼──────────┐ │ │ │Home Loan │ │ FD │ │Car Loan│ │Credit Card│ │ │ │ Plugin │ │Plugin │ │Plugin │ │ Plugin │ │ │ └──────────┘ └────────┘ └────────┘ └───────────┘ │ │ │ │ New product = new plugin, core never touched │ │ Buggy plugin = disable just that one │ └──────────────────────────────────────────────────────────┘
Key Takeaway
The microkernel architecture splits a system into a minimal, stable core and a set of independent plugins. The core provides essential common functionality plus a registry and lifecycle for plugins, and importantly the plugin contract (an interface) that every plugin must implement. The core contains no feature-specific logic: it holds a registry, and when a request arrives, it asks each plugin whether it supports it and delegates to the matching one.
At National Bank of India, Pankaj designed the banking product engine this way. The core was a ProductEngine holding a registry of ProductPlugin instances, and each product (home loan, fixed deposit, car loan, credit card) was a plugin implementing the contract. Launching a new product meant writing a new plugin and registering it via ServiceLoader; the core was never touched and existing products were never at risk. When a pricing bug hit one product, Pankaj disabled just that plugin while the others kept serving.
The architecture fits anywhere the system is fundamentally a stable engine plus many growing variations: IDEs, browsers, CMS platforms, rules engines, and payment processors. The trade-offs are that designing the plugin contract is the hardest and most important decision because a leaky contract forces core changes later, versioning must be handled carefully with default methods for backward compatibility, and third-party plugins may need sandboxing for security.
Remember one sentence: Keep a small stable core that defines a plugin contract and a registry, and deliver every new feature as a new plugin so the system grows endlessly while the critical core stays untouched.
---