Plugin
For a short summary of a Plugin, see Reventless Components Overview.
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.
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:
| Component | Purpose | Quantity |
|---|---|---|
| Aggregates | Business logic and event sourcing | 0..n |
| ReadModels | Query-optimized projections | 0..n |
| Tasks | File processing and external integrations | 0..n |
| ExtensionPoints | External interface for other Plugins | 0..n |
| Extensions | Consume other Plugins' ExtensionPoints | 0..n |
| EventCollector | Centralized event consumption | 1 |
| Heartbeat | Health monitoring | 1 |
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
| Parameter | Type | Description |
|---|---|---|
name | string | Unique identifier for the Plugin within the system |
heartbeatInterval | int | Interval in seconds for health check signals |
extensionPoints | array<module(ExtensionPoint.T)> | ExtensionPoints exposed by this Plugin |
extensions | array<module(Extension.Blueprint)> | Extension blueprints — auto-merged by EP and named after the plugin |
aggregates | array<module(Aggregate.T)> | Aggregates contained in this Plugin |
readModels | array<module(ReadModel.T)> | ReadModels for query projections |
tasks | array<module(Task.T)> | Tasks for file processing and integrations |
stateChangeSlices | array<module(StateChangeSlice.T)> | DCB write-side slices |
stateViewSlices | array<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:
Communication Rules
- Aggregates within the same Plugin can communicate directly via EventMappings
- Aggregates in different Plugins must communicate via ExtensionPoints and Extensions
- ReadModels receive events through the Plugin's EventCollector
- Tasks can publish commands to Aggregates within the same Plugin
Cross-Plugin Communication
Plugins communicate with each other through ExtensionPoints and Extensions:
Communication Flow
- Outgoing Events: Aggregate events are mapped to ExtensionPoint events via ExtensionPointMappings
- Event Distribution: ExtensionPoint publishes events to the Platform Admin's Plugin ExtensionPoint
- Event Reception: Extensions receive events from ExtensionPoints they subscribe to
- Command Generation: Extension mappings transform incoming events to commands for local Aggregates
- 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:
// 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
Heartbeat Monitoring
The Plugin sends periodic heartbeat signals to the Platform Admin:
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
- Single Responsibility: Each Plugin should represent one bounded context
- Cohesive Components: Group related Aggregates and ReadModels together
- Clear Boundaries: Use ExtensionPoints to define explicit external interfaces
- Version Management: Use semantic versioning for Plugin versions
Cross-Plugin Communication
- Minimize Dependencies: Limit the number of Extensions per Plugin
- Stable Interfaces: Design ExtensionPoint specs to be stable over time
- Event Translation: Always translate internal events to ExtensionPoint events
- Loose Coupling: Avoid direct dependencies between Plugin internals
Deployment
- Independent Deployment: Design Plugins to be deployable independently
- Version Compatibility: Ensure ExtensionPoint compatibility across versions
- 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 referencespluginDefinition: The plugin's registration information
Related Components
- 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