How to use a recurring Integration Endpoint for importing data
You are here
BatchRetryable Bundled Batch Job Example
BatchRetryable Bundled Batch Job Example
BatchRetryable Bundled Batch Job Example
I recently presented at DynamicsMinds on batch jobs and how to design them for specific types of workloads. There was interest in code samples so this in one of those code samples talking about Batch bundling with a review of the code and design. Batch Bundling is a way of designing and managing a batch workload to be a collection of work bundles that need to be executed. This means that tasks are grouped together under 1 batch job with multiple task bundles where each task is given a set of work to accomplish as part of that tasks workload. In my last article on this, I compared batch capacity to be like a highway. If Individual task modeling is letting cars on the highway one at a time using some kind of meter, then batch bundling would be to let x cars onto the highway where you define what x is. This style of work item manage can help with some of the complexities of batch task management in terms of dependencies and overall scalability. The basic elements of the batch job don't change; we still need a service, a controller, and a data contract.
Data Contract
This is an example of a data contract class. We are passing in a List of RecIds to process, the bundle size - how many records we're going to process, and whether or not to throw an artificial error to simulate a BatchRetry.
[DataContractAttribute]
class AAXBatchBundleDataContract
{
List RecIds;
int64 workItemCount;
int64 bundleSize;NoYes throwError;
[DataMemberAttribute,
SysOperationControlVisibilityAttribute(false),
AifCollectionTypeAttribute("_RecIds", Types::Class, classStr(AAXBatchBundleDataContract)),
AifCollectionTypeAttribute("return", Types::Class, classStr(AAXBatchBundleDataContract))]
public List parmRecIds(List _RecIds = RecIds)
{
RecIds = _RecIds;
return RecIds;
}[DataMemberAttribute, SysOperationControlVisibilityAttribute(false)]
public NoYes parmThrowError(NoYes _throwError = throwError)
{
throwError = _throwError;
return throwError;
}[DataMemberAttribute, SysOperationControlVisibilityAttribute(false)]
public int64 parmWorkItemCount(int64 _workItemCount = workItemCount)
{
workItemCount = _workItemCount;
return workItemCount;
}[DataMemberAttribute, SysOperationControlVisibilityAttribute(false)]
public int64 parmBundleSize(int64 _bundleSize = bundleSize)
{
bundleSize = _bundleSize;
return bundleSize;
}}
Controller
next, we have the controller. This just links the execution controller to the service.
class AAXBatchBundleController extends SysOperationServiceController
{
public static SysOperationController construct(Args _args = null)
{
AAXBatchBundleController controller = new AAXBatchBundleController(
classStr(AAXBatchBundleService),
methodStr(AAXBatchBundleService, process));controller.parmArgs(_args);
return controller;
}/// <summary>
///
/// </summary>
/// <returns></returns>
public ClassDescription caption()
{
return "Batch Bundle Task Example";
}}
Service
Lastly, we have the service class. This is what does the actual work. Baed on the paramters provided, it will loop through the list of RecIds that pass each value to a work item processor class, below, to do the actual work. This is what is taking out group of RecIds and actually processing them. You can use this style for any kind of entity with a primary key for deferred processing in a batch bundle, like this. Other examples could be a Sales Order Number or a Purchase Order Number.
public class AAXBatchBundleService extends SysOperationServiceBase implements BatchRetryable
{public void process(AAXBatchBundleDataContract _dataContract)
{
ListEnumerator ListEnumerator;if(_dataContract.parmRecIds() == null)
{
_dataContract.parmRecIds(new List(Types::Int64));
}ListEnumerator = _dataContract.parmRecIds().getEnumerator();
info(strFmt("Running batch job with parameter: %1", _dataContract.parmWorkItemCount()));
Info(strfmt("ThrowError set to %1", _dataContract.parmThrowError()));
Info(strFmt("Bundle size set to %1", _dataContract.parmBundleSize()));while(ListEnumerator.moveNext())
{
AAXBatchBundleWorkItem::doProcessWorkItem(ListEnumerator.current());if(_dataContract.parmThrowError())
{
if(new RandomGenerate().randomInt(0,100) >= 99)
throw Error("Encountered Error");
}}
}
/// <summary>
///
/// </summary>
public boolean isRetryable()
{
return true;
}}
Work Item Logic Class
As mentioned above, we've enapsulated all logic for this example into a singular class that does the work.
internal final class AAXBatchBundleWorkItem
{
public static void doProcessWorkItem(RecId _recId)
{
AAXBatchBundleWorkItemTable AAXBatchBundleWorkItemTable;select forupdate Id, Updated, RecId from AAXBatchBundleWorkItemTable
where AAXBatchBundleWorkItemTable.RecId == _recId
&& AAXBatchBundleWorkItemTable.Updated == NoYes::No;if(AAXBatchBundleWorkItemTable)
{
ttsbegin;
AAXBatchBundleWorkItemTable.Updated = NoYes::Yes;
AAXBatchBundleWorkItemTable.update();
ttscommit;
}
}}
All code is available here for review.