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

Adapter Pattern

The Reventless framework separates deploy-time (Pulumi infrastructure creation) from runtime (Lambda execution). Cloud provider packages implement this separation for each component.

The Two-Layer Pattern

Every component adapter consists of two layers:

Deploy-Time Layer

Creates cloud resources and captures their identifiers (ARNs, URLs, table names) as Pulumi.Output.t values.

// Example: EventLog storage adapter (deploy time)
module Make = (Spec: Reventless.EventLog.Spec) => {
// Creates the DynamoDB table during `pulumi up`
let table = aws.dynamodb.Table.make(~name=Spec.name, ...)

// Returns the table ARN wrapped in Pulumi.Output.t
let tableArn: Pulumi.Output.t<string> = table.arn
}

Runtime Layer

Implements the actual data operations. Uses the Pulumi.Output.t values captured during deploy time — Pulumi serializes these into the Lambda bundle with their resolved values.

// Example: EventLog storage runtime
let append = (~events, ~condition) => async {
// tableArn is captured from deploy time; resolved at Lambda startup
let client = DynamoDB.makeClient()
await client->putItem(~tableName=tableArn, ...)
}

Adapter Interfaces Checklist

The following adapter interfaces must be implemented for a complete provider package. Each is defined in packages/reventless/src/components/.

Core Event Sourcing

InterfaceFilePurpose
EventLog_Adapter.StorageEventLog/EventLog_Adapter.resAppend-only event log with optimistic concurrency
EventTopic_Adapter.PublisherEventTopic/EventTopic_Adapter.resPub/sub event delivery
CommandTopic_Adapter.ChannelCommandTopic/CommandTopic_Adapter.resCommand queue with FIFO ordering

Read Models

InterfaceFilePurpose
EventCollector_Adapter.ChannelEventCollector/EventCollector_Adapter.resSubscribe to event topics
QueryDb_Adapter.StorageQueryDb/QueryDb_Adapter.resKey-value store for projections
QueryDb_Adapter.QueryEngineAdapterQueryDb/QueryDb_Adapter.resQuery execution (scan, filter)

Supporting Services

InterfaceFilePurpose
Task_Adapter.StorageTask/Task_Adapter.resBinary object storage
Heartbeat_Adapter.RunnerHeartbeat/Heartbeat_Adapter.resScheduled health checks
Counter_Adapter.StorageCounter/Counter_Adapter.resAtomic counters

DCB (Dynamic Consistency Boundary)

InterfaceFilePurpose
DcbEventLog_Adapter.StorageDcbEventLog/DcbEventLog_Adapter.resTag-based event log

Runtime Environment

The runtime environment adapter provides the Lambda execution context. It is defined in src/adapter/Runtime/Runtime.res in the reventless package.

module type Environment = {
let makeHandler: (
~handler: Runtime.eventHandler<'event, 'context, 'result>,
) => Lambda.handler<'event, 'context, 'result>
}

Cloud provider packages provide their own Runtime.Environment that adapts the generic handler type to the specific Lambda invocation model (e.g., SQS event format for AWS).

Pre-Configured Builder Modules

After implementing all adapter interfaces, export pre-configured builder modules that combine the framework builder functors with your adapters:

// ReventlessMyprovider.res — the package entry point
module Aggregate = {
module Make = (Spec: Reventless.Aggregate.Spec) =>
Aggregate_Builder.Make(Spec, MyRuntimeEnvironment, MyEventLogStorage, MyEventTopicPublisher, MyCommandTopicChannel)
}

module ReadModel = {
module Make = (Spec: Reventless.ReadModel.Spec) =>
ReadModel_Builder.Make(Spec, MyRuntimeEnvironment, MyEventCollectorChannel, MyQueryDbStorage, MyQueryEngineAdapter)
}

// ... etc

This is the API surface for application developers. They call ReventlessMyprovider.Aggregate.Make(MySpec) and get a fully wired Aggregate.T.

The local provider implements the same interfaces

reventless-local implements every adapter interface in this document using in-process data structures — no Pulumi resources, no cloud calls, and a store that resets between tests. It is the second implementation that keeps the interfaces honest: an abstraction with only one implementation is a guess.

Every framework test runs against it. See the Local provider for what it provides, and local persistence for the SQLite-backed variant that survives restarts.