MicroKernel Architecture
Understanding ObjectStack's micro-kernel plugin architecture for building extensible applications
MicroKernel Architecture
ObjectStack uses a micro-kernel architecture that separates core functionality from business logic. Like the Linux kernel, the ObjectKernel provides minimal essential services while all features are loaded as plugins.
Overview
The MicroKernel architecture enables:
- β Pluggable ObjectQL instances (bring your own query engine)
- β Service registry for dependency injection (DI)
- β Standardized plugin lifecycle (init/start/destroy)
- β Event-driven communication between plugins
- β Easy testing with mockable services
Architecture Diagram
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ObjectKernel (Core) β
β β’ Plugin Lifecycle Manager β
β β’ Service Registry (DI Container) β
β β’ Event Bus (Hook System) β
β β’ Dependency Resolver β
ββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββ΄βββββββββ¬βββββββββββββ¬βββββββββββ
β β β β
ββββββΌββββββ βββββββΌββββββ ββββΌββββ βββββΌβββββ
β ObjectQL β β Driver β β Hono β β Custom β
β Plugin β β Plugin β βServerβ β Plugin β
ββββββββββββ βββββββββββββ ββββββββ ββββββββββCore Components
1. ObjectKernel
The kernel manages plugin lifecycle and provides core services.
Location: packages/core/src/kernel.ts
API:
import { ObjectKernel } from '@objectstack/core';
import { DriverPlugin } from '@objectstack/runtime';
import { ObjectQLPlugin } from '@objectstack/objectql';
const kernel = new ObjectKernel();
// Register plugins
kernel.use(new ObjectQLPlugin())
.use(new DriverPlugin(driver, 'memory'));
// Start the kernel
await kernel.bootstrap();
// Access services
const ql = kernel.context.getService('objectql'); // Or via getKernel() if exposed
// Shutdown
// await kernel.shutdown(); // (Future)2. Plugin Interface
All plugins must implement the Plugin interface.
Location: packages/core/src/types.ts
Definition:
interface Plugin {
name: string;
version?: string;
dependencies?: string[];
init?(ctx: PluginContext): Promise<void> | void;
start?(ctx: PluginContext): Promise<void> | void;
destroy?(): Promise<void> | void;
}3. Plugin Lifecycle
ββββββββ
β idle β
ββββ¬ββββ
β kernel.use(plugin)
βΌ
ββββββββ
β init β β Register services, subscribe to events
ββββ¬ββββ
β kernel.bootstrap()
βΌ
βββββββββ
β start β β Connect to databases, start servers
ββββ¬βββββ
β
βΌ
βββββββββββ
β running β
ββββ¬βββββββ
β kernel.shutdown()
βΌ
βββββββββββ
β destroy β β Clean up resources
βββββββββββ4. PluginContext
The context provides access to kernel services and hooks.
API:
interface PluginContext {
// Service Registry
registerService(name: string, service: any): void;
getService<T>(name: string): T;
// Event System
hook(name: string, handler: Function): void;
trigger(name: string, ...args: any[]): Promise<void>;
// Logger
logger: Logger;
}Built-in Plugins
ObjectQLPlugin
Registers the ObjectQL query engine as a service.
import { ObjectKernel } from '@objectstack/core';
import { ObjectQLPlugin } from '@objectstack/objectql';
const kernel = new ObjectKernel();
// Default ObjectQL instance
kernel.use(new ObjectQLPlugin());
// Or bring your own instance
import { ObjectQL } from '@objectstack/objectql';
const customQL = new ObjectQL({ /* config */ });
kernel.use(new ObjectQLPlugin(customQL));Registers service: objectql
DriverPlugin
Registers a database driver with ObjectQL.
import { DriverPlugin } from '@objectstack/runtime';
import { createMemoryDriver } from '@objectstack/driver-memory';
const driver = createMemoryDriver();
kernel.use(new DriverPlugin(driver, 'memory'));Dependencies: ['com.objectstack.engine.objectql']
HonoServerPlugin
Starts an HTTP server using Hono.
import { HonoServerPlugin } from '@objectstack/plugin-hono-server';
kernel.use(new HonoServerPlugin({ port: 3000 }));Creating Custom Plugins
Basic Plugin Example
import { Plugin, PluginContext } from '@objectstack/runtime';
export class MyPlugin implements Plugin {
name = 'com.mycompany.my-plugin';
dependencies = ['com.objectstack.engine.objectql'];
async init(ctx: PluginContext): Promise<void> {
ctx.logger.info('MyPlugin initializing');
// Register a service
ctx.registerService('my-service', {
doSomething: () => console.log('Hello!'),
});
// Subscribe to events
ctx.hook('kernel:ready', async () => {
ctx.logger.info('Kernel is ready!');
});
}
async start(ctx: PluginContext): Promise<void> {
ctx.logger.info('MyPlugin starting');
// Get other services
const ql = ctx.getService('objectql');
// Register objects, start servers, etc.
}
async destroy(): Promise<void> {
// Clean up resources
}
}Plugin with Dependencies
export class AnalyticsPlugin implements Plugin {
name = 'com.mycompany.analytics';
dependencies = [
'com.objectstack.engine.objectql',
'com.mycompany.my-plugin',
];
async init(ctx: PluginContext): Promise<void> {
// This plugin will init AFTER its dependencies
const myService = ctx.getService('my-service');
myService.doSomething();
}
}Event System
Plugins communicate via events (hooks).
Standard Events
// Kernel lifecycle
'kernel:init' // Before plugins init
'kernel:ready' // After all plugins start
'kernel:shutdown' // Before shutdown
// Data lifecycle
'data:record:beforeCreate' // { table, data }
'data:record:afterCreate' // { table, record }
'data:record:beforeUpdate' // { table, id, data }
'data:record:afterUpdate' // { table, id, record }
'data:record:beforeDelete' // { table, id }
'data:record:afterDelete' // { table, id }
// Server lifecycle
'server:route:register' // { method, path, handler }
'server:ready' // { port, url }
'server:request' // { method, path, query, body }Using Events
// Subscribe to events
ctx.hook('data:record:afterCreate', async (event) => {
console.log('Record created:', event.record);
});
// Trigger events
await ctx.trigger('my:custom:event', { data: 'value' });Dependency Resolution
The kernel automatically resolves plugin dependencies using topological sort.
const kernel = new ObjectKernel();
// These will be initialized in dependency order
kernel.use(new DriverPlugin(driver, 'memory')); // depends on ObjectQL
kernel.use(new ObjectQLPlugin()); // no dependencies
kernel.use(new MyAnalyticsPlugin()); // depends on both
await kernel.bootstrap();
// Order: ObjectQL β Driver β AnalyticsTesting with MicroKernel
Mock Services
import { ObjectKernel } from '@objectstack/runtime';
// Create test kernel
const kernel = new ObjectKernel();
// Register mock ObjectQL
kernel.use({
name: 'objectql-mock',
async init(ctx) {
ctx.registerService('objectql', {
getSchema: () => mockSchema,
query: () => mockData,
});
},
});
// Test your plugin
kernel.use(new MyPlugin());
await kernel.bootstrap();Integration Tests
import { describe, it, expect } from 'vitest';
import { ObjectKernel } from '@objectstack/runtime';
import { ObjectQLPlugin } from '@objectstack/objectql';
describe('MyPlugin', () => {
it('should register service', async () => {
const kernel = new ObjectKernel();
kernel.use(new ObjectQLPlugin());
kernel.use(new MyPlugin());
await kernel.bootstrap();
const service = kernel.getService('my-service');
expect(service).toBeDefined();
await kernel.shutdown();
});
});Configuration-Driven Loading
Load plugins from a configuration file:
// objectstack.config.ts
export default {
plugins: [
{ name: '@objectstack/objectql#ObjectQLPlugin' },
{ name: '@objectstack/driver-memory#MemoryDriverPlugin' },
{ name: '@objectstack/plugin-hono-server#HonoServerPlugin', config: { port: 3000 } },
],
};// Runtime loader
import { loadConfig } from '@objectstack/config';
import { ObjectKernel } from '@objectstack/runtime';
const config = await loadConfig('./objectstack.config.ts');
const kernel = new ObjectKernel();
for (const pluginDef of config.plugins) {
const PluginClass = await import(pluginDef.name);
kernel.use(new PluginClass(pluginDef.config));
}
await kernel.bootstrap();Best Practices
1. Use Dependency Injection
// β Bad: Direct import
import { objectql } from './global-instance';
// β
Good: Get from context
const ql = ctx.getService('objectql');2. Handle Errors in Lifecycle Methods
async init(ctx: PluginContext): Promise<void> {
try {
await this.setupDatabase();
} catch (error) {
ctx.logger.error('Failed to setup database', { error });
throw error; // Kernel will halt bootstrap
}
}3. Clean Up in destroy()
async destroy(): Promise<void> {
// Close connections
await this.db.close();
// Stop timers
clearInterval(this.syncTimer);
// Unsubscribe from events
this.eventUnsubscribe();
}4. Use Meaningful Event Names
// β Bad: Generic names
ctx.hook('update', handler);
// β
Good: Specific, namespaced names
ctx.hook('analytics:metric:updated', handler);Migration from Monolithic Architecture
Before (Monolithic)
import { ObjectQL } from '@objectstack/objectql';
import { createMemoryDriver } from '@objectstack/driver-memory';
const ql = new ObjectQL();
const driver = createMemoryDriver();
await ql.addDriver(driver, 'memory');
await ql.loadObjects([Account, Contact]);After (MicroKernel)
import { ObjectKernel, DriverPlugin } from '@objectstack/runtime';
import { ObjectQLPlugin } from '@objectstack/objectql';
const kernel = new ObjectKernel();
kernel.use(new ObjectQLPlugin());
kernel.use(new DriverPlugin(driver, 'memory'));
kernel.use(new CRMPlugin()); // Loads Account, Contact
await kernel.bootstrap();
const ql = kernel.getService('objectql');Related Documentation
- Writing Plugins - Complete plugin development guide
- ObjectQL Plugin Reference - ObjectQL plugin API
- Server Drivers - Creating custom drivers
Quick Start: See examples/host for a complete MicroKernel implementation.