Osy#betaRun it on Osyrin: osyrin.com

Reference / Function

[OncePerDelivery] — a read made once for every event of a delivery

[OncePerDelivery] <Type> <Name>(<params>) { … }

`[OncePerDelivery]` on a function that reads makes it answer once per delivery: the events one commit raised are delivered together, and a handler that calls it for each of them gets the first call's answer for the same arguments — so a rule engine reads its rules once for the batch, not once per event. Outside a delivery it simply runs.

preview2 examples compiled by CIfunctioneventscompositioncost

Summary#

What one commit raises is delivered together — a ticket arriving, being queued, being assigned is ONE delivery. A handler of every event that reads its rules for each event would read them three times; marked [OncePerDelivery], the read is made once and every event's handler is handed that answer:

[OncePerDelivery]
List<Rule> SavedRules(string subject) { return Rule.Where(r => r.Subject == subject).ToList(); }

Signature#

[OncePerDelivery]
<Type> <Name>(<params>) { … }     // answers something; called by a handler, or by what a handler calls

Description#

What is remembered?#

The answer, per set of arguments — strings, numbers, yes-or-no, Guids, dates and records (by id). A call with any other kind of argument is not remembered: it simply runs. A list it answers is handed to each caller as their own copy, so a handler adding to it does not change what the next event's handler gets; the records in it are the same records, read on the delivery's own unit of work.

For how long?#

For one delivery — the events of one commit (or of one workflow step) delivered together. The next delivery reads again. So the function should READ what the delivery itself does not change: rules, subscriptions, settings. What a handler writes during the delivery is not in an answer remembered before it.

Outside a delivery?#

An ordinary call: a page, a test or the hourly clock calling the same function reads every time.

What is refused?#

A function that answers nothing, a handler, an action or a test, and a method of a class:

`[OncePerDelivery]` remembers what a function ANSWERS for the rest of a delivery — 'RemembersNothing' answers nothing, so there is nothing to remember. Put it on the function that reads: `[OncePerDelivery] List<Rule> SavedRules() { … }`.

Examples#

Three events, one read#

One commit raises three events; each handler run reads the notes through Notes(), which is read once between them.

entity Lamp {
  [MaxLength(40)] string Name = "";
  security { allow read, create, update when IsAnonymous || IsAuthenticated; }
}

entity Note {
  [MaxLength(100)] string Text = "";
  security { allow read, create when IsAnonymous || IsAuthenticated; }
}

entity Heard {
  [MaxLength(200)] string Says = "";
  security { allow read, create when IsAnonymous || IsAuthenticated; }
}

[Event] public sealed class LampLit { public Lamp Lamp; }

[On]
void Remember(Event e) { new Heard { Says = Events.Type(e) + " with " + Notes().Count + " notes" }; }

[OncePerDelivery]
List<Note> Notes() { return Note.ToList(); }

void LightThrice(Lamp l) {
  Raise(new LampLit { Lamp = l });
  Raise(new LampLit { Lamp = l });
  Raise(new LampLit { Lamp = l });
}
[Test]
void Every_event_of_the_delivery_reads_the_same_notes() {
  new Note { Text = "bright" };
  var lamp = new Lamp { Name = "Porch" };
  Background.Settle();
  LightThrice(lamp);
  Background.Settle();
  Assert.Equal(3, Heard.Count(h => h.Says == "LampLit with 1 notes"));
}

See also#

Related

[On] — hear an event, after the commit that raised it

`[On]` on a function makes it a HANDLER of the event its one parameter is. Every handler of an event — in the app and…

[Interest] — the events a handler of every event cares about

`[Interest(nameof(Triggers))]` beside the `[On]` of a handler of EVERY event names a function that answers which event…

Raise(e) — say that something happened

`Raise(e)` says that the event `e` happened — an instance of a class marked `[Event]`. It is written in the same commit…