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

26 lines
2.3 KiB
Markdown

---
bc-version: [all]
domain: events
keywords: [ishandled, overridable, onbefore, integration-event, extensibility, event-override, subscriber-hook]
technologies: [al]
countries: [w1]
application-area: [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`.