bcquality/microsoft/knowledge/events/use-ishandled-to-make-base-behaviour-overridable.md
Jesper Schulz-Wedde 54ddd8ecc2 Add events knowledge domain and review leaf skill
Add a new `events` knowledge domain covering AL events & subscribers,
wired into the AL review pipeline.

- 3 atomic articles (+ .good.al/.bad.al samples) under
  microsoft/knowledge/events/: the IsHandled override pattern, thin
  OnBefore/OnAfter integration-event publishers, and static vs manual
  subscribers.
- New leaf skill microsoft/skills/review/al-events-review.md sourcing the
  events domain.
- Wired into microsoft/skills/review/al-code-review.md (sub-skills + Source
  + description) and README.md (leaf-skill count + domain list).

AL event syntax verified against Microsoft Learn. Samples are
demonstration-only (not compiled by CI). Additive change; no contract change.

Part of #34.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-25 11:58:58 +02:00

2.3 KiB

bc-version domain keywords technologies countries application-area
all
events
ishandled
overridable
onbefore
integration-event
extensibility
event-override
subscriber-hook
al
w1
all

Use the IsHandled pattern to make base behaviour overridable

Description

AL has no method overriding, so a procedure that runs its body unconditionally cannot be replaced by an extension without editing base code. The established Business Central seam for substituting default behaviour is the IsHandled pattern: the routine raises an OnBefore… integration event carrying a var IsHandled: Boolean, then exits early when a subscriber has set it. This hands a partner a sanctioned hook to replace the logic instead of overwriting the routine. LLMs trained on languages with inheritance emit routines whose logic always runs and expose no OnBefore/IsHandled seam, so the behaviour silently cannot be overridden.

Best Practice

Raise OnBeforeX(…, IsHandled) as the first step of the routine and guard with if IsHandled then exit; before any default logic runs. Declare the publisher [IntegrationEvent(false, false)] local procedure OnBeforeX(…; var IsHandled: Boolean) with an empty body, and keep IsHandled a var parameter so a subscriber can write to it. A subscriber that replaces the behaviour does its work and sets IsHandled := true; one that only augments leaves it untouched and guards with if IsHandled then exit; itself. Reserve the override hook for cases where a partner genuinely needs to replace logic — when the goal is only to react, a positive OnAfter event is the better seam.

See sample: use-ishandled-to-make-base-behaviour-overridable.good.al.

Anti Pattern

Two shapes. First, a routine whose default logic always runs because there is no OnBefore…/IsHandled hook at all — extensions cannot change it without overwriting base code. Second, a routine that raises OnBeforeX(IsHandled) but omits the if IsHandled then exit; guard, so the default logic still executes after a subscriber set IsHandled := true, duplicating work and side effects. Detection: an OnBefore publisher with a var IsHandled: Boolean parameter whose caller never tests IsHandled, or a public routine doing non-trivial work with no overridable seam.

See sample: use-ishandled-to-make-base-behaviour-overridable.bad.al.