mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-08-07 09:56:52 +01:00
Add CMFRT naming conventions, object ID ranges, and patterns documentation
- Introduced guidelines for using the "CMFRT" prefix in object names, fields, and procedures to avoid naming collisions and ensure clarity. - Established rules for object ID ranges to prevent conflicts with other extensions and maintain historical integrity. - Documented best practices and anti-patterns for various coding patterns, including case statements, interface injection, label usage, and validation methods. - Implemented a standards review skill to evaluate AL source changes against CMFRT company standards, ensuring compliance with naming, permissions, and architectural patterns.
This commit is contained in:
parent
d6ac005173
commit
afb1fa2883
53 changed files with 1288 additions and 0 deletions
|
|
@ -0,0 +1,15 @@
|
|||
page 55035 "CMFRT AQ FS JJL API"
|
||||
{
|
||||
PageType = API;
|
||||
SourceTable = "Job Journal Line"; // real table exposed directly to the API
|
||||
|
||||
trigger OnInsertRecord(BelowxRec: Boolean): Boolean
|
||||
begin
|
||||
// Manual insert/exit boilerplate duplicates the framework insert
|
||||
// and couples the HTTP request to the real-table insert: no retry,
|
||||
// no error queue, one validation error fails the whole request.
|
||||
Rec.Insert(true);
|
||||
Rec.CMFRTAQFSLogIncomingRequest();
|
||||
exit(false);
|
||||
end;
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
page 55035 "CMFRT AQ FS JJL API"
|
||||
{
|
||||
PageType = API;
|
||||
SourceTable = "CMFRT AQ FS JJL Buffer"; // buffer table, not Job Journal Line
|
||||
|
||||
trigger OnInsertRecord(BelowxRec: Boolean): Boolean
|
||||
begin
|
||||
// Side effects only; framework performs the default insert.
|
||||
Rec.CMFRTAQFSLogIncomingRequest();
|
||||
end;
|
||||
}
|
||||
|
||||
codeunit 55038 "CMFRT AQ FS JJL Proc"
|
||||
{
|
||||
procedure CMFRTAQProcessPendingEntries()
|
||||
var
|
||||
JJLBuffer: Record "CMFRT AQ FS JJL Buffer";
|
||||
JJLCreator: Codeunit "CMFRT AQ FS JJL Creator";
|
||||
begin
|
||||
JJLBuffer.SetRange("CMFRT AQ Status", "CMFRT AQ Buffer Status"::"CMFRT AQ Pending");
|
||||
if JJLBuffer.FindSet(true) then
|
||||
repeat
|
||||
// Proc owns the error boundary: one bad row does not abort the batch.
|
||||
if Codeunit.Run(Codeunit::"CMFRT AQ FS JJL Creator", JJLBuffer) then
|
||||
JJLBuffer."CMFRT AQ Status" := "CMFRT AQ Buffer Status"::"CMFRT AQ Processed"
|
||||
else begin
|
||||
JJLBuffer."CMFRT AQ Status" := "CMFRT AQ Buffer Status"::"CMFRT AQ Error";
|
||||
JJLBuffer."CMFRT AQ Error Message" := CopyStr(GetLastErrorText(), 1, MaxStrLen(JJLBuffer."CMFRT AQ Error Message"));
|
||||
end;
|
||||
JJLBuffer.Modify(true);
|
||||
until JJLBuffer.Next() = 0;
|
||||
end;
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: architecture
|
||||
keywords: [api, buffer, staging, oninsertrecord, api-page, inbound, creator, proc, codeunit-run]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# CMFRT inbound API pages write to buffer tables
|
||||
|
||||
## Description
|
||||
|
||||
Inbound CMFRT API pages never source from real application tables. Each API page sources from a dedicated buffer (staging) table owned by the extension. The buffer row records the raw inbound payload plus a status field (Pending/Processing/Processed/Error) and an operation enum where applicable. A Proc codeunit picks up pending buffer rows and owns the `Codeunit.Run` error boundary; a Creator codeunit transfers buffer values into the real table via `Validate` calls. The API page's `OnInsertRecord` trigger contains no manual insert plumbing — the framework performs the default insert; the trigger only performs side effects such as request logging.
|
||||
|
||||
## Best Practice
|
||||
|
||||
For each inbound API: one buffer table (with status and error-message fields), one API page sourced from the buffer, one Proc codeunit that iterates pending rows and calls the Creator inside a `Codeunit.Run` boundary so one failing row does not abort the batch, and one Creator codeunit that fills and inserts the real record. `OnInsertRecord` bodies contain only logging or metadata capture and no `exit` statement, so the framework insert proceeds.
|
||||
|
||||
See sample: `cmfrt-buffer-table-api-pattern.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
An API page sourced directly from a real table (Sales Header, Job, Ship-to Address), or an `OnInsertRecord` trigger that calls `Rec.Insert(true)` followed by `exit(false)` to suppress the framework insert. Direct-to-real-table APIs make inbound failures atomic with the HTTP request (no retry, no error queue), and the manual insert/exit boilerplate duplicates framework behaviour while hiding the insert from other trigger logic.
|
||||
|
||||
See sample: `cmfrt-buffer-table-api-pattern.bad.al`.
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// Subscriber calls the implementation codeunit DIRECTLY — bypasses the base
|
||||
// table's entry point and all OnBefore/OnAfter integration events.
|
||||
codeunit 2045720 "CMFRT BA Sales Subscribers"
|
||||
{
|
||||
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", 'OnAfterPostSalesDoc', '', false, false)]
|
||||
local procedure CMFRTBAOnAfterPostSalesDoc(var SalesHeader: Record "Sales Header")
|
||||
var
|
||||
SyncImpl: Codeunit "CMFRT BA SyncPostedDoc Impl";
|
||||
begin
|
||||
// Direct call to implementation codeunit — OnBefore/OnAfter events on the
|
||||
// base table never fire, dependent extensions cannot intercept this operation.
|
||||
SyncImpl.CMFRTBASyncPostedSalesDoc(SalesHeader."No.");
|
||||
end;
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// Subscriber codeunit calls the BASE TABLE procedure — not the implementation codeunit.
|
||||
codeunit 2045720 "CMFRT BA Sales Subscribers"
|
||||
{
|
||||
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", 'OnAfterPostSalesDoc', '', false, false)]
|
||||
local procedure CMFRTBAOnAfterPostSalesDoc(var SalesHeader: Record "Sales Header")
|
||||
var
|
||||
CMFRTBAItem: Record "CMFRT BA Item";
|
||||
begin
|
||||
// Route through the base table — integration events fire normally.
|
||||
CMFRTBAItem.CMFRTBASyncPostedSalesDoc(SalesHeader."No.");
|
||||
end;
|
||||
}
|
||||
|
||||
// Base table owns all entry points to implementation codeunits.
|
||||
table 2045095 "CMFRT BA Item"
|
||||
{
|
||||
procedure CMFRTBASyncPostedSalesDoc(SalesDocNo: Code[20])
|
||||
var
|
||||
SyncImpl: Codeunit "CMFRT BA SyncPostedDoc Impl";
|
||||
Handled: Boolean;
|
||||
begin
|
||||
OnBeforeCMFRTBASyncPostedSalesDoc(SalesDocNo, Handled);
|
||||
if Handled then
|
||||
exit;
|
||||
CMFRTBASyncPostedSalesDoc(SyncImpl);
|
||||
end;
|
||||
|
||||
procedure CMFRTBASyncPostedSalesDoc(SyncImpl: Interface "CMFRT BA ISyncPostedDoc")
|
||||
begin
|
||||
SyncImpl.CMFRTBASyncPostedSalesDoc(Rec);
|
||||
end;
|
||||
|
||||
[IntegrationEvent(false, false)]
|
||||
local procedure OnBeforeCMFRTBASyncPostedSalesDoc(SalesDocNo: Code[20]; var Handled: Boolean)
|
||||
begin
|
||||
end;
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: architecture
|
||||
keywords: [codeunit, base-table, entry-point, coupling, architecture, cross-codeunit, subscriber]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# CMFRT calls to implementation codeunits from base table only
|
||||
|
||||
## Description
|
||||
|
||||
In the CMFRT extension architecture, all calls to implementation codeunits must originate from the base-table object. No AL object other than the base table may call a CMFRT implementation codeunit directly. Event subscriber codeunits handle platform or application events and call the base table's entry-point procedures — not the implementation codeunit directly. Pages and reports call base-table procedures. This keeps the base table as the single integration point for all business-logic calls and ensures that `OnBefore`/`OnAfter` integration events fire consistently regardless of where a workflow begins.
|
||||
|
||||
## Best Practice
|
||||
|
||||
When a subscriber codeunit handles an event and needs to trigger a CMFRT operation, it calls the relevant base-table procedure. When a page action initiates business logic, it calls the base-table procedure. The implementation codeunit is an internal detail of the base table and should never appear in `using` clauses or variable declarations of pages, reports, or subscriber codeunits.
|
||||
|
||||
See sample: `cmfrt-calls-from-base-table-only.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Calling an implementation codeunit directly from a page, report, subscriber codeunit, or any object other than the base table. Direct calls bypass the base table's integration events, making the operation invisible to dependent extensions that subscribed to those events. This also creates hidden coupling between the caller and the implementation, which breaks when the implementation codeunit is renamed or replaced under the interface.
|
||||
|
||||
See sample: `cmfrt-calls-from-base-table-only.bad.al`.
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Two global procedures in one codeunit — mixed concerns, impossible to
|
||||
// apply interface injection independently to each operation.
|
||||
codeunit 2045710 "CMFRT BA Price Utilities"
|
||||
{
|
||||
procedure CMFRTBACreateSalesPrice(ItemNo: Code[20]; UnitPrice: Decimal)
|
||||
begin
|
||||
end;
|
||||
|
||||
// Second global entry point belongs in its own codeunit with its own interface.
|
||||
procedure CMFRTBADeleteExpiredPrices(ItemNo: Code[20]; CutoffDate: Date)
|
||||
begin
|
||||
end;
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// One codeunit — one global entry point — one interface.
|
||||
interface "CMFRT BA ICreateSalesPrice"
|
||||
{
|
||||
procedure CMFRTBACreateSalesPrice(ItemNo: Code[20]; UnitPrice: Decimal);
|
||||
}
|
||||
|
||||
codeunit 2045710 "CMFRT BA CreateSalesPrice Impl" implements "CMFRT BA ICreateSalesPrice"
|
||||
{
|
||||
procedure CMFRTBACreateSalesPrice(ItemNo: Code[20]; UnitPrice: Decimal)
|
||||
begin
|
||||
CMFRTBAValidateItem(ItemNo);
|
||||
CMFRTBAWriteSalesPrice(ItemNo, UnitPrice);
|
||||
end;
|
||||
|
||||
local procedure CMFRTBAValidateItem(ItemNo: Code[20])
|
||||
begin
|
||||
end;
|
||||
|
||||
local procedure CMFRTBAWriteSalesPrice(ItemNo: Code[20]; UnitPrice: Decimal)
|
||||
begin
|
||||
end;
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: architecture
|
||||
keywords: [codeunit, single-responsibility, entry-point, interface, architecture, coupling]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# CMFRT one codeunit one global function
|
||||
|
||||
## Description
|
||||
|
||||
In the CMFRT extension architecture, each implementation codeunit exposes exactly one global procedure. That procedure is the codeunit's entry point and corresponds directly to the single interface the codeunit implements. Helper logic is placed in local procedures within the same codeunit and is never exposed as additional global procedures. This rule enforces single responsibility at the AL codeunit boundary: one codeunit, one concern, one interface, one entry point.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Create one implementation codeunit per functional concern. If a codeunit accumulates a second global procedure that has a distinct concern, extract that procedure into its own codeunit with its own interface. Keep local procedures `local` so callers outside the codeunit cannot bypass the interface contract.
|
||||
|
||||
See sample: `cmfrt-one-codeunit-one-function.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Placing multiple global procedures in a single implementation codeunit. A multi-entry-point codeunit mixes concerns, prevents the interface injection pattern from being applied independently to each concern, and grows into a difficult-to-test utility class. Callers that skip the base-table entry point and call implementation procedures directly bypass integration events and violate the architecture.
|
||||
|
||||
See sample: `cmfrt-one-codeunit-one-function.bad.al`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue