10.2 Workflow Framework & SysExtensionSerializer
Key Takeaways
- The Workflow framework coordinates business approval and task routing through Workflow Types, Categories, Tasks, Approvals, and Automated Tasks.
- The Workflow Document class (extending WorkflowDocument) defines the root query that exposes transactional data fields for conditional routing, hierarchy evaluation, and email tokens.
- Document status transitions must be executed exclusively within dedicated Workflow Event Handlers (such as WorkflowCompletedEventHandler and WorkflowCanceledEventHandler) rather than UI action controls.
- Workflows are initiated programmatically using Workflow::activateFromWorkflowType() or Workflow::activateFromWorkflowConfigurationId(), while actions are processed via WorkflowWorkItem::action().
- The SysExtensionSerializer framework enables polymorphic table extension, storing custom and ISV attributes in dedicated extension tables without altering base table schemas or increasing row size.
10.2 Workflow Framework & SysExtensionSerializer
Quick Answer: The Workflow framework in Dynamics 365 Finance and Operations provides a configurable business process engine for document approval, task assignment, and automated routing. A complete workflow implementation requires a Workflow Type, a Workflow Category, a Workflow Document class (
WorkflowDocument) that exposes underlying query fields for conditions, and dedicated Event Handlers (WorkflowCompletedEventHandler,WorkflowCanceledEventHandler) to mutate transactional status. In code, workflows are submitted usingWorkflow::activateFromWorkflowType()and advanced usingWorkflowWorkItem::action(). In parallel, for enterprise table extensibility, Microsoft provides theSysExtensionSerializerframework: an architectural pattern that serializes custom attributes into separate satellite extension tables linked byRecId, preventing base table bloat and ensuring upgrade stability across ISV packages.
1. Workflow Framework Architecture and Core Artifacts
Enterprise governance mandates that documents such as Purchase Orders, Expense Reports, and Vendor Invoices undergo multi-level approvals before posting. The Dynamics 365 Workflow framework provides a visual configuration engine backed by server-side X++ metadata artifacts.
Workflow Framework Architecture
┌─────────────────────────────────────────────────────────────┐
│ WORKFLOW CATEGORY │
│ (Groups workflow types into functional modules, e.g., AP) │
└──────────────────────────────┬──────────────────────────────┘
│ Categorizes
▼
┌─────────────────────────────────────────────────────────────┐
│ WORKFLOW TYPE │
│ • Defines supported elements (Tasks, Approvals) │
│ • Points to Workflow Document Class │
│ • Points to SubmitToWorkflowMenuItem │
└──────────────┬──────────────────────────────┬───────────────┘
│ References │ Governs
▼ ▼
┌──────────────────────────────┐ ┌────────────────────────────┐
│ WORKFLOW DOCUMENT CLASS │ │ WORKFLOW ELEMENTS │
│ (extends WorkflowDocument) │ │ 1. Tasks (User Actions) │
│ • Implements getQueryName() │ │ 2. Approvals (Decisions) │
│ • Exposes fields to engine │ │ 3. Automated Tasks (Code) │
└──────────────────────────────┘ └────────────┬───────────────┘
│ Fires
▼
┌──────────────────────────────┐
│ WORKFLOW EVENT HANDLERS │
│ • Element Completed / Denied│
│ • Workflow Completed │
│ • Workflow Canceled │
└──────────────────────────────┘
Core AOT Workflow Components
| Workflow Component | AOT Type / Base Class | Architectural Role | Lifecycle Trigger / Invocation |
|---|---|---|---|
| Workflow Category | Workflow Category | Groups related workflow types within a functional module (e.g., Accounts Payable, Procurement) | Visual grouping in configuration menus |
| Workflow Type | Workflow Type | Root template defining supported elements, document class, and submission menu item | Selected during initial workflow configuration creation |
| Workflow Document Class | Extends WorkflowDocument | Exposes relational table fields to the visual workflow designer via getQueryName() | Invoked by workflow engine to evaluate conditional rules and placeholders |
| Workflow Tasks | Workflow Task | Single-outcome work assigned to a user requiring completion action | Created by engine; assigned as work item in user queue |
| Workflow Approvals | Workflow Approval | Multi-outcome decision node (Approve, Reject, Request Change, Delegate) | Evaluated by user via action bar or programmatically via WorkflowWorkItem |
| Automated Tasks | Workflow Automated Task | Unattended code execution running directly on the server batch engine without human intervention | Executed synchronously or asynchronously by workflow batch runtime |
| Workflow Event Handlers | Implements WorkflowCompletedEventHandler, etc. | Executes transactional state mutations when workflow milestones occur | Dispatched by workflow engine on state transition |
- Workflow Category: Organizes workflow types within a functional module (e.g.,
AccountsPayable,Procurement). - Workflow Type: The root template defining how a document integrates with the workflow subsystem. It associates the Workflow Document class, specifies supported tasks and approvals, and binds the
SubmitToWorkflowMenuItem. - Workflow Document Class: An X++ class extending
WorkflowDocument. It overridesgetQueryName()to return the AOT Query name that exposes the document's relational data to the workflow configuration designer. The fields returned by this query drive conditional branch decisions, approval limit evaluations, and email template placeholders. - Workflow Elements:
- Tasks: Work assigned to a user requiring a single completion action (e.g., "Review order details").
- Approvals: Multi-outcome decision nodes supporting Approve, Reject, Request Change, and Delegate. Can be configured for single approver, majority vote, or hierarchical escalation.
- Automated Tasks: Unattended steps executed directly by the system without human intervention (e.g., auto-posting or generating an audit entry).
public class PurchReqWorkflowDocument extends WorkflowDocument
{
public QueryName getQueryName()
{
// Returns the AOT query that provides fields for workflow conditions
return queryStr(PurchReqDocumentQuery);
}
}
2. Workflow Event Handlers and Document State Transitions
Managing document status (e.g., Draft -> InReview -> Approved -> Rejected) must never be handled directly in form UI controls. Because workflows execute asynchronously in the batch engine, status transitions must be executed strictly inside Workflow Event Handlers.
Primary Event Handlers
WorkflowStartedEventHandler: Invoked immediately when a workflow instance is activated. Typically updates document state fromDrafttoSubmittedorInReview.WorkflowCompletedEventHandler: Invoked when the workflow traverses to an end milestone successfully. Sets document status toApprovedand unlocks the document for posting.WorkflowCanceledEventHandler: Invoked when the submitter recalls the workflow or an administrator cancels it. Resets state toDraft.WorkflowElementCompletedEventHandler: Invoked when a specific task or approval sub-step completes.
public class PurchReqWorkflowEventHandler implements WorkflowCompletedEventHandler,
WorkflowCanceledEventHandler
{
public void handle(WorkflowEventArgs _workflowEventArgs)
{
WorkflowContext context = _workflowEventArgs.parmWorkflowContext();
PurchReqTable purchReqTable;
// Retrieve document buffer using the context RecId
purchReqTable = PurchReqTable::findRecId(context.parmRecId(), true);
if (purchReqTable)
{
ttsbegin;
purchReqTable.ReqStatus = PurchReqStatus::Approved;
purchReqTable.update();
ttscommit;
info(strFmt("Purchase Requisition %1 has been approved.", purchReqTable.PurchReqId));
}
}
public void handleCanceled(WorkflowEventArgs _workflowEventArgs)
{
WorkflowContext context = _workflowEventArgs.parmWorkflowContext();
PurchReqTable purchReqTable = PurchReqTable::findRecId(context.parmRecId(), true);
if (purchReqTable)
{
ttsbegin;
purchReqTable.ReqStatus = PurchReqStatus::Draft;
purchReqTable.update();
ttscommit;
info(strFmt("Purchase Requisition %1 returned to Draft.", purchReqTable.PurchReqId));
}
}
}
3. Programmatic Workflow Execution: Submit, Approve, Recall
While users interact with workflows primarily through the yellow workflow action bar on forms, developers often need to automate submission, approval, or cancellation through batch jobs, integration services, or automated unit tests.
Submitting a Workflow in Code
To submit a document programmatically, invoke Workflow::activateFromWorkflowType() passing the workflow type name, the document RecId, a comment, and an initial submission flag:
public static void submitPurchReqToWorkflow(PurchReqTable _purchReqTable, WorkflowComment _comment)
{
WorkflowTypeName workflowTemplateName = workflowTypeStr(PurchReqTemplate);
WorkflowVersionTable versionTable;
// Activate the workflow instance
WorkflowCorrelationId correlationId = Workflow::activateFromWorkflowType(
workflowTemplateName,
_purchReqTable.RecId,
_comment,
NoYes::No);
ttsbegin;
_purchReqTable.selectForUpdate(true);
_purchReqTable.ReqStatus = PurchReqStatus::InReview;
_purchReqTable.update();
ttscommit;
}
Approving, Rejecting, and Recalling Work Items in Code
When a workflow requires action on an assigned work item, use WorkflowWorkItem::action() or dedicated action methods:
public static void approveWorkItem(WorkflowWorkItemTable _workItem, WorkflowComment _comment)
{
// Complete the approval work item programmatically
WorkflowWorkItemActionManager::dispatchWorkItemAction(
_workItem,
_comment,
curUserId(),
WorkflowWorkItemActionType::Complete, // Or Approve
menuItemActionStr(PurchReqApprovalApprove));
}
4. The SysExtensionSerializer Framework: Polymorphic Architecture
In large-scale enterprise deployments, multiple Independent Software Vendor (ISV) solutions and customer modifications frequently extend standard core tables such as SalesTable, PurchTable, or InventTable. If every ISV adds 30 new columns directly to SalesTable, the database row size rapidly approaches SQL Server's 8,060-byte limit, cache performance deteriorates, and table schema locks increase.
The SysExtensionSerializer framework provides an architectural solution: it implements a polymorphic extension design that persists custom attributes in isolated, dedicated satellite extension tables linked back to the base table by RecId.
SysExtensionSerializer Architecture
┌────────────────────────────────────────────────────────┐
│ BASE TABLE (e.g., SalesTable) │
│ • Core standard Microsoft schema │
│ • Zero bloat; unpolluted row size │
└───────────────────────────┬────────────────────────────┘
│ 1-to-1 Relation (RecId)
┌──────────────────┴──────────────────┐
▼ ▼
┌──────────────────────────────┐ ┌──────────────────────────────┐
│ ISV 1 EXTENSION TABLE │ │ ISV 2 EXTENSION TABLE │
│ (SalesTable_ISV_Logistics) │ │ (SalesTable_ISV_TaxEngine) │
│ • CarrierTrackingCode │ │ • FiscalInvoiceCode │
│ • CustomsClearanceStatus │ │ • ExemptionCertificateNum │
└──────────────────────────────┘ └──────────────────────────────┘
▲ ▲
└──────────────────┬──────────────────┘
│ Managed by
┌───────────────────────────┴────────────────────────────┐
│ SysExtensionSerializer Framework │
│ • Intercepts insert/update/delete on SalesTable │
│ • Automatically serializes/hydrates extension records │
│ • Maintains polymorphic isolation across models │
└────────────────────────────────────────────────────────┘
Direct Table Extension vs. SysExtensionSerializer Architecture
| Architectural Dimension | Direct Table Extension (Table.Extension) | SysExtensionSerializer Framework |
|---|---|---|
| Storage Strategy | Adds columns directly to the base SQL table row | Stores attributes in dedicated satellite extension tables linked by RecId foreign key |
| SQL Server Row Size Impact | Directly consumes the 8,060-byte SQL Server in-row page limit | Zero impact on base table row size; extension fields live in separate tables |
| Schema Lock Contention | High risk during deployment; alters core production tables | Low risk; satellite tables are independent entities altered without locking base table |
| ISV & Partner Isolation | Multiple ISVs modifying the same base table risk column collisions and bloated schemas | Complete isolation; each ISV maintains its own satellite table and serializer mapping |
| AOS Cache Footprint | Bloats the standard record buffer in AOS cache even when custom fields are unread | Keeps standard record buffer lean; extension tables are hydrated on-demand or polymorphically |
| CRUD Lifecycle Coordination | Managed natively by SQL engine on single row | Intercepted and synchronized automatically via SysExtensionSerializerExtensionMap |
Core Benefits of SysExtensionSerializer
- Zero Base Table Schema Contention: Extension fields exist in separate SQL tables, avoiding alter-table lock contention during deployments.
- Database Cache Efficiency: Keeps base table record buffers lean in AOS memory cache.
- Independent ISV Lifecycles: An ISV can install, update, or uninstall its satellite table schema without triggering data rebuilds or conflicts on the base table.
- Polymorphic Data Contract Serialization: Handles automatic CRUD synchronization between the base record and satellite records using the framework's internal serialization hooks.
5. Scenario Walk-Through: Requisition Workflow & ISV Carbon Extension
Scenario Description
An enterprise requires all Purchase Requisitions exceeding $50,000 to route to the Corporate VP of Finance. Additionally, an environmental compliance ISV model must track carbon footprint metrics on the requisition lines (PurchReqLine) without altering the core PurchReqLine table definition.
Technical Solution
- Workflow Query Exposure: In
PurchReqWorkflowDocument, exposePurchReqLinejoined toPurchReqTable. In the Workflow designer, configure a condition rule:Where PurchReqTable.TotalAmount > 50000.00. - Event Handler Implementation: Register
PurchReqWorkflowCompletedHandler. When the VP approves the work item, the handler setsPurchReqTable.ReqStatus = PurchReqStatus::Approvedand triggers downstream PO creation. - Polymorphic Carbon Extension: Implement an extension table
PurchReqLine_CarbonExtwith foreign keyPurchReqLineRecId. Implement a serializer mapping class usingSysExtensionSerializerExtensionMapto automatically synchronize carbon attributes whenever requisition lines are created or modified.
6. Real-World Exam Traps: Workflow & SysExtensionSerializer
[!WARNING] Exam Trap 1: Performing Document State Updates Directly in Form UI Code An exam scenario describes a developer writing code in the
clicked()method of a workflow submit button to immediately update the document status toApproved. This is an architectural anti-pattern. Workflows execute asynchronously in background batch services. State transitions must strictly be handled by Workflow Event Handlers (WorkflowCompletedEventHandler,WorkflowCanceledEventHandler).
[!WARNING] Exam Trap 2: Incorrect
WorkflowDocumentQuery Configuration When authoring a customWorkflowDocumentclass, the underlying AOT Query returned bygetQueryName()must have the document table as its root data source. If child tables are placed as the root, the workflow runtime cannot bind the document contextRecId, resulting in runtime activation errors.
[!WARNING] Exam Trap 3: Confusing Workflow Tasks vs. Approvals A Workflow Task represents a single-action assignment (e.g., "Complete shipping address") that finishes when marked complete. A Workflow Approval represents an approval hierarchy with multiple decision branches (Approve, Reject, Request Change, Delegate).
[!WARNING] Exam Trap 4: Direct Table Alteration vs.
SysExtensionSerializerQuestions testing enterprise ISV architecture will present choices between adding 50 custom columns directly toCustTablevia standard table extension versus creating a dedicated extension table managed bySysExtensionSerializer. For high-volume polymorphic ISV models,SysExtensionSerializeris the Microsoft-recommended best practice to prevent base table bloat.
A developer needs to programmatically submit a Purchase Requisition record to an existing workflow template from a batch integration service. Which method should the developer invoke in X++?
An enterprise development team is designing an approval process for vendor contracts. When the workflow approval is completed and fully signed off, the contract record status must change from InReview to Approved. Where must the status update logic be implemented according to Microsoft architectural standards?
An ISV solution requires adding 40 industry-specific compliance and environmental tracking fields to the core standard SalesTable. The lead architect rejects adding these fields directly to a SalesTable table extension to prevent SQL Server row-size bloat and cache performance degradation. Which architectural framework should be implemented to persist these fields in a satellite table linked by RecId?
When developing a custom workflow for a custom business document, the developer creates a class extending WorkflowDocument. What is the primary requirement of this class to allow the workflow visual configuration designer to build conditional branching rules?