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.
Last updated: September 2026

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 RunBaseBatch paradigm. 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 via SysOperationExecutionMode (Synchronous, Asynchronous, ScheduledBatch, and ReliableAsynchronous) and natively supports multi-threaded batch task bundling via BatchHeader.


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 DimensionLegacy RunBaseBatch FrameworkModern SysOperation Framework
Design PatternMonolithic class handling UI, data, and executionModel-View-Controller (MVC) separation
State SerializationBinary container packing via pack() and unpack() macrosAutomated XML/JSON serialization via Data Contracts
UI GenerationDialog elements declared procedurally via DialogFieldAuto-generated from Data Contracts or custom UI Builder
Execution TiersAmbiguous client/server tier transitionsExplicit server-side execution via [SysEntryPointAttribute]
Execution ModesLimited to interactive client run or batchSynchronous, Asynchronous, ScheduledBatch, ReliableAsynchronous
Extensibility & TestingDifficult to isolate for unit tests without UI instantiationHigh 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 the validate() 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 to true, 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 using registerOverrideMethod must be called in postRun(), not in postBuild().
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 BehaviorRuntime Execution TierBatch Job Created?Browser Close Resilient?Primary Use Case
SynchronousUI blocked until execution finishesInteractive client session / IIS WorkerNoNo (aborts if browser disconnects)Quick calculations, immediate dialog-driven validation
AsynchronousNon-blocking; user continues workingBackground thread pool on AOSNoNo (tied to active user session)Moderate tasks (< 1-2 min) where user needs UI responsiveness
ScheduledBatchPrompt provides "Run in background" tabServer Batch Engine (BatchJob / Batch)YesYes (persisted in database queue)High-volume batch workloads, scheduled recurring nightly runs
ReliableAsynchronousNon-blocking; dispatches immediatelyServer Batch Engine via dedicated queueYesYes (guaranteed execution via batch)Critical background tasks that must run immediately and survive disconnects
  1. 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.
  2. 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.
  3. 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.
  4. 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

  1. The master controller instantiates a BatchHeader object.
  2. Business data is partitioned into discrete chunks (e.g., by Customer Group or Item Range).
  3. For each partition, an individual runtime batch task (BatchInfo / runtime controller task) is created.
  4. Tasks are appended using batchHeader.addRuntimeTask().
  5. Optional inter-task dependencies are established using batchHeader.addDependency().
  6. 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

  1. Data Contract with Validation: Declare InterestCalculationContract implementing SysOperationValidatable. Decorate member methods with [DataMemberAttribute]. In validate(), ensure parmCutOffDate() <= DateTimeUtil::getSystemDate(DateTimeUtil::getUserPreferredTimeZone()).
  2. Service Business Logic: Declare InterestCalculationService with entry method [SysEntryPointAttribute(true)] public void calculate(InterestCalculationContract _contract). The method iterates matching accounts and calculates ledger vouchers within a ttsbegin/ttscommit block.
  3. Service Controller: Implement InterestCalculationController extending SysOperationServiceController. In construct(), set execution mode to SysOperationExecutionMode::ScheduledBatch. In main(), invoke startOperation().
  4. 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 of postRun() A scenario asks where to call dfControl.registerOverrideMethod() to capture field modifications in a custom SysOperationUIBuilder. Calling registerOverrideMethod in postBuild() throws a runtime NullReferenceException or fails silently because the underlying FormRun and form controls are not yet fully instantiated or attached to the dialog. You must register control method overrides in postRun().

[!WARNING] Exam Trap 3: Selecting RunBaseBatch for New Feature Development Exam questions often describe a new batch requirement and offer RunBaseBatch and SysOperation among the choices. In modern Dynamics 365 Finance and Operations, RunBaseBatch is considered legacy. SysOperation is 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.

Loading diagram...
SysOperation MVC Architecture and Execution Lifecycle
Test Your Knowledge

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?

A
B
C
D
Test Your Knowledge

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
B
C
D
Test Your Knowledge

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?

A
B
C
D
Test Your Knowledge

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?

A
B
C
D