Skip to main content

Plugin Architecture

Stratix uses a plugin-based architecture for extensibility and modularity.

Why Plugins?​

  • ✅ Modularity - Add only what you need
  • ✅ Extensibility - Create custom plugins
  • ✅ Dependency Management - Automatic plugin ordering
  • ✅ Lifecycle Management - Initialize, start, stop hooks
  • ✅ Type Safety - Strongly typed plugin contracts

Plugin Interface​

import { Plugin, PluginContext } from '@stratix/core';

export interface Plugin {
readonly metadata: PluginMetadata;

initialize?(context: PluginContext): Promise<void>;
start?(context: PluginContext): Promise<void>;
stop?(): Promise<void>;
healthCheck?(): Promise<HealthCheckResult>;
}

Plugin Metadata​

export interface PluginMetadata {
name: string;
version: string;
description?: string;
dependencies?: string[];
optionalDependencies?: string[];
}

Basic Plugin Example​

import { Plugin, PluginContext } from '@stratix/core';

export class MyPlugin implements Plugin {
readonly metadata = {
name: 'my-plugin',
version: '1.0.0',
description: 'My custom plugin'
};

async initialize(context: PluginContext): Promise<void> {
console.log('Plugin initializing...');

// Register services in container
context.container.register('myService', () => new MyService());
}

async start(context: PluginContext): Promise<void> {
console.log('Plugin starting...');
}

async stop(): Promise<void> {
console.log('Plugin stopping...');
}

async healthCheck(): Promise<HealthCheckResult> {
return {
status: 'healthy',
details: { uptime: process.uptime() }
};
}
}

Plugin Lifecycle​

1. Register​

const app = await ApplicationBuilder.create()
.usePlugin(new MyPlugin())
.build();

2. Initialize​

Called once during application startup:

async initialize(context: PluginContext): Promise<void> {
// Register services
context.container.register('service', () => new Service());

// Load configuration
const config = context.getConfig<MyConfig>();

// Set up resources
this.connection = await createConnection(config);
}

3. Start​

Called after all plugins are initialized:

async start(context: PluginContext): Promise<void> {
// Start servers
await this.server.listen(3000);

// Subscribe to events
context.eventBus.subscribe('user.created', this.handleUserCreated);
}

4. Stop​

Called during graceful shutdown:

async stop(): Promise<void> {
// Close connections
await this.connection.close();

// Stop servers
await this.server.close();
}

Plugin Dependencies​

Plugins can depend on other plugins:

export class DatabasePlugin implements Plugin {
readonly metadata = {
name: 'database',
version: '1.0.0',
dependencies: ['logger'] // Requires logger plugin
};
}

export class CachePlugin implements Plugin {
readonly metadata = {
name: 'cache',
version: '1.0.0',
dependencies: ['database'], // Requires database plugin
optionalDependencies: ['logger'] // Optional logger
};
}

Initialization order:

  1. logger (no dependencies)
  2. database (depends on logger)
  3. cache (depends on database)

Plugin Context​

The PluginContext provides access to:

interface PluginContext {
container: Container;
logger: Logger;
eventBus: EventBus;
getConfig<T>(): T;
}

Container​

Register and resolve services:

async initialize(context: PluginContext): Promise<void> {
context.container.register('database', () => new Database());

const db = context.container.resolve('database');
}

Logger​

Log messages:

async start(context: PluginContext): Promise<void> {
context.logger.info('Plugin started');
context.logger.error('Error occurred', error);
}

Event Bus​

Publish and subscribe to events:

async start(context: PluginContext): Promise<void> {
context.eventBus.subscribe('user.created', async (event) => {
context.logger.info('User created', event);
});
}

Configuration​

Get plugin configuration:

async initialize(context: PluginContext): Promise<void> {
const config = context.getConfig<DatabaseConfig>();
this.connection = await connect(config);
}

Real-World Example: HTTP Plugin​

import { Plugin, PluginContext } from '@stratix/core';
import Fastify, { FastifyInstance } from 'fastify';

export interface HTTPPluginConfig {
port: number;
host?: string;
}

export class HTTPPlugin implements Plugin {
readonly metadata = {
name: 'http',
version: '1.0.0',
description: 'HTTP server plugin',
dependencies: ['logger']
};

private server?: FastifyInstance;

async initialize(context: PluginContext): Promise<void> {
const config = context.getConfig<HTTPPluginConfig>();

this.server = Fastify({
logger: false // Use Stratix logger instead
});

// Register routes
this.server.get('/health', async () => ({
status: 'healthy'
}));

context.logger.info('HTTP plugin initialized');
}

async start(context: PluginContext): Promise<void> {
const config = context.getConfig<HTTPPluginConfig>();

await this.server!.listen({
port: config.port,
host: config.host || '0.0.0.0'
});

context.logger.info(`HTTP server listening on port ${config.port}`);
}

async stop(): Promise<void> {
await this.server?.close();
}

async healthCheck(): Promise<HealthCheckResult> {
return {
status: this.server ? 'healthy' : 'unhealthy',
details: {
port: this.server?.server.address()
}
};
}
}

Best Practices​

1. Clear Metadata​

readonly metadata = {
name: 'my-plugin',
version: '1.0.0',
description: 'What this plugin does'
};

2. Declare Dependencies​

readonly metadata = {
name: 'cache',
dependencies: ['database'],
optionalDependencies: ['logger']
};

3. Graceful Shutdown​

async stop(): Promise<void> {
await this.connection?.close();
await this.server?.close();
}

4. Health Checks​

async healthCheck(): Promise<HealthCheckResult> {
try {
await this.connection.ping();
return { status: 'healthy' };
} catch (error) {
return { status: 'unhealthy', error: error.message };
}
}

Next Steps​