bcquality/microsoft/knowledge/events/choose-static-vs-manual-subscribers-deliberately.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.4 KiB

bc-version domain keywords technologies countries application-area
all
events
event-subscriber
static-subscriber
manual-subscriber
bindsubscription
unbindsubscription
eventsubscriberinstance
scoped-binding
al
w1
all

Choose static vs manual subscribers deliberately and bind manual ones with BindSubscription

Description

An [EventSubscriber] codeunit is static by default (EventSubscriberInstance = StaticAutomatic): it is always bound, so it fires for every raise of the event in every session. That is correct for always-on behaviour such as auditing, but wrong for behaviour that must be scoped — test isolation, a one-off migration, or a conditional override — because a static subscriber cannot be switched off. For scoped behaviour, set EventSubscriberInstance = Manual and activate the codeunit only while needed with BindSubscription, releasing it with UnbindSubscription. LLMs are largely unaware the manual model exists and default everything to static, producing always-on side effects that leak across unrelated operations and tests.

Best Practice

Use a static subscriber for behaviour that genuinely applies all the time. For anything scoped, mark the codeunit EventSubscriberInstance = Manual, call BindSubscription(SubscriberInstance) at the start of the scope and UnbindSubscription(SubscriberInstance) at the end. A manual subscriber held only in a local variable unbinds automatically when that variable leaves scope, which suits test setup/teardown; a binding you intend to outlive a single call must be unbound explicitly. Keep subscriber methods local per CodeCop AA0207.

See sample: choose-static-vs-manual-subscribers-deliberately.good.al.

Anti Pattern

Two shapes. First, a static subscriber used for behaviour that should be scoped — an always-on side effect (sending mail, writing extra records) that now fires for every event in every session and test with no way to disable it. Second, a manual subscriber that is bound with BindSubscription and never unbound: when the instance is held beyond the intended scope (for example on a SingleInstance codeunit), the binding leaks for the whole session and later unrelated operations keep hitting it. Detection: scoped side effects on a static subscriber, or a BindSubscription call with no matching UnbindSubscription and no scope that releases the instance.

See sample: choose-static-vs-manual-subscribers-deliberately.bad.al.