ZuloOne
English
Sign in
Menu

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 with EventResult.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.

Where next#

Was this page helpful?