Publishing Events
A workflow often needs to tell the rest of the system that something happened—an order was approved, a payment was released, a task is ready for an agent.
The publish / awaitPublish pair appends that business event as a durable workflow step.
|
|
The awaitPublish method
After the approval decision, we publish an OrderApproved event so a shipment workflow and the read model can react:
-
Java
-
Kotlin
ctx.awaitPublish("notifyApproved", (1)
new OrderApproved(orderId, approvedBy)); (2)
| 1 | The step name. It is the durable identifier of the step, exactly as for every other primitive. |
| 2 | The event to publish.
A plain payload has its MessageType resolved through the configured MessageTypeResolver; add a Metadata argument to publish metadata with it.
You can pass an EventMessage instead when you need to set the type yourself. |
awaitPublish("notifyApproved", OrderApproved(orderId, approvedBy))
One event, two roles
publish appends exactly one event: the event you pass, enriched with workflow metadata.
The OrderApproved event keeps its own type, payload, identifier and timestamp.
The engine only adds metadata: workflowId, stepName, stepType=COMPLETED and stepPrimitive=PUBLISH.
Those keys override metadata of the same name on the event you pass.
That single event plays both roles:
-
For consumers it is the business event. Entities, projections and other workflows receive the event you published, with an additional
workflowIdtag. -
For the workflow it is the completed step. On replay the step is found in the workflow state and the event is not published again.
The workflow payload is not modified by publish.
Use setPayload when the published data should also become part of the workflow state.
Workflow-to-workflow communication
A published event is routed like any business event.
Another workflow can start on it or wait for it with awaitEvent.
// Approval workflow
ctx.awaitPublish("notifyApproved", (1)
new OrderApproved(orderId, approvedBy)); (2)
// Shipment workflow, started on OrderApproved
@Workflow(idProperty = "orderId",
startOnEventClass = OrderApproved.class,
workflowNamespace = "io.myapp.shipping")
public void execute(SimpleWorkflowContext ctx) {
ctx.awaitExecute("prepareShipment", Boolean.class, () -> true);
}
When the publisher also waits for a reply, register the wait first with the non-blocking waitForEvent, then publish, then block on the handle.
A wait registered after the reply has already passed is never woken.
var reply = ctx.waitForEvent("awaitQuote", QuoteReceived.class,
associate(payloadProperty("orderId"), equalsTo(orderId)));
ctx.awaitPublish("requestQuote", new QuoteRequested(orderId));
reply.await();
Naming publish steps
The step name is the durable identifier of the publish, not the wire name of the event.
Consumers see only the event’s own type.
Name the step after the intent, as a verb phrase, unique within the workflow body: notifyApproved, requestQuote, announceShipment.
Two publishes of the same event type need two step names.
|
A workflow started by a published event derives its own instance id from that event.
An id equal to an existing instance is rejected as a duplicate.
When two workflows are keyed by the same property, give them distinct id prefixes through their |
Non-blocking form
publish returns a WorkflowStepResult you can await later or combine with other steps:
var notified = ctx.publish("notifyApproved", new OrderApproved(orderId, approvedBy));
var reserved = ctx.execute("reserveStock", Map.of(), (pc, p) -> inventory.reserve(p));
ctx.allMatch(WorkflowStepResult::success, notified, reserved).await();
Each publish call is its own ordered append.
Two calls produce two events and two steps.