Alpha Version: You are viewing the ALPHA documentation. This is an experimental version and may contain breaking changes.
Skip to main content

Plugin

For a short summary of a Plugin, see Reventless Components Overview.

Framework Implementation

This component follows the Reventless Component Structure Pattern, using separate files for interface definitions (Plugin.res), builder logic (Plugin_Builder.res), and helper functions (Plugin_Helpers.res).

Overview

A Plugin is the top-level organizational unit in a Reventless application, corresponding to a Bounded Context in Domain-Driven Design (DDD). It serves as both a logical boundary for domain concepts and a deployment unit for infrastructure.

d2 diagram

Purpose and Responsibilities

  • Bounded Context: Establishes clear boundaries for domain concepts, naming, and functionality
  • Deployment Unit: Groups all related components for unified deployment
  • Component Container: Contains and orchestrates Aggregates, ReadModels, Tasks, ExtensionPoints, and Extensions
  • Cross-Plugin Communication: Manages communication with other Plugins via ExtensionPoints and Extensions
  • Health Monitoring: Provides heartbeat signals to the Platform Admin for monitoring

Plugin Structure

A Plugin contains and manages the following components:

ComponentPurposeQuantity
AggregatesBusiness logic and event sourcing0..n
ReadModelsQuery-optimized projections0..n
TasksFile processing and external integrations0..n
ExtensionPointsExternal interface for other Plugins0..n
ExtensionsConsume other Plugins' ExtensionPoints0..n
EventCollectorCentralized event consumption1
HeartbeatHealth monitoring1

Plugin Configuration

A Plugin is configured with the following parameters:

let make: (
~name: string, // Unique plugin name
~heartbeatInterval: int, // Health check interval (seconds)
~extensionPoints: array<module(ExtensionPoint.T)>=?,
~extensions: array<module(Extension.Blueprint)>=?,
~aggregates: array<module(Aggregate.T)>=?,
~readModels: array<module(ReadModel.T)>=?,
~tasks: array<module(Task.T)>=?,
~stateChangeSlices: array<module(StateChangeSlice.T)>=?,
~stateViewSlices: array<module(StateViewSlice.T)>=?,
~automationSlices: array<module(AutomationSlice.T)>=?,
~outboundTranslationSlices: array<module(OutboundTranslationSlice.T)>=?,
~inboundTranslationSlices: array<module(InboundTranslationSlice.T)>=?,
~opts: Pulumi.ComponentResource.options=?,
) => component

Parameters

ParameterTypeDescription
namestringUnique identifier for the Plugin within the system
heartbeatIntervalintInterval in seconds for health check signals
extensionPointsarray<module(ExtensionPoint.T)>ExtensionPoints exposed by this Plugin
extensionsarray<module(Extension.Blueprint)>Extension blueprints — auto-merged by EP and named after the plugin
aggregatesarray<module(Aggregate.T)>Aggregates contained in this Plugin
readModelsarray<module(ReadModel.T)>ReadModels for query projections
tasksarray<module(Task.T)>Tasks for file processing and integrations
stateChangeSlicesarray<module(StateChangeSlice.T)>DCB write-side slices
stateViewSlicesarray<module(StateViewSlice.T)>DCB read-side slices

Plugin Outputs

When deployed, a Plugin produces the following outputs:

type outputs = {
id: Pulumi.Output.t<string>, // Plugin identifier (name@version)
version: Pulumi.Output.t<string>, // Version string
heartbeatInterval: Pulumi.Output.t<int>, // Configured heartbeat interval
eventCollector: Pulumi.Output.t<EventCollector.outputs>, // Event collector outputs
extensionPoints: Pulumi.Output.t<dict<ExtensionPoint.outputs>>,
extensions: Pulumi.Output.t<dict<Extension.outputs>>,
aggregates: Pulumi.Output.t<dict<Aggregate.outputs>>,
readModels: Pulumi.Output.t<dict<ReadModel.outputs>>,
tasks: Pulumi.Output.t<dict<Task.outputs>>,
resolvers: Pulumi.Output.t<array<Reventless.Adapter.resource>>,
heartbeat: Pulumi.Output.t<Heartbeat.outputs>,
}

Internal Communication

Within a Plugin, components communicate through internal messaging infrastructure:

d2 diagram

Communication Rules

  1. Aggregates within the same Plugin can communicate directly via EventMappings
  2. Aggregates in different Plugins must communicate via ExtensionPoints and Extensions
  3. ReadModels receive events through the Plugin's EventCollector
  4. Tasks can publish commands to Aggregates within the same Plugin

Cross-Plugin Communication

Plugins communicate with each other through ExtensionPoints and Extensions:

d2 diagram

Communication Flow

  1. Outgoing Events: Aggregate events are mapped to ExtensionPoint events via ExtensionPointMappings
  2. Event Distribution: ExtensionPoint publishes events to the Platform Admin's Plugin ExtensionPoint
  3. Event Reception: Extensions receive events from ExtensionPoints they subscribe to
  4. Command Generation: Extension mappings transform incoming events to commands for local Aggregates
  5. Command Forwarding: Extensions can also send commands back to ExtensionPoints

Plugin Definition

At runtime, each Plugin registers itself with the Platform Admin using a plugin definition:

type pluginDefinition = {
id: string, // Unique identifier (name@version)
name: string, // Plugin name
version: string, // Version string
extensionPoints: array<extensionPointDefinition>,
extensions: array<extensionDefinition>,
mutable eventCollector: string, // Event collector URN
}

This definition enables:

  • Discovery: Other Plugins can discover available ExtensionPoints
  • Routing: The Platform Admin routes events between Plugins
  • Monitoring: Health status tracking via heartbeats

Example Plugin Setup

You never hand-write a Plugin composition root. The plugin generator (generate-plugin, run by the prebuild script) scans your plugin's src/ folder by component-folder name (Aggregate/, ReadModel/, StateChangeSlice/, ExtensionPoint/, Extension/, Task/, …) and emits src/Plugin.res. That file is committed to git and compiled directly by CI. An optional src/plugin.json sets the plugin name and heartbeat interval.

The generated file exposes a Make(Platform) functor that wires every discovered component with the platform's per-component factories:

src/Plugin.res (generated — do not edit)
// AUTO-GENERATED — do not edit. Run `npm run generate` to update.
module Make = (Platform: ReventlessInfra.Platform.T) => {
// Aggregates
module CustomerAggregate = Platform.Aggregate.Make(
Customer,
Customer_Behavior,
ReventlessInfra.NoEventMappings.Make(Customer),
)

// ReadModels
module CustomersReadModel = Platform.ReadModel.Make(Customers, Customers_Projections)

// ExtensionPoints
module MyDomain_ExtensionPoint = Platform.ExtensionPoint.Make(MyDomain_ExtensionPointMapping)

// Extensions
module OtherDomain_Extension = Platform.Extension.Make(OtherDomain_Extension.Mapping)

// Tasks
module ImportCustomersTask = Platform.Task.Make(ImportCustomers)

let make = (~uiBundleUrl=?) =>
Platform.Plugin.make(
~name="MyDomain",
~heartbeatInterval=5,
~extensionPoints=[module(MyDomain_ExtensionPoint)],
~extensions=[module(OtherDomain_Extension)],
~aggregates=[module(CustomerAggregate)],
~readModels=[module(CustomersReadModel)],
~tasks=[module(ImportCustomersTask)],
// ... any slices, with pluginStructure and uiFragments
)
}

The plugin module is then referenced from the platform as MyDomainPlugin.Plugin.Make(Platform).

Runtime Behavior

Initialization Sequence

d2 diagram

Heartbeat Monitoring

The Plugin sends periodic heartbeat signals to the Platform Admin:

d2 diagram

For how a plugin version moves between Connected / Disconnected / Inactive / Retired at runtime — heartbeat-driven liveness, the deploy overlap window, version supersession and failover, and how it's surfaced in the admin UI — see Plugin Lifecycle.

Best Practices

Plugin Design

  1. Single Responsibility: Each Plugin should represent one bounded context
  2. Cohesive Components: Group related Aggregates and ReadModels together
  3. Clear Boundaries: Use ExtensionPoints to define explicit external interfaces
  4. Version Management: Use semantic versioning for Plugin versions

Cross-Plugin Communication

  1. Minimize Dependencies: Limit the number of Extensions per Plugin
  2. Stable Interfaces: Design ExtensionPoint specs to be stable over time
  3. Event Translation: Always translate internal events to ExtensionPoint events
  4. Loose Coupling: Avoid direct dependencies between Plugin internals

Deployment

  1. Independent Deployment: Design Plugins to be deployable independently
  2. Version Compatibility: Ensure ExtensionPoint compatibility across versions
  3. Health Monitoring: Configure appropriate heartbeat intervals

Pulumi

The Plugin's Pulumi root component is named using the pattern: {name}@{version} and has a type of reventless:Plugin.

Stack Outputs

A Plugin deployment exports the following stack outputs:

  • extensionPoints: Dictionary of ExtensionPoint outputs for cross-stack references
  • pluginDefinition: The plugin's registration information
  • Aggregate - Business logic components within a Plugin
  • ReadModel - Query projections within a Plugin
  • Task - File processing and external integrations
  • ExtensionPoint - External interface for cross-Plugin communication
  • Extension - Consume other Plugins' ExtensionPoints
  • EventCollector - Centralized event consumption
  • Heartbeat - Health monitoring