Procedure e superficie pubblica

In AL una procedure senza modificatori è pubblica: qualunque estensione che dipenda dalla tua può chiamarla. local procedure la confina all’oggetto che la contiene, internal procedure la rende visibile solo dentro la stessa app. La scelta non è stilistica: ciò che esponi in una libreria condivisa diventa un contratto, e un’app dipendente smette di compilare se ne cambi nome, parametri o tipo di ritorno. Il criterio corretto è minimizzare la superficie pubblica. Ritirare una procedure pubblica non significa cancellarla: si marca Obsolete con state Pending, indicando l’alternativa, e solo dopo un ciclo di release si passa a Removed. Un Record passato per valore viene copiato: var evita la copia e lascia al chiamato modificarlo.

Il modello a eventi

Business Central si estende agganciandosi, non modificando: il codice base non si tocca. Un event publisher è una procedure con corpo vuoto decorata con [IntegrationEvent(...)] o [BusinessEvent(...)]; serve solo a definire un punto di aggancio e i parametri esposti. Un event subscriber è una procedure dentro una codeunit, decorata con [EventSubscriber(...)] che indica object type, oggetto, nome dell’evento ed eventuale element name.

La distinzione che l’esame verifica: un business event è un contratto stabile verso consumatori esterni, pensato per non cambiare né sparire; un integration event è un hook tecnico che chi lo pubblica può ancora far evolvere. Ci sono poi i trigger event generati automaticamente sulle tabelle (insert, modify, delete, rename, validazione di campo) e gli eventi di page: spesso bastano quelli. Pubblicare eventi propri ha senso quando la tua estensione è a sua volta una base su cui altri costruiranno. Infine, i subscriber di una codeunit sono attivi per tutta la sessione; con EventSubscriberInstance = Manual lo diventano solo fra BindSubscription e UnbindSubscription.

Errori, data type e conversioni

Error interrompe l’esecuzione e provoca il rollback della transazione. L’approccio moderno usa ErrorInfo, che porta messaggio breve, dettaglio, record e campo di riferimento e azioni correttive per l’utente. Per le validazioni massive esiste il comportamento collectible: una procedure dichiarata con ErrorBehavior::Collect non si ferma al primo errore ma li accumula, e il chiamante li mostra insieme. Se invece basta sapere se un’operazione è fallita, scegli fra try function e Codeunit.Run: la seconda isola la chiamata in una transazione separata annullata in caso di errore, la prima intercetta l’errore ma non garantisce il rollback delle scritture già eseguite.

Sui data type contano poche distinzioni: Enum, oggetto AL estendibile, contro Option, che non lo è; Text contro Code; i tipi a lunghezza limitata come Text[30], che troncano solo con CopyStr, altrimenti danno errore a runtime. Le conversioni non sono implicite: Evaluate converte da testo restituendo un booleano da verificare, Format produce una rappresentazione dipendente da lingua e formato, da Decimal a Integer serve un arrotondamento esplicito. Per i dati strutturati ci sono i tipi Json e le collection List e Dictionary, per i binari gli stream su campo BLOB.

Install e upgrade code

Quando schema o semantica dei dati cambiano servono oggetti dedicati: una codeunit Subtype = Install, i cui trigger girano alla prima installazione (per company e per database), e una Subtype = Upgrade, che gira solo passando da una versione già installata a una nuova. L’upgrade è il posto dove popolare campi nuovi, spostare dati fra tabelle o normalizzare valori. Va scritto idempotente: può essere rieseguito su company diverse o dopo un fallimento, senza duplicare lavoro già fatto. Il meccanismo standard sono gli upgrade tag: verifichi se il tag esiste, migri solo in caso contrario, registri il tag alla fine. La versione dichiarata in app.json determina l’esecuzione dell’upgrade; campi e tabelle da eliminare vanno prima marcati obsoleti e rimossi in una release successiva.

Trappole tipiche d’esame

  • Reagire alla registrazione di un documento standard senza toccare il codice base → event subscriber: modificare o duplicare la codeunit di posting è escluso per definizione dal modello di estensibilità.
  • Contratto stabile verso consumatori esterni → business event: l’integration event resta modificabile da chi lo pubblica, quindi non risponde a uno scenario che chiede compatibilità garantita.
  • Mostrare tutti gli errori di validazione in una volta → procedure con collectible errors: un Error classico interrompe al primo problema; è la trappola più frequente sull’error handling.
  • Popolare un campo nuovo sui record esistenti alla v2 → codeunit Upgrade con upgrade tag: la codeunit Install non gira su un aggiornamento di versione, e senza tag il codice rischia di rigirare.
  • Subscriber attivo solo dentro un test → EventSubscriberInstance = Manual e binding esplicito: uno statico resterebbe attivo per l’intera sessione.
  • Valori che altre estensioni devono poter ampliare → enum estendibile, non Option: e una procedure pubblica da ritirare si marca obsoleta, non si cancella.