| title | Plugin Ecosystem Architecture |
|---|---|
| description | Building an interoperable plugin ecosystem for ObjectStack with vendor-agnostic protocols |
ObjectStack uses a MicroKernel Architecture where all functionality is implemented through plugins. This document defines a comprehensive plugin ecosystem specification that ensures plugins from different vendors can call each other, collaborate, and compose together.
Plugins declare capabilities through implementing Protocols rather than hard-coded dependencies, similar to:
- Kubernetes CRDs (Custom Resource Definitions)
- OSGi Service Registry
- Eclipse Extension Points
All protocols and plugins must use Semantic Versioning (SemVer) for compatibility management:
- Major: Breaking changes
- Minor: Backwards-compatible feature additions
- Patch: Backwards-compatible bug fixes
All plugins and protocols use reverse domain notation to ensure global uniqueness:
{domain}.{category}.{name}
Examples:
- Plugin:
com.acme.crm.customer-management - Protocol:
com.objectstack.protocol.storage.v1 - Interface:
com.acme.crm.interface.contact_service
Plugins communicate through Interfaces and Extension Points, not direct implementation dependencies.
A Protocol defines a standardized set of capabilities. Plugins declare which protocols they implement.
import { PluginCapabilityManifest } from '@objectstack/spec/system';
const capabilities: PluginCapabilityManifest = {
implements: [
{
protocol: {
id: 'com.objectstack.protocol.storage.v1',
label: 'Storage Protocol v1',
version: { major: 1, minor: 0, patch: 0 },
},
conformance: 'full', // full | partial | experimental | deprecated
certified: true,
},
],
};| Level | Description |
|---|---|
full |
Complete implementation of all protocol features |
partial |
Subset implementation with specific features listed |
experimental |
Unstable/preview implementation |
deprecated |
Still supported but scheduled for removal |
An Interface defines the service contract a plugin provides. Other plugins can call these interfaces.
const capabilities: PluginCapabilityManifest = {
provides: [
{
id: 'com.acme.crm.interface.contact_service',
name: 'ContactService',
version: { major: 1, minor: 0, patch: 0 },
stability: 'stable', // stable | beta | alpha | experimental
methods: [
{
name: 'getContact',
description: 'Retrieve a contact by ID',
parameters: [
{ name: 'id', type: 'string', required: true },
],
returnType: 'Contact',
async: true,
},
{
name: 'createContact',
parameters: [
{ name: 'data', type: 'ContactInput', required: true },
],
returnType: 'Contact',
async: true,
},
],
events: [
{
name: 'contactCreated',
description: 'Fired when a new contact is created',
payload: 'Contact',
},
],
},
],
};Plugins declare dependencies on other plugins and specify required capabilities.
const capabilities: PluginCapabilityManifest = {
requires: [
{
pluginId: 'com.objectstack.driver.postgres',
version: '^1.0.0',
optional: false,
requiredCapabilities: [
'com.objectstack.protocol.storage.v1',
'com.objectstack.protocol.transactions.v1',
],
},
{
pluginId: 'com.acme.analytics',
version: '>=2.0.0',
optional: true,
reason: 'Enhanced analytics features',
},
],
};Extension Points allow plugins to declare where other plugins can extend functionality.
const capabilities: PluginCapabilityManifest = {
extensionPoints: [
{
id: 'com.acme.crm.extension.contact_validator',
name: 'Contact Validator',
type: 'validator', // action | hook | widget | provider | transformer | validator | decorator
cardinality: 'multiple', // single | multiple
contract: {
input: 'Contact',
output: 'ValidationResult',
},
},
],
extensions: [
{
targetPluginId: 'com.acme.crm',
extensionPointId: 'com.acme.crm.extension.contact_validator',
implementation: './validators/email-validator.ts',
priority: 50,
},
],
};| Type | Purpose |
|---|---|
action |
Executable actions |
hook |
Lifecycle event hooks |
widget |
UI components |
provider |
Data/service providers |
transformer |
Data transformers |
validator |
Data validators |
decorator |
Functionality decorators |
ObjectStack uses different naming conventions to distinguish between package-level identifiers (for distribution and installation) and code-level identifiers (for services and data access).
Format:
{vendor-domain}.{category}.{plugin-name}
Rules:
- Use reverse domain notation
- All lowercase
- Use hyphens for word separation (NPM package naming convention)
- Avoid underscores and camelCase
Examples:
✅ com.objectstack.driver.postgres
✅ com.acme.crm.customer-management
✅ org.apache.kafka.connector
❌ com.acme.CRM (Do not use uppercase)
❌ com.acme.crm_plugin (Do not use underscores)
Format:
{vendor-domain}.protocol.{category}.{name}.v{major}
Examples:
com.objectstack.protocol.storage.v1
com.objectstack.protocol.auth.oauth2.v2
com.acme.protocol.payment.stripe.v1
Format:
{plugin-id}.interface.{interface-name}
Rules:
- Use snake_case for interface names (consistent with service names in code)
- Follow ObjectStack data layer naming convention
Examples:
✅ com.acme.crm.interface.contact_service
✅ com.acme.analytics.interface.metrics_collector
✅ com.acme.auth.interface.user_provider
Format:
{plugin-id}.extension.{extension-name}
Rules:
- Use snake_case for extension names (consistent with method/function naming)
Examples:
✅ com.acme.crm.extension.contact_validator
✅ com.acme.app.extension.theme_provider
✅ com.acme.crm.extension.customer_enrichment
| Type | Format | Separator | Example |
|---|---|---|---|
| Plugin ID | {domain}.{category}.{name} |
kebab-case (hyphens) | com.acme.crm.customer-management |
| Protocol ID | {domain}.protocol.{name}.v{N} |
kebab-case | com.objectstack.protocol.storage.v1 |
| Interface ID | {plugin}.interface.{name} |
snake_case | com.acme.crm.interface.contact_service |
| Extension ID | {plugin}.extension.{name} |
snake_case | com.acme.crm.extension.contact_validator |
Important Notes:
- Package-level identifiers (plugins, protocols) use kebab-case, following NPM package naming convention
- Code-level identifiers (interfaces, extensions) use snake_case, consistent with ObjectStack data layer naming
The ObjectStack Hub provides a centralized plugin registry that supports:
- Plugin publishing and version management
- Dependency resolution
- Capability searching
- Quality scoring
- Security scanning
import { PluginRegistryEntry } from '@objectstack/spec/hub';
const registryEntry: PluginRegistryEntry = {
id: 'com.acme.crm.customer-management',
version: '1.2.3',
name: 'Customer Management Plugin',
description: 'Comprehensive customer relationship management',
category: 'data',
tags: ['crm', 'customer', 'sales'],
vendor: {
id: 'com.acme',
name: 'ACME Corporation',
verified: true,
trustLevel: 'verified',
},
capabilities: { /* ... */ },
quality: {
testCoverage: 85,
documentationScore: 90,
securityScan: {
passed: true,
vulnerabilities: { critical: 0, high: 0, medium: 0, low: 0 },
},
},
statistics: {
downloads: 15000,
ratings: { average: 4.5, count: 120 },
},
};import { PluginSearchFilters } from '@objectstack/spec/hub';
const filters: PluginSearchFilters = {
query: 'CRM',
category: ['data', 'integration'],
implementsProtocols: ['com.objectstack.protocol.storage.v1'],
trustLevel: ['official', 'verified'],
minRating: 4.0,
sortBy: 'downloads',
};The plugin registry supports multiple trust levels:
| Trust Level | Description |
|---|---|
official |
Official ObjectStack plugins |
verified |
Verified vendors |
community |
Community contributions |
unverified |
Unverified vendors |
quality: {
testCoverage: 85,
documentationScore: 90,
codeQuality: 88,
securityScan: {
lastScanDate: '2024-01-15T00:00:00Z',
vulnerabilities: {
critical: 0,
high: 0,
medium: 1,
low: 3,
},
passed: true,
},
conformanceTests: [
{
protocolId: 'com.objectstack.protocol.storage.v1',
passed: true,
totalTests: 150,
passedTests: 150,
},
],
}Plugins must declare required permissions in their manifest:
// objectstack.config.ts
export default {
id: 'com.acme.analytics',
permissions: [
'system.user.read',
'system.data.write',
'network.http.request',
'storage.local.write',
],
};Plugin A invokes services provided by Plugin B.
// Plugin B: Provides Interface
export class ContactServicePlugin implements Plugin {
name = 'com.acme.crm';
async init(ctx: PluginContext) {
ctx.registerService('contact-service', {
async getContact(id: string): Promise<Contact> {
// Implementation
},
});
}
}
// Plugin A: Uses Interface
export class ReportingPlugin implements Plugin {
name = 'com.acme.reporting';
dependencies = ['com.acme.crm'];
async start(ctx: PluginContext) {
const contactService = ctx.getService('contact-service');
const contact = await contactService.getContact('123');
}
}Plugins communicate through publish/subscribe pattern.
// Plugin A: Publishes Events
await ctx.trigger('crm:contact:created', {
contactId: '123',
data: contact,
});
// Plugin B: Subscribes to Events
ctx.hook('crm:contact:created', async (event) => {
console.log('New contact:', event.data);
});Plugin A contributes extensions to Plugin B.
// Plugin B: Defines Extension Point
capabilities: {
extensionPoints: [{
id: 'com.acme.crm.extension.contact_validator',
type: 'validator',
contract: {
input: 'Contact',
output: 'ValidationResult',
},
}],
}
// Plugin A: Contributes Extension
capabilities: {
extensions: [{
targetPluginId: 'com.acme.crm',
extensionPointId: 'com.acme.crm.extension.contact_validator',
implementation: './validators/email-validator.ts',
}],
}// objectstack.config.ts
import { ObjectStackManifest } from '@objectstack/spec/system';
const manifest: ObjectStackManifest = {
id: 'com.acme.crm',
version: '1.0.0',
type: 'plugin',
name: 'ACME CRM Core',
capabilities: {
implements: [
{
protocol: {
id: 'com.objectstack.protocol.storage.v1',
label: 'Storage Protocol',
version: { major: 1, minor: 0, patch: 0 },
},
conformance: 'full',
},
],
provides: [
{
id: 'com.acme.crm.interface.customer_service',
name: 'CustomerService',
version: { major: 1, minor: 0, patch: 0 },
methods: [
{
name: 'getCustomer',
parameters: [{ name: 'id', type: 'string' }],
returnType: 'Customer',
async: true,
},
],
},
],
extensionPoints: [
{
id: 'com.acme.crm.extension.customer_enrichment',
name: 'Customer Data Enrichment',
type: 'transformer',
cardinality: 'multiple',
},
],
},
objects: ['./src/objects/*.object.ts'],
};
export default manifest;// objectstack.config.ts
const manifest: ObjectStackManifest = {
id: 'com.acme.crm.email-integration',
version: '1.0.0',
type: 'plugin',
name: 'Email Integration for CRM',
capabilities: {
requires: [
{
pluginId: 'com.acme.crm',
version: '^1.0.0',
requiredCapabilities: [
'com.acme.crm.interface.customer_service',
],
},
],
implements: [
{
protocol: {
id: 'com.objectstack.protocol.email.v1',
label: 'Email Protocol',
version: { major: 1, minor: 0, patch: 0 },
},
conformance: 'full',
},
],
extensions: [
{
targetPluginId: 'com.acme.crm',
extensionPointId: 'com.acme.crm.extension.customer_enrichment',
implementation: './enrichment/email-enricher.ts',
priority: 100,
},
],
},
};
export default manifest;const manifest: ObjectStackManifest = {
id: 'com.acme.crm.analytics',
version: '2.0.0',
type: 'plugin',
name: 'CRM Analytics',
capabilities: {
requires: [
{
pluginId: 'com.acme.crm',
version: '^1.0.0',
},
],
provides: [
{
id: 'com.acme.crm.interface.analytics_service',
name: 'AnalyticsService',
version: { major: 2, minor: 0, patch: 0 },
methods: [
{
name: 'getMetrics',
parameters: [
{ name: 'query', type: 'MetricsQuery' },
],
returnType: 'MetricsResult',
async: true,
},
],
events: [
{
name: 'metricsUpdated',
payload: 'MetricsSnapshot',
},
],
},
],
},
};
export default manifest;- Use semantic versioning
- Protocols should be stable and backwards compatible
- Provide clear protocol documentation and examples
- Define explicit test specifications
- Exposing implementation details in protocols
- Frequent breaking changes
- Undocumented protocols
// Explicitly declare dependencies
requires: [{
pluginId: 'com.objectstack.driver.postgres',
version: '^1.0.0',
requiredCapabilities: ['com.objectstack.protocol.storage.v1'],
}]
// Use extension points instead of hard-coding
extensionPoints: [{
id: 'com.acme.app.extension.authentication',
type: 'provider',
cardinality: 'single',
}]
// Provide clear interfaces
provides: [{
id: 'com.acme.interface.user_service',
methods: [/* well-documented methods */],
stability: 'stable',
}]// ❌ Don't directly import other plugins
import { UserService } from '@acme/other-plugin';
// ❌ Don't use global state
global.myPluginData = {};
// ❌ Don't hard-code plugin identifiers
const otherId = 'other-plugin'; // Should use id from manifest// ✅ Use SemVer ranges
version: '^1.0.0' // >= 1.0.0 < 2.0.0
version: '~1.2.3' // >= 1.2.3 < 1.3.0
version: '>=2.0.0 <3.0.0'
// ✅ Mark deprecations
deprecated: true,
deprecationMessage: 'Use com.acme.crm.v2 instead',
replacedBy: 'com.acme.crm.v2',// Protocol Conformance Tests
describe('Storage Protocol Conformance', () => {
it('should implement all required methods', async () => {
const driver = new MyDriver();
expect(driver.query).toBeDefined();
expect(driver.insert).toBeDefined();
// ...
});
});
// Integration Tests
describe('Plugin Integration', () => {
it('should work with CRM plugin', async () => {
const kernel = new ObjectKernel();
kernel.use(new CRMPlugin());
kernel.use(new MyPlugin());
await kernel.bootstrap();
// Test inter-plugin communication
});
});The ObjectStack plugin ecosystem ensures vendor interoperability through:
- Protocol-First Design - Plugins communicate through protocols not implementations
- Reverse Domain Naming - Ensures globally unique identifiers
- Capability Declaration - Explicit interface and dependency declarations
- Extension Points - Standardized extension mechanisms
- Version Management - Semantic versioning
- Quality Assurance - Testing, certification, and scoring systems
- Centralized Registry - Plugin discovery and dependency resolution
By following these specifications, plugins from different vendors can:
- 🔍 Discover and depend on each other
- 🤝 Safely invoke each other's services
- 🔌 Flexibly compose and extend
- 📈 Continuously evolve without breaking compatibility