Wrap with Chain of Command
Updated
Several handlers of one object, and several implementations of one IFoo,
are an onion: the top layer starts first, next(...) goes down. Work
after next() sees stock and GL. The longer contract is
Chain of Command.
Note
The owner — the handler, service, command, posting, or driver in the object's own model — is link 0 and does not call
next. There is nobody below.
How to#
A wrap lives in your model, on a layer strictly above the owner. The strip in the studio is "Add yours on top". Two links on one layer throw.
Class names are unique across the whole workspace. The owner is
<Name>EventHandler. The next link uses a suffix (<Name>ExtEventHandler,
<Name>Tx_<Model>). A service class still equals the service name — put
the wrap in a namespace of your own so two class Pricing do not collide.
1. Events on a foreign object — [ExtensionOf("ObjectName")] on the
class, and next / nextAsync from every override, with the same
arguments the signature received:
[ExtensionOf("GoodsIssue")]
public partial class GoodsIssueCostingEventHandler : TypedDocumentEventHandler<GoodsIssue>
{
public override async Task<EventResult> OnAfterPostAsync(
GoodsIssue header, EventContext context)
{
var prior = await next(header, context);
if (!prior.Success) return prior;
return EventResult.Ok();
}
}
Missing [ExtensionOf] on somebody else's object is ZOCOC004. The same
attribute on an object you own is ZOCOC004 as well — that class is the
owner. Missing next above the owner is ZOCOC001. See
Write an event handler.
A second handler in your model of your object is not a second owner.
It sits above link 0: next is required, [ExtensionOf] is not.
2. A service — the same metadata name (the contract stays IFoo).
Pass the method's arguments into next<T> / nextAsync<T>. Unused methods
forward: => next<T>(…). Forgotten next is ZOCOC002: the registry does
not register the wrap, and GetService<IFoo>() stays on the inner one.
3. A driver, a command, or one posting — wrap that script. Do not
start a second command "with the same meaning", a second driver beside the
register's TotalDriverMetaId, or a third posting on the subtype unless you
own a new accounting goal. Forgotten next is ZOCOC003.
Several posting scripts on one subtype are a horizontal list
(executionOrder), not this onion. See Write postings
and Extend another model.
Warning
[Replace]on the method swallows the chain on purpose: lower links do not run. A command wrap with[Replace]does not call the owner's subtype transition either.Handlers find their object by name in the script envelope, not by
objectMetaId. Rename the document and leave the old name in the envelopes — posting still writes movements, and every handler silently stops. Probe withEventResult.Cancel("PROBE")as the first line, not a logger.
An empty class with no override is normal: that hook is not in the chain. A
second next in one override throws.
Bump your modelVersion in the same change.
What's not there yet#
Platform contracts (IDataService, IDocumentManager, ISqlService) are
not wrappable from the workspace. A Core kernel driver
(StockBaseTotalDriver, …) is wrapped with next(), not a partial class
named after the engine.