Skip to main content

Delta Protocol

The LionWeb Delta protocol enables real-time, incremental model synchronisation between multiple clients through a WebSocket connection.

ProtocolTransportUse case
BulkHTTP RESTSnapshots — retrieve or replace entire partitions
DeltaWebSocketStreams — receive individual mutation events as they happen

Use the Delta protocol when multiple tools need to observe or apply changes to the same model concurrently, without re-fetching full snapshots.

Choosing a Server

The Delta protocol can be used with the LionWeb JVM Server — the standalone server bundled in this repository (see the Server Guide). It holds models in memory, making it a convenient choice for simple use cases, local development, and testing.

For production scenarios that require persistence across restarts, the LionWeb Server is backed by a database and provides a more robust foundation. Delta protocol support in the LionWeb Server is planned for a future release.

Gradle Dependency

The DeltaClient is part of the same client artifact as the bulk APIs — no additional dependency is required:

dependencies {
implementation("io.lionweb:lionweb-2024.1-client:$lionwebVersion")
}

Connecting to the LionWeb JVM Server

import io.lionweb.client.delta.DeltaClient;
import io.lionweb.server.delta.WebSocketDeltaChannel;
import java.net.URI;

// WebSocketDeltaChannel requires the lionweb-2024.1-server artifact on the classpath
WebSocketDeltaChannel channel =
new WebSocketDeltaChannel(URI.create("ws://localhost:9240"));
channel.connectBlocking();

DeltaClient client = new DeltaClient(channel, "my-tool-client");

WebSocketDeltaChannel is provided by the server module (lionweb-2024.1-server). When writing code that should work with any transport (e.g. for testing), depend on DeltaChannel from the client module instead.

Participation Lifecycle

Signing On

client.sendSignOnRequest();
String participationId = client.getParticipationId();

sendSignOnRequest() sends a SignOnRequest and waits for a SignOnResponse. The server assigns a participationId that uniquely identifies this client session. Store it for reconnection.

Subscribing to Partition Lifecycle Events

// Receive PartitionAdded / PartitionDeleted events
client.sendListAndSubscribePartitionsRequest();

Subscribing to Partition Content Events

// Receive node-level mutation events for a specific partition
client.sendSubscribeToPartitionContentsRequest(partitionId);

The server only broadcasts events to clients that have subscribed to the relevant partition, keeping traffic minimal.

Reconnecting

If the WebSocket connection drops, recreate the channel and call:

// lastReceivedSequenceNumber is the sequence number of the last event received
client.sendReconnectRequest(participationId, lastReceivedSequenceNumber);

Signing Off

client.sendSignOffRequest();
channel.closeBlocking();

Message Types

Queries (Synchronous Request / Response)

Queries block the calling thread until the server responds.

RequestResponseDescription
SignOnRequestSignOnResponseRegister a new participation
ReconnectRequestReconnectResponseResume a previous participation
SignOffRequestSignOffResponseTerminate a participation
ListPartitionsRequestListPartitionsResponseList available partitions
ListAndSubscribePartitionsRequestListAndSubscribePartitionsResponseList partitions and subscribe to lifecycle events
SubscribeToPartitionContentsRequestSubscribeToPartitionContentsResponseSubscribe to content events for one partition
UnsubscribeFromPartitionContentsRequestUnsubscribeFromPartitionContentsResponseUnsubscribe from content events
GetAvailableIdsRequestGetAvailableIdsResponseRequest a batch of unique IDs from the server

All query classes are in io.lionweb.client.delta.messages.queries.

Commands (Client → Server Mutations)

Commands are fire-and-forget messages that mutate the server-side model. The server broadcasts corresponding events to subscribed clients.

Partitions (io.lionweb.client.delta.messages.commands.partitions):

CommandDescription
AddPartitionCreate a new partition
DeletePartitionRemove a partition

Children (io.lionweb.client.delta.messages.commands.children):

CommandDescription
AddChildAdd a child node to a containment
DeleteChildRemove a child node
MoveChildInSameContainmentReorder within the same containment
MoveChildFromOtherContainmentMove from one containment to another
MoveChildFromOtherContainmentInSameParentMove between containments in the same parent
ReplaceChildReplace a child with a different node

Properties (io.lionweb.client.delta.messages.commands.properties):

CommandDescription
AddPropertySet a property value for the first time
ChangePropertyUpdate an existing property value
DeletePropertyClear a property value

References (io.lionweb.client.delta.messages.commands.references):

CommandDescription
AddReferenceAdd a reference value
ChangeReferenceUpdate a reference value
DeleteReferenceRemove a reference value

Annotations (io.lionweb.client.delta.messages.commands.annotations):

CommandDescription
AddAnnotationAttach an annotation to a node
DeleteAnnotationRemove an annotation
MoveAnnotationInSameParentReorder annotations on the same node
MoveAnnotationFromOtherParentMove an annotation to a different node

Classifier (io.lionweb.client.delta.messages.commands):

CommandDescription
ChangeClassifierChange the classifier (concept) of a node

Events (Server → Client Broadcasts)

Events mirror commands. The server sends them to all clients subscribed to the affected partition.

EventTriggered by
PartitionAddedAddPartition command or partition lifecycle subscription
PartitionDeletedDeletePartition command
ChildAddedAddChild
ChildDeletedDeleteChild
ChildMovedInSameContainmentMoveChildInSameContainment
ChildMovedFromOtherContainmentMoveChildFromOtherContainment
ChildMovedFromOtherContainmentInSameParentMoveChildFromOtherContainmentInSameParent
ChildReplacedReplaceChild
PropertyAddedAddProperty
PropertyChangedChangeProperty
PropertyDeletedDeleteProperty
ReferenceAddedAddReference
ReferenceChangedChangeReference
ReferenceDeletedDeleteReference
AnnotationAddedAddAnnotation
AnnotationDeletedDeleteAnnotation
AnnotationMovedInSameParentMoveAnnotationInSameParent
AnnotationMovedFromOtherParentMoveAnnotationFromOtherParent
ClassifierChangedChangeClassifier
ErrorEventServer-side processing failure

All event classes are in io.lionweb.client.delta.messages.events and its sub-packages.

Monitoring Local Nodes

DeltaClient can track a local node tree and automatically send commands when the nodes change:

// After retrieving the partition via the Bulk API:
client.monitorPartition(partitionNode);

When a monitored node is mutated locally (e.g. a property is set), DeltaClient sends the corresponding command to the server. When the client receives an event from the server (from another participant), it applies the change to the monitored node automatically.

DeltaClient implements DeltaEventReceiver and is registered on the channel automatically.

Error Handling

When the server encounters a processing error it sends an ErrorEvent. DeltaClient throws ErrorEventReceivedException (in io.lionweb.client.delta) so callers can catch it:

import io.lionweb.client.delta.ErrorEventReceivedException;

try {
client.signOn();
} catch (ErrorEventReceivedException e) {
System.err.println("Delta error: " + e.getErrorEvent().getErrorCode());
}

Known error codes are defined in StandardErrorCode.

Testing with InMemoryDeltaChannel

For unit tests that do not require a running server, use InMemoryDeltaChannel:

import io.lionweb.LionWebVersion;
import io.lionweb.client.api.HistorySupport;
import io.lionweb.client.api.RepositoryConfiguration;
import io.lionweb.client.delta.DeltaChannel;
import io.lionweb.client.delta.DeltaClient;
import io.lionweb.client.delta.InMemoryDeltaChannel;
import io.lionweb.client.inmemory.InMemoryServer;

// Set up an in-memory server with a repository
InMemoryServer server = new InMemoryServer();
server.createRepository(
new RepositoryConfiguration("MyRepo", LionWebVersion.v2024_1, HistorySupport.DISABLED));

// Create a channel and wire it to the server — no network involved
DeltaChannel channel = new InMemoryDeltaChannel();
server.monitorDeltaChannel("MyRepo", channel);

DeltaClient client = new DeltaClient(channel, "test-client");
client.sendSignOnRequest();
// Commands sent through the channel are processed by the server in-process

InMemoryServer.monitorDeltaChannel() wires the channel directly to the server, so tests run without network overhead.