Context Loaders
Overview
Context Loaders are a powerful BankLingo Process Engine feature that enables dynamic data loading into process contexts without hardcoding entity-specific logic. They provide a declarative, JSON-based approach to fetch data during process execution.
Key Benefits
- 🎯 Declarative: Define data loading requirements in JSON, not code
- 🔄 Reusable: Same loaders can be used across multiple processes
- 🚀 Dynamic: Load any entity or execute any command at runtime
- 📦 Type-Safe: Leverages existing entity contexts and command infrastructure
- 🔌 Extensible: Easy to add new loaders without changing core engine
Use Cases
- User Task Forms: Pre-populate form fields with current entity data
- Service Tasks: Load reference data for validation or processing
- Gateway Conditions: Fetch data needed for routing decisions
- Event Handlers: Load entity context when external events trigger processes
Loader Formats
Context loaders support two primary formats:
1. Entity Loader Format
Load a complete entity context by entity type and ID.
{
"entity": "Loan",
"id": 12345
}
When to Use:
- Need complete entity context with all properties
- Entity ID is known at design time or available in process variables
- Want to leverage typed context (LoanProcessContext, DepositProcessContext, etc.)
Supported Entity Types:
Loan- Loan accountsDeposit- Deposit accountsTransaction- Financial transactionsContact/Customer- Customer/contact recordsTransfer- Fund transfersBillsPayment- Bills payment transactionsAirtimeRecharge- Airtime recharge transactionsDataRecharge- Data recharge transactionsLoanRequest- Loan applicationsTeller- Teller transactionsReconciliation- Reconciliation jobsInsurancePolicy- Insurance policiesInsuranceClaims- Insurance claimsInwardTransaction- Inward payment transactions
2. Command Loader Format
Execute a MediatR command/query to fetch specific data.
{
"cmd": "GetLoanByIdQuery",
"context": {
"id": 12345,
"includeSchedule": true
}
}
When to Use:
- Need specific data subsets (not entire entity)
- Require complex queries with multiple parameters
- Want to execute business logic during data fetch
- Need to call external services or APIs
Examples:
// Fetch customer details
{
"cmd": "GetContactByIdQuery",
"context": {"id": 67890}
}
// Get account balance
{
"cmd": "GetDepositAccountBalanceQuery",
"context": {"accountNumber": "0123456789"}
}
// Fetch transaction history
{
"cmd": "GetTransactionHistoryQuery",
"context": {
"accountNumber": "0123456789",
"startDate": "2024-01-01",
"endDate": "2024-12-31"
}
}
Using Context Loaders
In User Task ServerScript
Context loaders are typically executed in User Task ServerScripts to pre-populate form data before displaying to users.
Example: Loan Approval Form
BPMN Definition:
<userTask id="Task_ApproveLoan" name="Approve Loan">
<extensionElements>
<banklingo:serverScript>
<![CDATA[
// Load loan context
var loader = {"entity": "Loan", "id": execution.getVariable("loanId")};
var loanContext = await contextLoaderService.ExecuteContextLoaderAsync(
JSON.stringify(loader),
execution.getVariable("currentUser"),
execution.getAllVariables()
);
// Make context available to form
execution.setVariable("loan", loanContext);
]]>
</banklingo:serverScript>
</extensionElements>
</userTask>
Form Access:
// In your form component
const loan = processVariables.loan;
console.log(loan.loanAmount); // 50000
console.log(loan.customerId); // 67890
console.log(loan.status); // 0 (PENDING)
console.log(loan.statusDescription); // "PENDING"
In Service Task
Execute loaders within service tasks for data processing.
Example: Customer Validation Task
<serviceTask id="Task_ValidateCustomer"
name="Validate Customer"
banklingo:class="com.banklingo.services.ValidationService"
banklingo:method="validateCustomer">
<extensionElements>
<banklingo:serverScript>
<![CDATA[
// Load customer details
var loader = {
"cmd": "GetContactByIdQuery",
"context": {"id": execution.getVariable("customerId")}
};
var customerContext = await contextLoaderService.ExecuteContextLoaderAsync(
JSON.stringify(loader),
execution.getVariable("currentUser"),
execution.getAllVariables()
);
// Perform validation
var isValid = customerContext.kycStatus === "Approved"
&& customerContext.accountStatus === "Active";
execution.setVariable("customerValid", isValid);
]]>
</banklingo:serverScript>
</extensionElements>
</serviceTask>
With Variable Substitution
Use variable placeholders {{variableName}} to reference process variables.
Example: Dynamic Entity Loading
// Process has variables: entityType="Loan", entityId=12345
var loader = {
"entity": "{{entityType}}", // Resolves to "Loan"
"id": "{{entityId}}" // Resolves to 12345
};
var context = await contextLoaderService.ExecuteContextLoaderAsync(
JSON.stringify(loader),
currentUser,
execution.getAllVariables()
);
Example: Parameterized Commands
// Process has variables: accountNumber="0123456789", startDate="2024-01-01"
var loader = {
"cmd": "GetTransactionHistoryQuery",
"context": {
"accountNumber": "{{accountNumber}}",
"startDate": "{{startDate}}",
"endDate": "2024-12-31"
}
};
var history = await contextLoaderService.ExecuteContextLoaderAsync(
JSON.stringify(loader),
currentUser,
execution.getAllVariables()
);
API Reference
IContextLoaderService
The main service interface for executing context loaders.
Methods
ExecuteContextLoaderAsync
Implementation details removed for security.
Contact support for implementation guidance.
Parameters:
contextExpression- JSON string defining the loader (entity or command format)createdBy- Username of the user executing the loadervariables- Optional process variables for parameter substitution
Returns: BaseProcessContext containing the loaded entity or command result data
Throws:
ArgumentException- Invalid or malformed context expressionInvalidOperationException- Entity not found or command execution failedNotSupportedException- Unsupported entity type or legacy format
Example:
Implementation details removed for security.
Contact support for implementation guidance.
IsValidContextExpression
Implementation details removed for security.
Contact support for implementation guidance.
Validates whether a context expression is in valid JSON format.
Example:
Implementation details removed for security.
Contact support for implementation guidance.
GetLoaderType
Implementation details removed for security.
Contact support for implementation guidance.
Determines the loader type from a context expression.
Returns: "Entity", "Command", "Legacy", or "Unknown"
Example:
Implementation details removed for security.
Contact support for implementation guidance.
Available Context Loaders
Entity Loaders
Pre-configured loaders for all supported entity types.
| Code | Name | Description | Example |
|---|---|---|---|
ENTITY_LOAN | Entity - Loan | Loads loan entity context by ID | {"entity":"Loan","id":12345} |
ENTITY_CONTACT | Entity - Contact/Customer | Loads contact/customer context | {"entity":"Contact","id":67890} |
ENTITY_DEPOSIT | Entity - Deposit Account | Loads deposit account context | {"entity":"Deposit","id":54321} |
ENTITY_TRANSACTION | Entity - Transaction | Loads transaction context | {"entity":"Transaction","id":98765} |
ENTITY_TRANSFER | Entity - Transfer | Loads transfer context | {"entity":"Transfer","id":11111} |
ENTITY_BILLS_PAYMENT | Entity - Bills Payment | Loads bills payment context | {"entity":"BillsPayment","id":22222} |
ENTITY_AIRTIME_RECHARGE | Entity - Airtime Recharge | Loads airtime recharge context | {"entity":"AirtimeRecharge","id":33333} |
ENTITY_DATA_RECHARGE | Entity - Data Recharge | Loads data recharge context | {"entity":"DataRecharge","id":44444} |
ENTITY_LOAN_REQUEST | Entity - Loan Request | Loads loan request context | {"entity":"LoanRequest","id":55555} |
ENTITY_TELLER | Entity - Teller Transaction | Loads teller transaction context | {"entity":"Teller","id":66666} |
ENTITY_RECONCILIATION | Entity - Reconciliation | Loads reconciliation context | {"entity":"Reconciliation","id":77777} |
ENTITY_INSURANCE_POLICY | Entity - Insurance Policy | Loads insurance policy context | {"entity":"InsurancePolicy","id":88888} |
ENTITY_INSURANCE_CLAIMS | Entity - Insurance Claims | Loads insurance claims context | {"entity":"InsuranceClaims","id":99999} |
ENTITY_INWARD_TRANSACTION | Entity - Inward Transaction | Loads inward transaction context | {"entity":"InwardTransaction","id":10101} |
Tracing an inward-transaction event context
For OnInwardTransactionReceived, two searchable information-level log entries expose the context
at the important boundaries:
OnInwardTransactionReceived Hangfire input contextcontains the exactentityDatapayload received by the Administration API Hangfire worker.OnInwardTransactionReceived resolved process contextcontains the final context after any database-loaded fields have been merged with the supplied payload. Supplied fields take precedence, and this resolved context is what process trigger conditions and scripts receive.
Both entries contain EntityId and ContextJson. To trace a specific inward transaction, search by
the event name together with the persisted inward transaction ID, for example
OnInwardTransactionReceived 83528. The input payload can also be found using its sessionId.
Command Loaders
Pre-configured loaders for common query commands.
| Code | Name | Description | Example |
|---|---|---|---|
CMD_FETCH_LOAN_DETAILS | Command - Fetch Loan Details | Fetches loan details via query | {"cmd":"GetLoanByIdQuery","context":{"id":12345}} |
CMD_FETCH_CUSTOMER_DETAILS | Command - Fetch Customer Details | Fetches customer details | {"cmd":"GetContactByIdQuery","context":{"id":67890}} |
CMD_FETCH_ACCOUNT_BALANCE | Command - Fetch Account Balance | Fetches account balance | {"cmd":"GetDepositAccountBalanceQuery","context":{"accountNumber":"0123456789"}} |
CMD_FETCH_TRANSACTION_HISTORY | Command - Fetch Transaction History | Fetches transaction history | {"cmd":"GetTransactionHistoryQuery","context":{"accountNumber":"0123456789","startDate":"2024-01-01","endDate":"2024-12-31"}} |
CMD_VALIDATE_CUSTOMER | Command - Validate Customer | Validates customer information | {"cmd":"ValidateCustomerCommand","context":{"customerId":67890}} |
Advanced Scenarios
Chaining Multiple Loaders
Load multiple related entities in sequence.
// Load loan first
var loanLoader = {"entity": "Loan", "id": execution.getVariable("loanId")};
var loan = await contextLoaderService.ExecuteContextLoaderAsync(
JSON.stringify(loanLoader),
currentUser,
execution.getAllVariables()
);
// Extract customer ID from loan
execution.setVariable("customerId", loan.customerId);
// Load customer
var customerLoader = {"entity": "Contact", "id": "{{customerId}}"};
var customer = await contextLoaderService.ExecuteContextLoaderAsync(
JSON.stringify(customerLoader),
currentUser,
execution.getAllVariables()
);
// Both contexts now available
execution.setVariable("loan", loan);
execution.setVariable("customer", customer);
Conditional Loading
Load different entities based on process state.
var entityType = execution.getVariable("transactionType");
var entityId = execution.getVariable("transactionId");
var loader = {
"entity": entityType === "TRANSFER" ? "Transfer" :
entityType === "BILLS" ? "BillsPayment" :
entityType === "AIRTIME" ? "AirtimeRecharge" : "Transaction",
"id": entityId
};
var context = await contextLoaderService.ExecuteContextLoaderAsync(
JSON.stringify(loader),
currentUser,
execution.getAllVariables()
);
Error Handling
Gracefully handle loader failures.
try {
var loader = {"entity": "Loan", "id": execution.getVariable("loanId")};
var loan = await contextLoaderService.ExecuteContextLoaderAsync(
JSON.stringify(loader),
currentUser,
execution.getAllVariables()
);
execution.setVariable("loan", loan);
execution.setVariable("loadSuccess", true);
} catch (error) {
execution.setVariable("loadSuccess", false);
execution.setVariable("loadError", error.message);
// Log error
logger.error("Failed to load loan context", error);
// Can trigger error boundary event
throw new BpmnError("LOAD_FAILED", "Failed to load entity context");
}
Custom Command Execution
Execute custom commands with complex parameters.
var loader = {
"cmd": "CalculateLoanEligibilityCommand",
"context": {
"customerId": execution.getVariable("customerId"),
"requestedAmount": execution.getVariable("loanAmount"),
"tenor": execution.getVariable("loanTenor"),
"productCode": execution.getVariable("productCode"),
"includeAssessment": true,
"includeRiskScore": true
}
};
var eligibility = await contextLoaderService.ExecuteContextLoaderAsync(
JSON.stringify(loader),
currentUser,
execution.getAllVariables()
);
// Access eligibility result
if (eligibility.isEligible) {
execution.setVariable("maxLoanAmount", eligibility.maxEligibleAmount);
execution.setVariable("interestRate", eligibility.recommendedRate);
} else {
execution.setVariable("rejectionReason", eligibility.reason);
}
Implementation Details
How Entity Loaders Work
- Parse JSON: Extract entity type and ID from context expression
- Resolve Variables: Substitute any variable placeholders (
{{variableName}}) - Map Entity Type: Convert entity name string to
ProcessEntityTypeenum - Query Database: Fetch entity from appropriate repository using ID
- Convert to Dictionary: Extract all entity properties into key-value pairs
- Create Context: Use
ProcessContextFactory.CreateContext()to generate typed context - Return Context: Return fully populated
BaseProcessContext(e.g.,LoanProcessContext)
Key Classes:
ContextLoaderService.ExecuteEntityLoaderAsync()ProcessContextFactory.CreateContext()- Entity repositories via
IUnitOfWork
How Command Loaders Work
- Parse JSON: Extract command name and context parameters
- Resolve Variables: Substitute variable placeholders in parameters
- Resolve Command Type: Find command class by name using reflection
- Create Command Instance: Instantiate command and set properties from parameters
- Execute via MediatR: Send command through
IMediator.Send() - Convert Result: Transform command result into
BaseProcessContext - Return Context: Return context with command result data
Key Classes:
ContextLoaderService.ExecuteCommandLoaderAsync()- MediatR
IMediatorinterface - Command/Query classes implementing
IRequest<T>
Variable Substitution
Variable placeholders support multiple formats:
// Format 1: Direct variable name
{"id": "loanId"} // Looks up "loanId" in variables dict
// Format 2: Curly brace placeholder
{"id": "{{loanId}}"} // Extracts variable name, looks up value
// Format 3: Direct value (no substitution)
{"id": 12345} // Uses value as-is
Substitution Rules:
- Variables must exist in process variables dictionary
- Type conversion is attempted automatically (string → long, etc.)
- Missing variables throw
ArgumentException - Invalid type conversions throw
InvalidCastException
Best Practices
✅ DO
- Use entity loaders when you need complete entity context
- Use command loaders for targeted queries or business logic execution
- Validate loader format before execution using
IsValidContextExpression() - Handle errors gracefully with try-catch blocks
- Cache loaded contexts in process variables to avoid redundant queries
- Document custom loaders in process definition comments
⌠DON'T
- Don't use legacy function-style loaders (e.g.,
FetchLoan(id)) - use JSON format - Don't hardcode IDs - use variable substitution for flexibility
- Don't load large datasets - use pagination or filtering in commands
- Don't ignore errors - always handle loader failures appropriately
- Don't reload same entity multiple times - cache in variables
Performance Tips
-
Load Once, Use Many Times: Cache loaded contexts in variables
// ⌠Bad: Load multiple timesvar loan1 = await load({"entity":"Loan","id":123});var loan2 = await load({"entity":"Loan","id":123}); // Redundant!// ✅ Good: Load once, reusevar loan = await load({"entity":"Loan","id":123});execution.setVariable("loan", loan); // Cache in variable// Use "loan" variable throughout process -
Use Specific Commands for Partial Data: Don't load entire entities for single fields
// ⌠Bad: Load entire loan just for amountvar loan = await load({"entity":"Loan","id":123});var amount = loan.loanAmount;// ✅ Good: Use specific commandvar result = await load({"cmd":"GetLoanAmountQuery","context":{"id":123}}); -
Batch Loading: Use commands that support batch fetching
// ✅ Good: Load multiple loans in one queryvar loans = await load({"cmd":"GetLoansByIdsQuery","context":{"ids":[123,456,789]}});
Troubleshooting
Error: "Entity type is required in entity loader format"
Cause: Missing or empty entity field in loader JSON.
Solution:
// ⌠Wrong
{"id": 12345}
// ✅ Correct
{"entity": "Loan", "id": 12345}
Error: "Invalid entity type: XYZ"
Cause: Entity type doesn't match any ProcessEntityType enum value.
Solution: Use one of the supported entity types (case-insensitive):
Loan,Deposit,Transaction,Contact,Transfer,BillsPayment,AirtimeRecharge,DataRecharge,LoanRequest,Teller,Reconciliation,InsurancePolicy,InsuranceClaims,InwardTransaction
Error: "Entity not found: Loan with ID 12345"
Cause: No entity exists in database with specified ID.
Solution:
- Verify entity ID is correct
- Check if entity was deleted
- Ensure ID variable substitution is working correctly
Error: "Command not found: GetLoanByIdQueryXYZ"
Cause: Command class doesn't exist or isn't registered with MediatR.
Solution:
- Verify command name spelling
- Ensure command implements
IRequest<T> - Check MediatR registration in dependency injection
Error: "Variable not found: loanId"
Cause: Referenced variable doesn't exist in process variables.
Solution:
- Verify variable name spelling
- Ensure variable was set before loader execution
- Check variable scope (process instance vs. local task)
Error: "Legacy function-style loaders are no longer supported"
Cause: Using old format like FetchLoan(12345).
Solution: Convert to JSON format:
// ⌠Legacy (deprecated)
"FetchLoan(12345)"
// ✅ New format
{"entity":"Loan","id":12345}
Migration from Legacy Loaders
If you have existing processes using legacy function-style loaders, follow this migration guide:
Step 1: Identify Legacy Loaders
Search for context expressions containing parentheses:
FetchLoan(loanId)ValidateCustomer(customerId)FetchAccountBalance(accountId)
Step 2: Convert to JSON Format
| Legacy Format | New JSON Format |
|---|---|
FetchLoan(loanId) | {"entity":"Loan","id":"{{loanId}}"} |
ValidateCustomer(customerId) | {"cmd":"ValidateCustomerCommand","context":{"customerId":"{{customerId}}"}} |
FetchAccountBalance(accountId) | {"cmd":"GetDepositAccountBalanceQuery","context":{"accountId":"{{accountId}}"}} |
Step 3: Update Process Definitions
Replace old loader calls with new JSON format in BPMN XML.
Step 4: Test Thoroughly
- Verify variable substitution works correctly
- Ensure entity/command resolution succeeds
- Check error handling paths
Related Documentation
- Process Context Models - Detailed context structure reference
- Event Context Standards - Event dispatching with contexts
- Event-Driven Architecture - Overall architecture
- Process Variables - Managing process variables
- ServerScript Execution - Writing ServerScripts
Summary
Context Loaders provide a powerful, declarative way to load data into BankLingo processes:
- Two formats: Entity-based and Command-based
- 14 entity types supported out-of-the-box
- Unlimited custom commands via MediatR integration
- Variable substitution for dynamic parameter passing
- Type-safe using existing ProcessContext infrastructure
- Extensible - easy to add new loaders and commands
Use entity loaders for complete entity contexts, command loaders for targeted queries, and variable substitution for maximum flexibility.