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 used
  • logicName (string, required): Unique human-readable name for logging and debugging
  • stages (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:

  1. Stage 1 executes and completes
  2. Stage 2 executes (can reference results from Stage 1)
  3. Stage 3 executes (can reference results from Stages 1 and 2)
  4. 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 get and composite satisfy 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