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} |
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 times
var loan1 = await load({"entity":"Loan","id":123});
var loan2 = await load({"entity":"Loan","id":123}); // Redundant!
// ✅ Good: Load once, reuse
var 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 amount
var loan = await load({"entity":"Loan","id":123});
var amount = loan.loanAmount;
// ✅ Good: Use specific command
var result = await load({
"cmd":"GetLoanAmountQuery",
"context":{"id":123}
}); -
Batch Loading: Use commands that support batch fetching
// ✅ Good: Load multiple loans in one query
var 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.