Skip to content

Reactions

A reaction is behavior that runs when something happens — the “if this then that” of a Screenplay. It states what sets it off and what that sets off in turn: notifications, calls to external systems, follow-up events, or commands. Reactions live inside Automation slices.

reaction <Name>
[description "<text>"]
<trigger>
[description "<text>"]
[<value> ...]
[produces <EventType> ...]
[invokes <Command> ...]
[file <Path>]
[csharp
```
<C# returning event side effects>
```]
[where <condition>]

A reaction declares at least one trigger. Everything under a trigger is optional.

The model has three parts, and the language gives each its own word:

PartWhat it is
Triggersomething that can cause the reaction to run
Trigger datathe values that particular occurrence hands the reaction
Reactionthe behavior that runs, and what it sets off

A reaction never needs to know where its trigger came from. when OrderPlaced reads the same whether OrderPlaced is a domain event from the event store, a signal an integration raises, or something the host does — that is the trigger’s business. See Triggers for declaring one and for the built-in set.

Three forms, and a reaction may declare several:

reaction OrderHandling
when OrderPlaced
every 15 minutes
at 08:00

when <Name> names an event, a trigger the document declares, or one a consumer registered with the compiler. every and at are the clock — spelled the way a schedule is said out loud rather than as a trigger with arguments, because a reaction driven by the passage of time is common enough to earn the words.

FormRuns
every 30 secondson that interval
every 15 minuteson that interval
every 2 hourson that interval
every 1 dayon that interval
at 08:00every day, at that time
at 09:30 on Mondayevery week, on that day
at 00:00 on day 1every month, on that day

A time with no qualifier is every day. A day of the week and a day of the month cannot both be given — most months have no such occurrence.

Under a trigger, a bare name says the reaction uses that value from the occurrence:

event OrderPlaced
order String
customer String
reaction HandleOrder
when OrderPlaced
order
customer

This is a selection, not a declaration — the shape belongs to the event or the trigger, and the reaction states which parts of it matter. Taking a value the occurrence does not carry is reported, because the document already knows what an event and a declared trigger provide.

where filters trigger occurrences using the same condition grammar as produces when and require, with and, or and parentheses:

event IssueOpened
labels String
title String
reaction HandleImportantIssue
when IssueOpened
labels
title
where labels contains "important" or title starts with "URGENT"

Alongside ==, != and the ordering operators, text compares with contains for a substring anywhere and starts with for one at the beginning.

where belongs to the reaction rather than to one trigger: it says which occurrences are worth running for, whatever set them off.

Screenplay’s workflow is author the document first, then Stage performs it, so a reaction must be describable before any code exists. A trigger with nothing under it is already a complete statement — this reaction runs when that happens:

reaction PaymentReconciler
description "Matches settled payments against outstanding invoices and closes them out"
when InvoicePaid
when InvoiceMarkedOverdue
description "Re-checks whether a late payment has since arrived"

That document parses, and it tells a reader exactly what the reaction is for — with no file to point at and no code to invent. The file reference and the inline block are realization metadata, attached once the slice is implemented.

Give the reaction a description for what it does overall, and a trigger its own description when that reaction needs explaining beyond the trigger’s name.

An automation is the “if this, then that” of the system, and a document that could only draw the if left the arrows out of an automation invisible.

event InvitationAccepted
workspaceId Uuid
event WorkspaceProvisioned
workspaceId Uuid
command SendWelcomeMail
workspaceId Uuid
reaction Provisioner
when InvitationAccepted
workspaceId
produces WorkspaceProvisioned
for workspaceId
workspaceId = workspaceId
invokes SendWelcomeMail
workspaceId = workspaceId

produces is the same declaration a command carries — appending an event is the same act wherever it happens, including for to say which event source it lands on.

invokes is a different word on purpose. A command is not produced; it is asked for. An event is a fact the reaction appends and nothing can refuse it, while a command is an intent handed to something else that may still validate and reject it. Using produces for both would say those are the same kind of consequence, and they are not.

Both are declarations of what happens, not of how — a trigger can state its consequences and still carry a file or an inline block that implements them.

Delegating to a file:

reaction NotifyCustomer
description "Emails the billing contact once an invoice is registered"
when InvoiceRegistered
file Reactions/NotifyCustomerReaction.cs

Inline C#:

reaction OverdueInvoiceDetector
when InvoiceStatusChanged
csharp
```
if (@event.Status != InvoiceStatus.Paid &&
@event.ChangedAt < DateTimeOffset.UtcNow.AddDays(-30))
{
return [new MarkInvoiceOverdue(
InvoiceId: @event.InvoiceId,
OverdueAt: DateTimeOffset.UtcNow
)];
}
```

Inside the block, @event is the triggering occurrence; returned events are appended as side effects.

  • Reactions that only translate events into other events belong in Translate slices when driven by external data (captures); event-to-event automation stays in Automation slices.
  • Keep reaction logic small; anything substantial belongs in a file reference where it can be tested on its own.
  • Describe the reaction before you implement it — a document full of file lines and nothing else tells a reader nothing.
  • A name that only an integration knows belongs in a trigger declaration, so the document says what the reaction is handed rather than leaving the reader to guess.