10.1 SysOperation Framework Architecture
Key Takeaways
- The SysOperation framework (formerly Business Operation Framework) supersedes RunBaseBatch by implementing Model-View-Controller (MVC) separation of concerns and automated contract serialization.
- A SysOperation solution comprises four primary classes: Data Contract ([DataContractAttribute], [DataMemberAttribute]), Service Class ([SysEntryPointAttribute]), Controller Class (SysOperationServiceController), and UI Builder Class (SysOperationUIBuilder).
- SysOperation supports four distinct execution modes via SysOperationExecutionMode: Synchronous (blocks UI), Asynchronous (non-blocking thread), ScheduledBatch (enqueued to batch server), and ReliableAsynchronous (guaranteed batch-backed asynchronous run).
- Custom UI behavior, runtime control additions, and event overrides must be registered in the UI Builder postRun() method rather than postBuild() to ensure dialog controls are fully instantiated.
- High-throughput enterprise workloads achieve parallel execution by using BatchHeader to spawn independent runtime batch tasks (addRuntimeTask) with explicit parent-child dependency trees.
10.1 SysOperation Framework Architecture
Quick Answer: The SysOperation framework (formerly known as the Business Operation Framework or BOF) is the modern Microsoft-recommended pattern for batch and interactive processing in Dynamics 365 Finance and Operations, completely replacing the legacy
RunBaseBatchparadigm. By applying the Model-View-Controller (MVC) architectural pattern, SysOperation decouples business logic, state data, and user interface controls into isolated, testable components. The architecture relies on four foundational classes: the Data Contract ([DataContractAttribute],[DataMemberAttribute]), the Service Class ([SysEntryPointAttribute]), the Controller Class (SysOperationServiceController), and the optional UI Builder Class (SysOperationUIBuilder). It provides four execution modes viaSysOperationExecutionMode(Synchronous,Asynchronous,ScheduledBatch, andReliableAsynchronous) and natively supports multi-threaded batch task bundling viaBatchHeader.
1. Evolution from RunBaseBatch to SysOperation
In legacy Dynamics AX systems, batchable background logic relied heavily on the RunBase and RunBaseBatch base classes. While functional, RunBaseBatch forced business logic, dialog construction, batch parameters, and serialization into a single monolithic class. State persistence depended on macro-driven binary packing (#CurrentList, pack(), unpack()), which was notoriously fragile across code updates and model refactoring.
The SysOperation framework resolves these structural liabilities through strict separation of responsibilities:
| Architectural Dimension | Legacy RunBaseBatch Framework | Modern SysOperation Framework |
|---|---|---|
| Design Pattern | Monolithic class handling UI, data, and execution | Model-View-Controller (MVC) separation |
| State Serialization | Binary container packing via pack() and unpack() macros | Automated XML/JSON serialization via Data Contracts |
| UI Generation | Dialog elements declared procedurally via DialogField | Auto-generated from Data Contracts or custom UI Builder |
| Execution Tiers | Ambiguous client/server tier transitions | Explicit server-side execution via [SysEntryPointAttribute] |
| Execution Modes | Limited to interactive client run or batch | Synchronous, Asynchronous, ScheduledBatch, ReliableAsynchronous |
| Extensibility & Testing | Difficult to isolate for unit tests without UI instantiation | High testability; Service and Contract classes can be tested headlessly |
2. Core Architectural Classes
The SysOperation framework distributes responsibilities across four distinct classes, ensuring clean decoupling between parameters, presentation, business rules, and operational orchestration.
SysOperation MVC Architecture
┌─────────────────────────────────────────────────────────────┐
│ CONTROLLER CLASS │
│ (extends SysOperationServiceController) │
│ • Defines execution mode (SysOperationExecutionMode) │
│ • Coordinates UI prompt and unpacks caller Args │
│ • Dispatches payload to Service Class │
└──────────────┬──────────────────────────────┬───────────────┘
│ Coordinates │ Passes Contract
▼ ▼
┌──────────────────────────────┐ ┌────────────────────────────┐
│ UI BUILDER CLASS │ │ SERVICE CLASS │
│ (extends SysOperationUI... ) │ │ • Core business logic │
│ • Customizes dialog layout │ │ • [SysEntryPointAttribute]│
│ • Dynamic field visibility │ │ • Executes synchronously │
│ • Overrides control events │ │ or as server batch task │
└──────────────┬───────────────┘ └────────────▲───────────────┘
│ Hydrates values │ Consumes parameters
└──────────────┬───────────────┘
▼
┌──────────────────────────────┐
│ DATA CONTRACT CLASS │
│ • [DataContractAttribute] │
│ • [DataMemberAttribute] │
│ • Strong typed parameters │
└──────────────────────────────┘
1. Data Contract Class (Model)
The Data Contract acts as the pure data container holding parameters required by the business process. It contains private member variables exposed through public accessor methods (parm...).
[DataContractAttribute]: Decorates the class definition to identify it as a serializable contract.[DataMemberAttribute('ExternalName')]: Decorates each parameter accessor method. Without this attribute, the framework ignores the method during serialization.SysOperationValidatable: An optional interface allowing contract-level validation via thevalidate()method before processing commences.
[DataContractAttribute]
public class SalesInvoiceBatchContract implements SysOperationValidatable
{
private CustAccount custAccount;
private TransDate invoiceDate;
private boolean printCopy;
[DataMemberAttribute('CustAccount')]
public CustAccount parmCustAccount(CustAccount _custAccount = custAccount)
{
custAccount = _custAccount;
return custAccount;
}
[DataMemberAttribute('InvoiceDate')]
public TransDate parmInvoiceDate(TransDate _invoiceDate = invoiceDate)
{
invoiceDate = _invoiceDate;
return invoiceDate;
}
[DataMemberAttribute('PrintCopy')]
public boolean parmPrintCopy(boolean _printCopy = printCopy)
{
printCopy = _printCopy;
return printCopy;
}
public boolean validate()
{
boolean isValid = true;
if (invoiceDate == dateNull())
{
isValid = checkFailed("Invoice date must be specified.");
}
return isValid;
}
}
2. Service Class (Business Logic Engine)
The Service class contains the actual algorithmic workload. It remains completely unaware of dialogs, form controls, or caller arguments. It accepts the Data Contract as an input parameter and performs the business logic.
[SysEntryPointAttribute(true/false)]: Marks the service entry method. If set totrue, the framework performs automated Code Access Security (CAS) authorization checks on the invoking user.
public class SalesInvoiceBatchService
{
[SysEntryPointAttribute(false)]
public void processInvoice(SalesInvoiceBatchContract _contract)
{
CustAccount customer = _contract.parmCustAccount();
TransDate targetDate = _contract.parmInvoiceDate();
boolean shouldPrint = _contract.parmPrintCopy();
info(strFmt("Processing invoices for account %1 as of %2.", customer, targetDate));
// Core batch calculation logic executes here
}
}
3. Controller Class (Controller Orchestrator)
The Controller orchestrates initialization, determines whether to display a dialog, sets execution modes, and passes caller arguments (Args). Developers extend SysOperationServiceController or use it directly via factory construction.
public class SalesInvoiceBatchController extends SysOperationServiceController
{
public void new()
{
super();
// Bind service class, service method, and execution mode
this.initializeFromArgs(new Args());
}
public static SalesInvoiceBatchController construct(SysOperationExecutionMode _executionMode = SysOperationExecutionMode::Synchronous)
{
SalesInvoiceBatchController controller = new SalesInvoiceBatchController();
controller.initServiceParameters(
classStr(SalesInvoiceBatchService),
methodStr(SalesInvoiceBatchService, processInvoice),
_executionMode);
return controller;
}
public static void main(Args _args)
{
SalesInvoiceBatchController controller = SalesInvoiceBatchController::construct();
controller.startOperation(); // Prompts dialog and initiates execution
}
}
4. UI Builder Class (View Customizer)
When the auto-generated dialog created from the Data Contract is insufficient, a custom UI Builder class extending SysOperationUIBuilder is implemented. It provides fine-grained control over dialog controls, lookups, dynamic visibility, and event overrides.
- Binding UI Builder to Contract: The Data Contract class is linked to its UI Builder using
[SysOperationContractProcessingAttribute(classStr(MyUIBuilder))]. - Lifecycle Methods:
build(): Constructs the abstract dialog structure.postBuild(): Adds additional unbound controls or custom groups after standard contract fields are bound.postRun(): Overrides control methods and binds event handlers. Crucially, event handler overrides usingregisterOverrideMethodmust be called inpostRun(), not inpostBuild().
public class SalesInvoiceBatchUIBuilder extends SysOperationUIBuilder
{
private DialogField dfCustAccount;
private DialogField dfPrintCopy;
public void postBuild()
{
super();
// Retrieve dialog controls bound to contract members
SalesInvoiceBatchContract contract = this.dataContractObject() as SalesInvoiceBatchContract;
dfCustAccount = this.bindInfo().getDialogField(contract, methodStr(SalesInvoiceBatchContract, parmCustAccount));
dfPrintCopy = this.bindInfo().getDialogField(contract, methodStr(SalesInvoiceBatchContract, parmPrintCopy));
}
public void postRun()
{
super();
// Register control event overrides after dialog is fully rendered
dfCustAccount.registerOverrideMethod(methodStr(FormStringControl, modified), methodStr(SalesInvoiceBatchUIBuilder, custAccountModified), this);
}
public boolean custAccountModified(FormStringControl _control)
{
boolean isModified = _control.modified();
if (isModified && _control.text() == '')
{
dfPrintCopy.value(false);
dfPrintCopy.enabled(false);
}
else
{
dfPrintCopy.enabled(true);
}
return isModified;
}
}
3. SysOperation Execution Modes
The SysOperationExecutionMode enumeration dictates how and where the operational payload is processed:
SysOperationExecutionMode Pipeline Options
Caller calls controller.startOperation()
│
├──> Synchronous --> Runs immediately on current client thread (Blocks UI session)
├──> Asynchronous --> Spawns background thread immediately (Non-blocking UI)
├──> ScheduledBatch --> Packages payload into BatchJob / Batch task for Batch Engine
└──> ReliableAsynchronous --> Queues as guaranteed batch execution; survives client disconnection
Execution Mode (SysOperationExecutionMode) | Client UI Behavior | Runtime Execution Tier | Batch Job Created? | Browser Close Resilient? | Primary Use Case |
|---|---|---|---|---|---|
Synchronous | UI blocked until execution finishes | Interactive client session / IIS Worker | No | No (aborts if browser disconnects) | Quick calculations, immediate dialog-driven validation |
Asynchronous | Non-blocking; user continues working | Background thread pool on AOS | No | No (tied to active user session) | Moderate tasks (< 1-2 min) where user needs UI responsiveness |
ScheduledBatch | Prompt provides "Run in background" tab | Server Batch Engine (BatchJob / Batch) | Yes | Yes (persisted in database queue) | High-volume batch workloads, scheduled recurring nightly runs |
ReliableAsynchronous | Non-blocking; dispatches immediately | Server Batch Engine via dedicated queue | Yes | Yes (guaranteed execution via batch) | Critical background tasks that must run immediately and survive disconnects |
SysOperationExecutionMode::Synchronous: Executes immediately within the calling user's interactive thread. The client user interface remains blocked until the process completes. Ideal for rapid calculations returning immediate results to an open form.SysOperationExecutionMode::Asynchronous: Executes immediately in a background worker thread without blocking the user interface. If the client browser session terminates before execution finishes, the process may be interrupted.SysOperationExecutionMode::ScheduledBatch: Bypasses immediate execution and schedules the task within the server batch queue (BatchJob/Batch). Users configure recurrence, batch alerts, and batch group assignments in the standard batch dialog.SysOperationExecutionMode::ReliableAsynchronous: Combines asynchronous user experience with batch queue reliability. The operation is dispatched as a guaranteed batch execution task, ensuring completion even if the user closes their browser immediately after submitting.
4. Batch Bundling and Multi-Threaded Processing
For enterprise workloads processing millions of transactions (such as monthly depreciation or retail statement posting), running a single sequential thread creates severe batch windows bottlenecks. SysOperation supports high-throughput multi-threading and batch task bundling using the BatchHeader API.
Multi-Threading Workflow with BatchHeader
- The master controller instantiates a
BatchHeaderobject. - Business data is partitioned into discrete chunks (e.g., by Customer Group or Item Range).
- For each partition, an individual runtime batch task (
BatchInfo/ runtime controller task) is created. - Tasks are appended using
batchHeader.addRuntimeTask(). - Optional inter-task dependencies are established using
batchHeader.addDependency(). - The entire batch graph is committed to the database using
batchHeader.save().
public class LargeScalePostingController extends SysOperationServiceController
{
public void runMultiThreadedBatch(List _customerBatches)
{
BatchHeader batchHeader = this.batchInfo().parmBatchHeader();
if (!batchHeader)
{
batchHeader = BatchHeader::construct(this.parmCurrentBatch().BatchJobId);
}
ListEnumerator enumerator = _customerBatches.getEnumerator();
while (enumerator.moveNext())
{
CustAccount account = enumerator.current();
// 1. Prepare dedicated contract for this thread
SalesInvoiceBatchContract contract = new SalesInvoiceBatchContract();
contract.parmCustAccount(account);
contract.parmInvoiceDate(systemDateGet());
// 2. Prepare child task controller
SalesInvoiceBatchController childController = SalesInvoiceBatchController::construct(SysOperationExecutionMode::ScheduledBatch);
childController.getDataContractObject().setCompany(curExt());
// 3. Add task to batch header
batchHeader.addRuntimeTask(childController, this.parmCurrentBatch().RecId);
}
// 4. Save and schedule all bundled tasks
batchHeader.save();
}
}
5. Scenario Walk-Through: End-of-Month Interest Engine
Business Scenario
A multinational financial organization needs an automated batch job to calculate accrued interest across open customer accounts. The process must prompt the user for an interest calculation cut-off date and allow filtering by Customer Group. The calculation must run as a scheduled batch job on dedicated batch servers, while preventing invalid future dates from being submitted.
Architectural Implementation Solution
- Data Contract with Validation: Declare
InterestCalculationContractimplementingSysOperationValidatable. Decorate member methods with[DataMemberAttribute]. Invalidate(), ensureparmCutOffDate() <= DateTimeUtil::getSystemDate(DateTimeUtil::getUserPreferredTimeZone()). - Service Business Logic: Declare
InterestCalculationServicewith entry method[SysEntryPointAttribute(true)] public void calculate(InterestCalculationContract _contract). The method iterates matching accounts and calculates ledger vouchers within attsbegin/ttscommitblock. - Service Controller: Implement
InterestCalculationControllerextendingSysOperationServiceController. Inconstruct(), set execution mode toSysOperationExecutionMode::ScheduledBatch. Inmain(), invokestartOperation(). - Batch Scheduling: When launched, the framework renders the parameters dialog alongside the standard Run in the background (Batch) tab, allowing the financial team to set recurrence patterns without custom scheduling code.
6. Real-World Exam Traps: SysOperation Framework
[!WARNING] Exam Trap 1: Missing
[DataMemberAttribute]on Contract Parm Methods If a developer defines getter/setter methods on a Data Contract but forgets to decorate them with[DataMemberAttribute], the code compiles without error. However, at runtime, the field silently fails to serialize. The dialog will not show the field, or values entered by the user will arrive as empty/default values in the Service class.
[!WARNING] Exam Trap 2: Registering UI Overrides in
postBuild()Instead ofpostRun()A scenario asks where to calldfControl.registerOverrideMethod()to capture field modifications in a customSysOperationUIBuilder. CallingregisterOverrideMethodinpostBuild()throws a runtimeNullReferenceExceptionor fails silently because the underlyingFormRunand form controls are not yet fully instantiated or attached to the dialog. You must register control method overrides inpostRun().
[!WARNING] Exam Trap 3: Selecting
RunBaseBatchfor New Feature Development Exam questions often describe a new batch requirement and offerRunBaseBatchandSysOperationamong the choices. In modern Dynamics 365 Finance and Operations,RunBaseBatchis considered legacy.SysOperationis always the correct answer due to its MVC decoupling, automated serialization, and execution flexibility.
[!WARNING] Exam Trap 4: Forgetting
[SysEntryPointAttribute]on Service Methods When exposing business logic through SysOperation or Custom Services, the service method must be decorated with[SysEntryPointAttribute]. Omitting this attribute causes runtime authorization failures or prevents the framework from properly marshaling data across tiers.
A developer needs to implement a long-running batch calculation in Dynamics 365 Finance and Operations. The operation must execute in the background without freezing the user interface, but it must be backed by the server batch queue to guarantee execution even if the user immediately closes their web browser session. Which SysOperationExecutionMode must be configured on the controller?
An X++ developer creates a custom UI Builder class extending SysOperationUIBuilder to add dynamic lookup filtering to a dialog control. In which lifecycle method of the SysOperationUIBuilder must the developer call registerOverrideMethod to ensure that the dialog control is fully instantiated before binding the event?
A developer writes a custom SysOperation Data Contract class with private variables and public accessor methods. However, when the controller runs and prompts the user dialog, one of the critical parameters does not appear on the form, and its value is not passed to the service class. What is the root cause of this defect?
An enterprise organization needs to process 500,000 retail transactions in nightly batch windows. To achieve acceptable throughput, the processing logic must be split across multiple parallel threads executing on available batch server instances. How should this multi-threaded bundling be implemented using the SysOperation framework?