Configuring query logic for the IBM Master Data Management migration proxy service
The query logic configuration file (query_logic_config.json) for the IBM Master Data Management migration proxy service is a per-tenant JSON configuration that drives the custom query logic system. By using query logic, you can define
how the proxy service builds IBM Master Data Management requests for a given InfoSphere MDM transaction type without code changes.
By configuring query logic, you can:
- Map any transaction type (for example,
getPerson,getContract) to one or more named logic definitions. - Select the active logic at runtime by evaluating a condition against the incoming transaction.
- Build a simple GET or a composite (multi-sub-request) IBM Master Data Management call from a single declarative block.
- Use conditional branching and iteration inside composite requests.
- Reference request IDs of previously defined sub-requests to build JSONPath expressions for the IBM Master Data Management composite service.
If no logic matches or no configuration exists for a transaction type, the system falls back transparently to the handler's default hardcoded logic.
The query logic configuration file is stored in the following location: FORFILES/{tenantId}/query_logic_config.json
Query configuration file structure
The query logic configuration file uses a hierarchical JSON structure with nested objects that define transaction-specific logic definitions.
{
"queryLogic": {
"<transactionType>": [
{
"condition": "<SpEL boolean expression>",
"logicName": "<unique name>",
"stages": [
{
"action": "<actionType>",
"content": { }
}
]
}
]
}
}
Top-level structure
The top-leve structure for a query logic object is as follows:
queryLogic(object, required): Root container for all query logic definitions<transactionType>(array of QueryLogicDefinition): Transaction-specific logic definitions
QueryLogicDefinition
Each logic definition includes:
condition(string, required): SpEL boolean expression that determines when this logic is usedlogicName(string, required): Unique human-readable name for logging and debuggingstages(array of StageDefinition, required): Sequence of execution phases
Selection order
Definitions inside a transaction-type array are evaluated in order. The first definition whose condition evaluates to true is used. Order your definitions from most-specific to least-specific.
Understanding stages
The stages array in a logic definition represents a sequence of execution phases. Each stage defines a complete action (either get or composite) that is executed in order.
Multi-stage support
Multiple stages are supported. When you define more than one stage in the stages array, they execute sequentially:
- Stage 1 executes and completes
- Stage 2 executes (can reference results from Stage 1)
- Stage 3 executes (can reference results from Stages 1 and 2)
- And so on
When to use multiple stages
Multi-stage execution is useful when you need to:
- Sequential operations: Perform operations that must happen in a specific order (for example, fetch data, then update based on what was fetched)
- Cross-stage data dependencies: Use results from one stage to determine what to fetch or update in subsequent stages
- Complex workflows: Break down complex operations into logical phases for better organization and maintainability
Single-stage versus multi-stage
Most query logic definitions use a single stage because:
- A single composite action can handle multiple sub-requests with conditional branching and iteration
- Simpler to understand and maintain
- Sufficient for most data retrieval scenarios
Use multiple stages when you need:
- Sequential operations that must complete before the next begins
- Update operations that depend on data fetched in earlier stages
- Clear separation of distinct operational phases
Supported actions
An action defines the fundamental strategy for how a stage constructs and executes IBM Master Data Management requests. The system supports two action types that cover different request patterns. An action determines:
- Request structure: Whether to build a single simple request or a composite request with multiple sub-requests
- Content interpretation: How the content block is parsed and processed
- Execution model: Whether requests execute independently or as part of a coordinated composite call
Conceptual model
- Simple actions (
get): Direct, single-purpose operations - Composite actions (
composite): Orchestrated multi-request operations
Why only two actions
The current implementation supports exactly two action types: get and composite. This limitation is intentional and by design.
Design rationale:
- Coverage of core use cases: These two actions cover the vast majority of real-world query patterns
- Simplicity and predictability: A small, well-defined action set makes the system easier to understand, simpler to test, and more predictable in production
- Proven sufficiency: Analysis of existing IBM Master Data Management transaction patterns shows that
getandcompositesatisfy all current requirements
The action system is designed for extensibility. New actions can be introduced when justified by concrete use cases that cannot be efficiently expressed with existing actions.
get action
Builds a single IBM Master Data Management record-fetch request. The content block must be a single logic object.
{
"action": "get",
"content": {
"identifier": "person",
"logicType": "getById",
"idSource": "#transaction.getParams().get('PartyId')"
}
}
Use the get action when you need to fetch exactly one record and no conditional branching or iteration is required.
composite action
Builds a composite IBM Master Data Management request that bundles multiple sub-requests into a single API call.
{
"action": "composite",
"content": {
"requests": [ ],
"choose": { },
"for-each": { }
}
}
Use the composite action when you need to fetch a record together with its relationships, related parties, or any other data that requires more than one IBM Master Data Management call.
Composite content definition
| Field | Type | Required | Description |
|---|---|---|---|
requests |
Array of logic objects | yes | The base set of sub-requests, executed unconditionally |
choose |
Choose block | no | Conditional branching |
for-each |
Foreach block | no | Iteration over collections |
Supported logic types
A logic type defines what kind of IBM Master Data Management operation a single sub-request performs. The system supports three logic types for different data retrieval patterns. Every logic object must have:
| Field | Type | Required | Description |
|---|---|---|---|
identifier |
string | yes | Unique name within the logic definition |
logicType |
string | yes | One of: getById, getRelationships, or search |
Additional fields depend on the logicType.
getById logic type
Fetches a single record by its source ID.
IBM Master Data Management endpoint: GET /mdm/v1/records/bysourceid
| Field | Type | Required | Description |
|---|---|---|---|
idSource |
string | yes | SpEL expression that resolves to the record ID string |
record_type |
string | no | IBM Master Data Management record type. If omitted, inferred from transaction metadata |
Example:
{
"identifier": "contract",
"logicType": "getById",
"idSource": "#transaction.getParams().get('ContractId')"
}
getRelationships logic type
Fetches all relationships for a given record.
IBM Master Data Management endpoint: GET /mdm/v1/records/{recordId}/relationships
| Field | Type | Required | Description |
|---|---|---|---|
relationshipPath |
string | yes | SpEL expression that resolves to a string containing the record ID |
Example:
{
"identifier": "contractRelationships",
"logicType": "getRelationships",
"relationshipPath": "'{$.responses[?(@.request_id == ' + #requestContext.getRequestNode('contract').getRequestId() + ')].response.record.id}'"
}
search logic type
Performs a search operation based on property/value criteria.
IBM Master Data Management endpoint: POST /mdm/v1/search
| Field | Type | Required | Description |
|---|---|---|---|
searchBy |
Array of SearchCriteria | yes | List of property/value pairs to search on |
condition |
string | yes | Search condition type (for example, equal, contains) |
SearchCriteria object:
| Field | Type | Required | Description |
|---|---|---|---|
property |
string | yes | The IBM Master Data Management attribute path to search on |
value |
string | yes | SpEL expression that resolves to the value to match |
Example:
{
"identifier": "partyAddress",
"logicType": "search",
"searchBy": [
{
"property": "record_id",
"value": "#transaction.getParams().get('PartyId')"
}
],
"condition": "equal"
}
Control structures
Control structures are declarative constructs that enable dynamic, data-driven composition of composite requests. They allow you to add conditional logic and iteration to your query definitions.
Request construction time versus runtime logic
Evaluation happens once when the configuration is processed to build the composite request that will be sent to IBM Master Data Management. The control structures generate conditional and iterative constructs in the composite request JSON that the IBM Master Data Management composite service evaluates during execution.
choose / whens / otherwise
Adds sub-requests conditionally.
{
"choose": {
"whens": [
{
"condition": "<SpEL string expression>",
"requests": [ ]
}
],
"otherwise": {
"requests": [ ],
"message": {
"type": "Warning",
"code": "ERR_001",
"text": "No matching record found"
}
}
}
}
choose fields
| Field | Type | Required | Description |
|---|---|---|---|
whens |
Array of When objects | yes | Ordered list of conditional branches |
otherwise |
Otherwise object | no | Fallback branch |
for-each
Iterates over a collection and adds a set of sub-requests for each element.
{
"for-each": {
"select": "<SpEL string expression>",
"requests": [ ]
}
}
| Field | Type | Required | Description |
|---|---|---|---|
select |
string | yes | SpEL expression that resolves to a JSONPath expression |
requests |
Array of logic objects | no | Sub-requests to execute for each element |
SpEL expressions
Spring Expression Language (SpEL) is used throughout the query logic configuration to evaluate conditions and build dynamic values. SpEL expressions serve two distinct roles:
| Role | Where used | Evaluated to |
|---|---|---|
| Condition | QueryLogicDefinition.condition |
Boolean |
| String template | idSource, relationshipPath, when.condition, for-each.select |
String |
Condition expressions
Used in the top-level condition field. Must evaluate to a Java Boolean.
#transaction.getParams().get('ContractInquiryLevel') == 0
#transaction.getParams().get('ContractInquiryLevel') > 0
String-producing expressions
Used in idSource, relationshipPath, when.condition, and for-each.select. Must evaluate to a String.
Simple parameter lookup:
#transaction.getParams().get('PartyId')
Building a JSONPath string:
'{$.responses[?(@.request_id == ' + #requestContext.getRequestNode('contract').getRequestId() + ')].response.record.id}'
Supported operators
| Category | Operators |
|---|---|
| Comparison | ==, !=, <, >, <=, >= |
| Logical | &&, ` |
| Ternary | condition ? valueIfTrue : valueIfFalse |
| String concatenation | + |
Expression length limit
Expressions are limited to 300 characters.
SpEL evaluation model
The SpEL evaluation model defines the context variables available during expression evaluation and the security restrictions that protect against injection attacks.
Available context variables
| Variable | Type | Description |
|---|---|---|
#transaction |
TransactionContext | The incoming transaction |
#requestContext |
RequestExecutionContext | The execution context |
Security restrictions
The SpEL evaluator is hardened against injection attacks. The following are blocked:
| Blocked feature | Example | Effect |
|---|---|---|
| Type references | T(java.lang.System) |
Throws exception |
| Constructor calls | new java.io.File('/path') |
Throws exception |
| Reflection methods | getClass(), getClassLoader() |
Throws exception |
| Bean resolver | @myBean |
Throws exception |
Configuration examples
The following examples demonstrate common query logic configuration patterns for different transaction scenarios.
Example: Simple GET by ID
Fetch a person record using the PartyId request parameter.
{
"queryLogic": {
"getPerson": [
{
"condition": "#transaction.getParams().get('InquiryLevel') == 0",
"logicName": "queryPersonBasic",
"stages": [
{
"action": "get",
"content": {
"identifier": "person",
"logicType": "getById",
"idSource": "#transaction.getParams().get('PartyId')"
}
}
]
}
]
}
}
Example: Composite GET with relationships
Fetch a contract and its relationships in a single composite call.
{
"queryLogic": {
"getContract": [
{
"condition": "#transaction.getParams().get('ContractInquiryLevel') > 0",
"logicName": "queryContractWithParties",
"stages": [
{
"action": "composite",
"content": {
"requests": [
{
"identifier": "contract",
"logicType": "getById",
"idSource": "#transaction.getParams().get('ContractId')"
},
{
"identifier": "contractRelationships",
"logicType": "getRelationships",
"relationshipPath": "'{$.responses[?(@.request_id == ' + #requestContext.getRequestNode('contract').getRequestId() + ')].response.record.id}'"
}
]
}
}
]
}
]
}
}
Example: Composite GET with conditional branching
Fetch a claim and try to fetch the associated party as a person; if not found, fetch as an organization.
{
"queryLogic": {
"getClaim": [
{
"condition": "#transaction.getParams().get('ClaimInquiryLevel') > 1",
"logicName": "queryClaimWithParties",
"stages": [
{
"action": "composite",
"content": {
"requests": [
{
"identifier": "claim",
"logicType": "getById",
"idSource": "#transaction.getParams().get('ClaimId')"
},
{
"identifier": "person",
"logicType": "getById",
"idSource": "'{$.responses[?(@.request_id == ' + #requestContext.getRequestNode('claim').getRequestId() + ')].response.record.attributes.tcrm_claim_party_role_b_obj.party_id}'",
"record_type": "tcrm_person_b_obj_type"
}
],
"choose": {
"whens": [
{
"condition": "'{$.responses[?(@.request_id == ' + #requestContext.getRequestNode('person').getRequestId() + ')].response.data.code} == \\\"404\\\"'",
"requests": [
{
"identifier": "organization",
"logicType": "getById",
"idSource": "'{$.responses[?(@.request_id == ' + #requestContext.getRequestNode('claim').getRequestId() + ')].response.record.attributes.tcrm_claim_party_role_b_obj.party_id}'",
"record_type": "tcrm_organization_b_obj_type"
}
]
}
]
}
}
}
]
}
]
}
}
Validation and error handling
Understanding how the system validates configuration and handles errors is critical for troubleshooting and maintaining reliable query logic definitions.
Configuration validation
Duplicate logicName
The system does not enforce uniqueness of logicName values within a transaction type. All definitions remain active and are evaluated in order.
Use unique, descriptive logicName values to make logs easier to understand.
Duplicate identifier
The system does not validate for duplicate identifier values during configuration processing. Duplicate identifiers will cause runtime issues.
Always use unique identifier values within a composite request.
Runtime error handling
Null request context
When #requestContext.getRequestNode('identifier') is called with an identifier that doesn't exist, it returns null. If you then call .getRequestId() on the null result, a NullPointerException is thrown.
Common causes:
- Typo in the identifier name
- Referencing an identifier before it's defined
- Referencing an identifier from a conditional branch that didn't execute
SpEL expression evaluation exceptions
Condition evaluation:
- Exceptions are caught and logged as WARNING
- The system continues evaluating subsequent logic definitions
- If no logic matches, the handler falls back to default behavior
Value resolution:
- Exception propagates as
ActionProcessingException - Request construction fails immediately
- Transaction returns an error response
Learn more
- Migrating data into IBM Master Data Management
- Enabling proxy communication
- Configuring additional transactions for the migration proxy service
- Configuring mapping for the migration proxy service
- Configuring properties for the migration proxy service
- Configuring XML templates for the migration proxy service