From 3769d2fbf47c7e36713e8eb52683e09bf65ca1e6 Mon Sep 17 00:00:00 2001 From: Jeffrey Bulanadi <41933086+jeffreybulanadi@users.noreply.github.com> Date: Thu, 25 Jun 2026 19:55:06 +0800 Subject: [PATCH] Add knowledge file for raising events inside try functions (#31) --- ...raising-events-inside-try-functions.bad.al | 38 +++++++++++++++++++ ...aising-events-inside-try-functions.good.al | 38 +++++++++++++++++++ ...oid-raising-events-inside-try-functions.md | 26 +++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 community/knowledge/events/avoid-raising-events-inside-try-functions.bad.al create mode 100644 community/knowledge/events/avoid-raising-events-inside-try-functions.good.al create mode 100644 community/knowledge/events/avoid-raising-events-inside-try-functions.md diff --git a/community/knowledge/events/avoid-raising-events-inside-try-functions.bad.al b/community/knowledge/events/avoid-raising-events-inside-try-functions.bad.al new file mode 100644 index 0000000..4dfce97 --- /dev/null +++ b/community/knowledge/events/avoid-raising-events-inside-try-functions.bad.al @@ -0,0 +1,38 @@ +// Demonstration only. Shows the wrong pattern: raising the integration event inside a TryFunction body. + +codeunit 50116 "Payment Processor Bad" +{ + [IntegrationEvent(false, false)] + procedure OnBeforeSubmitPayment(var PaymentAmount: Decimal; var Cancel: Boolean) + begin + end; + + procedure SubmitPayment(PaymentAmount: Decimal) + var + Success: Boolean; + begin + // TryFunction wraps both the event raise and the gateway call. + Success := TrySubmitPaymentInternal(PaymentAmount); + if not Success then + Error('Payment gateway call failed. Check connectivity and retry.'); + end; + + [TryFunction] + local procedure TrySubmitPaymentInternal(PaymentAmount: Decimal) + var + Cancel: Boolean; + Client: HttpClient; + Response: HttpResponseMessage; + begin + Cancel := false; + // BAD: event raised inside TryFunction. Any Error() thrown by a subscriber is caught here + // and silently swallowed - the subscriber's error never reaches the caller. + // A subscriber setting Cancel := true is also lost when TryFunction returns false. + OnBeforeSubmitPayment(PaymentAmount, Cancel); + if Cancel then + exit; + Client.Get('https://payments.example.com/submit?amount=' + Format(PaymentAmount), Response); + if not Response.IsSuccessStatusCode() then + Error('HTTP %1', Response.HttpStatusCode()); + end; +} diff --git a/community/knowledge/events/avoid-raising-events-inside-try-functions.good.al b/community/knowledge/events/avoid-raising-events-inside-try-functions.good.al new file mode 100644 index 0000000..7c76303 --- /dev/null +++ b/community/knowledge/events/avoid-raising-events-inside-try-functions.good.al @@ -0,0 +1,38 @@ +// Demonstration only. Shows the correct pattern: raise the integration event before entering TryFunction. + +codeunit 50114 "Payment Processor" +{ + [IntegrationEvent(false, false)] + procedure OnBeforeSubmitPayment(var PaymentAmount: Decimal; var Cancel: Boolean) + begin + end; + + procedure SubmitPayment(PaymentAmount: Decimal) + var + Cancel: Boolean; + Success: Boolean; + begin + Cancel := false; + // Event raised outside the try scope - subscriber errors propagate normally to the caller. + OnBeforeSubmitPayment(PaymentAmount, Cancel); + if Cancel then + exit; + + // Only the operation that can fail transiently lives inside TryFunction. + Success := TryCallPaymentGateway(PaymentAmount); + if not Success then + Error('Payment gateway call failed. Check connectivity and retry.'); + end; + + [TryFunction] + local procedure TryCallPaymentGateway(PaymentAmount: Decimal) + var + Client: HttpClient; + Response: HttpResponseMessage; + begin + // ... build request, set headers ... + Client.Get('https://payments.example.com/submit?amount=' + Format(PaymentAmount), Response); + if not Response.IsSuccessStatusCode() then + Error('HTTP %1', Response.HttpStatusCode()); + end; +} diff --git a/community/knowledge/events/avoid-raising-events-inside-try-functions.md b/community/knowledge/events/avoid-raising-events-inside-try-functions.md new file mode 100644 index 0000000..7e791fe --- /dev/null +++ b/community/knowledge/events/avoid-raising-events-inside-try-functions.md @@ -0,0 +1,26 @@ +--- +bc-version: [all] +domain: events +keywords: [tryfunction, integration-event, subscriber, error-handling, silent-failure, event-publisher] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# Do not raise integration events inside a TryFunction + +## Description + +A `TryFunction` catches all errors — including errors thrown by event subscribers. When an `[IntegrationEvent]` is raised inside a `TryFunction` body, any error a subscriber raises is silently swallowed by the TryFunction's error boundary. The subscriber's logic fails, the caller sees no error, and the calling code continues as if nothing happened. Subscribers have no way to signal failure to the caller. + +## Best Practice + +Raise the integration event before entering the TryFunction scope. The event and its subscribers execute outside the error boundary, so subscriber errors propagate normally to the caller. Move only the operation that genuinely needs error isolation (such as an HTTP call or a posting step) inside the TryFunction. + +See sample: `avoid-raising-events-inside-try-functions.good.al`. + +## Anti Pattern + +Raising an integration event inside a TryFunction body. Subscriber failures are caught and discarded by the TryFunction. The subscriber contract — that a subscriber can signal failure to the caller — is silently broken. + +See sample: `avoid-raising-events-inside-try-functions.bad.al`.