Triggers are a new but powerful mechanism in DAML. As such, I think that good design principles have not yet been discussed. I want to start this question, to gather feedback, further the discussion and identify gaps in my (and our) knowledge about them.
In my most recent work, I am using triggers to provide a Materialized view for a user of data that is logically spread out among several other contracts that describe that user’s interaction and obligation with other users. A technique that I have found particular clear is to keep a ContractId to that user’s Role contract within the state of the trigger, and then react to the creation of new contracts in the ACS that have not been processed. The set of contracts that have been processed (their existence acknowledged in that users view) is also maintained in the trigger state. Imagine the CopyTrigger example but 10x crazier.
Some specific questions:
In this design the existence of the Role contract is a prerequisite for the trigger. I currently error during initialize instead of keeping an Optional to the role ContractId. Is this the recommended way to signal failure?
Instead of keeping that ContractId in state, I could getContracts it from the ACS every time. Is getContract “fast”, should I care?
The separation of updateState and rule is a bit awkward. AFAIU, I can’t change state within the rule ( ... -> TriggerA ()) . Is the right approach to react to Message's sent to updateState as a result of the Creates and Exercises that one emits within the rule?
I would generally recommend to write your triggers such that they gracefully handle this case and don’t crash. This might not be super important in a production setup where this contract will be created once and you never have to worry about it existing afterwards but at least for development it makes things a bit easier since you don’t have to worry about the order in which you spin things up.
getContracts currently does a linear search through the ACS so it depends on how many contracts are active of the templates you have registered. This will change as soon as DAML-LF stabilizes support for the generic Map type. At this point, this becomes a single lookup. I wouldn’t worry too much about the performance here. If you are doing some expensive computation on the contracts this might be worth caching but I wouldn’t bother caching getContracts.
I don’t think the API here is quite final so some things might still be changing as more people start using the custom state but here’s the current thinking: Triggers are state-based not event-based. This is crucial imho. Event-based automation is super fragile if it gets shut down and started up again (either you react to old events which probably don’t make sense anymore or you ignore those events which is also bad). The trigger library tracks some state for you (the ACS) and you can track more state if you want but the API deliberately separates tracking state from the state-based rule.
Noted. It does seem a little weird to getContracts for the same Role contract repeatedly. Though, you’re guarding against it being archived. I am starting to think that this weirdness, is really a reflection of the dependence on the Role contract and not Triggers.
You make some interesting points. While I agree that event based automation can be difficult, it is the dual of the state based one, I don’t think that you get away from the complexity. In particular, can you clarify what you mean by:
what would “trigger” a Trigger if not an event? The way that I understand this, is that the rule is state based, but the updateState is event based, because the former does not get a Message and the latter does. My concern, is that this approach hinders the react to events model. I have incomplete thoughts on this at the moment, so I’ll do some more thinking. My point of comparison is the DAZL model, where reacting to events is really easy (ex. DABL Chess operator bot), such as progressing along designed state transitions.
2c on this: I also have the impression that there are cases where you don’t actually need state at all, and event-based is just fine. But likewise I haven’t formed a clear opinion yet, too little data - it just feels like the current trigger implementation is fairly complex / heavyweight to do simple things.
Triggers should be written under the assumption that the rule of your trigger can be triggered at any time. The fact that it’s only triggered at events is more of an optimization since only then the state can change so triggering your trigger at another time is useless.
I do see your point that reacting to events is difficult but that is intentional. While there might be cases where the issues of an event-based implementation are fine, this is not what triggers are designed for.
Sort of a small aside here: this kind of design intent behind a system is really useful for users in my opinion, but is often left out of documentation. Based on this thread, I think it would be very useful if you could add a paragraph or two at the top of the DAML Trigger docs page to elaborate on these design principles behind the Trigger API.
@cocreature I’d like to revisit this point. How should we write trigger code that is state based and not event-based? If we have a large ACS, and the trigger keeps firing because of changes to the ledger (avoiding the word “event”), we have to keep scanning the ACS to determine what’s different? Doesn’t that seem wasteful?