Summary#
A handler is an ordinary function with [On], taking the event:
[On]
void WinOnAccept(QuoteAccepted e) {
if (e.Quote.Opportunity != null) { MarkWon(e.Quote.Opportunity); }
}It runs once the work that raised the event has committed. A kit may hear the events of a kit it depends on, and an
app hears every kit's — nobody registers anything; the compiler finds every [On] in the app and its packages.
Signature#
[On] // hears the event its one parameter is
[Order(10)] // optional — lower runs first; none is 100
void <Name>(<EventClass> e) { … }
[On] // hears EVERY event — the app's and every package's
void <Name>(Event e) { … }Description#
In what order do handlers run?#
One order, fixed when the app compiles, never by file or load order:
[Order(n)], lower first — a handler with none is at 100, so kits that leave gaps of 10 below it run first;- then by package: a package before the packages that use it, the app last;
- then by full name.
A handler of every event#
A handler whose parameter is Event itself hears every event, of every class — what a timeline, a rule engine or
webhooks need, since they work on events they have never heard of. It asks Events.* what each one
is, and Events.Delivery() where it stands: the event's Id, the Root of its cascade, its Depth, when it was
RaisedAt, and the Cause an action that raised it was run under. It is never cut from a cascade for having run
earlier in it: it hears what it caused, by design, and is held to the depth limit like any other.
Hearing every event is not free: each one it hears is written and delivered. When it only acts on SOME of them — the
ones a rule is on, the ones a subscription takes — say which with [[function-interest|[Interest]]], and an event it
does not care about is not written for it at all. A read it makes for every event can be made once for all the events
one commit raised with [[function-once-per-delivery|[OncePerDelivery]]].
When does it run?#
After the commit that raised the event — in process, straight after it, so a handler's work is there on the next line.
A workflow's step commits more than once (a run's Start {} before its first state is entered), so an event raised in
a step is delivered when the step — and every run its commit started — is done: a handler never meets a run half-way.
All of one event's handlers commit together, with the event marked delivered, so their work lands once — and the
events one piece of work raised (a commit, or a workflow step and every run it started) are delivered together, in the
order they were raised, in one transaction: a ticket that raises three events costs one delivery, not three. A handler
of the second event sees what the first event's handlers did, exactly as it would after their commit.
What if a handler fails?#
Its writes are discarded, the failure is recorded with the event — which handler, and what it said — and the handlers after it still run. One handler's fault never takes the others' work with it. It is not run again: a fault in its code would fail again, and a second run would repeat what it did before it threw.
The one exception is a LOST RACE — another writer changed a row the handler changed, first. That is not a fault, so the handler is run again, reading the row as it now stands, a few times; only one that keeps losing is recorded.
When events are delivered together and one of them cannot land — a handler loses a race, or a write is refused only at the commit — nothing of the batch is kept, and each event is delivered on its own, exactly as above. So a failing handler never takes another event's work with it, and is recorded against its own event.
What if handlers raise events that come back?#
Each event carries the handlers that ran on the way to it. A handler already on that path is not run again — that is a loop — and the cut is recorded on the event it would have run on. A chain more than eight handlers deep is cut too.
Whose authority does a handler run with?#
The APP's, as a workflow body does: it runs after the person's request has ended, so there is no caller to run as. A
kit's deny still holds it — a handler is held to every package's write denies except its own package's. To act as a
person, run an [[function-action|[Action]]] as them: what that action raises belongs to this event's cascade, so a
rule set off by an event cannot set itself off again.
What may a handler look like?#
A top-level function with exactly one parameter, an [Event] class in sight (or Event), returning nothing — nobody waits for its
answer. A package's handler may be internal: the platform calls it, and nothing else can. Anything else is refused
where it is written:
'TwoParams' hears an event, so it takes exactly ONE parameter — the event: `[On] void TwoParams(QuoteAccepted e)`.
An event handler returns nothing — nobody waits for its answer, because it runs after the commit that raised the event.
`[Order]` places an event handler among the others — 'NoOn' has no `[On]`, so there is nothing for it to place.Examples#
Three handlers, and the middle one fails#
[Order] places them; the second throws, its write is discarded, and the first and third still run.
entity Ticket {
[Required, MaxLength(100)] string Subject;
[MaxLength(200)] string Trail = "";
security { allow read, create, update when IsAnonymous || IsAuthenticated; }
}
[Event("Ticket closed")]
public sealed class TicketClosed { public Ticket Ticket; }
void Close(Ticket t) { Raise(new TicketClosed { Ticket = t }); }
[On] [Order(10)]
void First(TicketClosed e) { e.Ticket.Trail = e.Ticket.Trail + "first;"; }
[On] [Order(20)]
void Second(TicketClosed e) {
e.Ticket.Trail = e.Ticket.Trail + "second;";
throw new ValidationException("the second one fails");
}
[On] [Order(30)]
void Third(TicketClosed e) { e.Ticket.Trail = e.Ticket.Trail + "third;"; }[Test]
void A_failing_handler_leaves_the_others_their_work() {
var t = new Ticket { Subject = "Printer jammed" };
Close(t);
Background.Settle();
Assert.Equal("first;third;", Ticket.Single().Trail);
}See also#
- [Event] — what happens to a record, declared once — declaring an event
- Raise(e) — say that something happened — raising one, and what it costs
- [Action] — what an app offers to be done, by name — something a person or a rule may do, run under their security
- try / catch / finally — how a failing handler's writes are discarded
- [Interest] — the events a handler of every event cares about — a handler of every event says which it cares about
- [OncePerDelivery] — a read made once for every event of a delivery — a read made once for every event of a delivery