Overview

Integration platforms in financial services exist to bridge systems that have nothing in common — a 30-year-old core banking system, a SaaS KYC provider, a real-time payment scheme, a partner B2B feed. The patterns that make this tractable were catalogued in Hohpe & Woolf’s Enterprise Integration Patterns; the runtime that implements them in most banks is IBM App Connect Enterprise (ACE), the successor to IBM Integration Bus (IIB) and WebSphere Message Broker.

The ACE primitive is a message flow: a directed graph of nodes that consume from a source, transform/route/enrich, and deliver to one or more sinks. Each node is one EIP — the platform’s value proposition is the catalogue, not the runtime.

ACE is not an ESB

Modern ACE deployments are microservice-shaped: one integration server per domain, packaged as a container, deployed via CI to OpenShift. The 2010s-style monolithic ESB — one shared broker for the whole bank — is exactly the anti-pattern this article is trying to help you avoid.

ACE topology

An ACE deployment has three logical units. The integration server runs flows; the integration node groups servers under shared admin; the toolkit (Eclipse-based) authors the flows. Modern containerised deployments collapse this — one server per container, no node, shared admin via Cloud Pak for Integration or a Helm chart.

Core patterns

Six EIPs cover the majority of real flows. Each maps to one or two ACE node types.

PatternWhat it doesACE node
Content-based routerRoute a message to one of several outputs based on its contentRouteToLabel + Filter
Message translatorConvert one format/schema into anotherMapping / Compute (ESQL)
SplitterBreak a composite message into individual elementsCompute with PROPAGATE
AggregatorCombine related messages into a single oneAggregateControl + AggregateReply
Message enricherAugment a message with data from a side lookupHTTPRequest / DatabaseRetrieve
Dead letter channelCapture unprocessable messages for triageFailure terminal → MQ DLQ

Orchestration vs choreography

The choice that defines the shape of the integration platform.

  • Orchestration: a central flow knows the steps and calls each system. Easy to reason about, easy to change in one place. Becomes a SPOF and a development bottleneck if it owns too much.
  • Choreography: services react to events and emit their own. No central coordinator. Scales independently but the end-to-end path lives across many systems and is hard to debug without strong tracing.

The decision rule that holds in practice: orchestrate within a domain, choreograph across domains. Within payments, an ACE flow orchestrates KYC + risk + ledger. Across payments, fraud, and notifications, services subscribe to events and act independently.

Canonical model

A canonical data model is a shared internal representation that intermediates between external schemas. The promise: change one external system without touching N others. The risk: a canonical model that tries to be everything to everyone becomes a 600-field bloat that nobody can change. Keep it minimal — only the fields that genuinely flow across multiple integrations.

canonical-payment-v1.xsdxsd
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           targetNamespace="http://acmebank.com/canonical/v1">

  <xs:complexType name="Payment">
    <xs:sequence>
      <xs:element name="id"          type="xs:string"/>
      <xs:element name="amount"      type="xs:decimal"/>
      <xs:element name="currency"    type="xs:string"/>
      <xs:element name="originator"  type="Party"/>
      <xs:element name="beneficiary" type="Party"/>
      <xs:element name="reference"   type="xs:string" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="Party">
    <xs:sequence>
      <xs:element name="name"    type="xs:string"/>
      <xs:element name="account" type="xs:string"/>
      <xs:element name="bic"     type="xs:string"/>
    </xs:sequence>
  </xs:complexType>

</xs:schema>

Map external schemas (ISO 20022 pacs.008, SWIFT MT103, partner JSON) into this canonical type at the integration boundary. Downstream nodes only see canonical — they have no knowledge of the upstream format.

Building a flow

A typical content-based-routing + enrichment flow looks like this in ACE:

  1. Choose the input node

    MQInput for queue-driven, HTTPInput for synchronous REST, KafkaConsumer for streams. The input node defines the transaction boundary.

  2. Parse and validate

    Attach a message domain (DFDL, JSON, XMLNSC) so the rest of the flow has structured access. Validate against the canonical schema; reject early.

  3. Transform to canonical

    A Mapping node is preferred over Compute (ESQL) for maintainability. Mapping is graphical and reviewable; ESQL is code that only one engineer remembers how to read.

  4. Enrich

    Side calls to lookup services (account master, sanctions, KYC). Use HTTPRequest with a 2-second timeout and a circuit breaker; never let a downstream slowdown block the flow indefinitely.

  5. Route

    A RouteToLabel sets a label based on content; one Label node per branch. Avoid deep Filter chains — they’re hard to read.

  6. Output

    MQOutput, HTTPRequest, KafkaProducer. Wire the failure terminal of the output node to an error subflow that always logs and DLQs.

Example: payment routing in ESQL

When ESQL is unavoidable, keep it short and side-effect-free.

RoutePayment.esqlesql
CREATE COMPUTE MODULE RoutePayment
  CREATE FUNCTION Main() RETURNS BOOLEAN
  BEGIN
    -- normalise currency to ISO 4217
    SET OutputRoot.JSON.Data.currency =
        UPPER(InputRoot.JSON.Data.currency);

    -- route by amount: > 1M = high-value branch
    DECLARE amt DECIMAL;
    SET amt = CAST(InputRoot.JSON.Data.amount AS DECIMAL);

    IF amt > 1000000 THEN
      PROPAGATE TO TERMINAL 'out1';       -- high-value
    ELSEIF amt > 10000 THEN
      PROPAGATE TO TERMINAL 'out2';       -- standard
    ELSE
      PROPAGATE TO TERMINAL 'out3';       -- micro-payment
    END IF;

    RETURN FALSE;
  END;
END MODULE;

Error handling

Three failure modes need explicit handling: malformed messages (parse failure), validation failures (business rule violation), and downstream errors (timeout, 5xx). Each goes to a different place.

  • Parse failureFAILURE terminal of the parser node → quarantine queue, never DLQ. The message is unrecoverable; manual triage.
  • Validation failure → respond to caller with structured error (sync) or emit a PaymentRejected event (async). Don’t DLQ a valid message that just doesn’t pass policy.
  • Downstream error → retry with backoff (3 attempts, exponential), then DLQ. The message will be replayable once the downstream is fixed.

HA & deployment

The container model gives you HA largely for free. Three things still need explicit attention:

integration-server-helm.yamlyaml
apiVersion: appconnect.ibm.com/v1beta1
kind: IntegrationServer
metadata:
  name: payments
  namespace: integration-prod
spec:
  replicas: 3
  version: 12.0.12.0-r1
  barURL: https://artifactory/payments-1.4.7.bar
  configurations:
    - payments-mq-policy
    - payments-truststore
    - payments-server-conf

  pod:
    containers:
      runtime:
        resources:
          requests: { cpu: 500m, memory: 1Gi }
          limits:   { cpu: 2,    memory: 2Gi }
        livenessProbe:
          httpGet: { path: /apiv2, port: 7600 }
          initialDelaySeconds: 120
        readinessProbe:
          httpGet: { path: /apiv2/iready, port: 7600 }

  autoScale:
    enabled: true
    minReplicas: 3
    maxReplicas: 10
    targetCPUUtilizationPercentage: 70
  1. MQ qmgr cluster, not single qmgr. If the integration server pulls from MQ, the qmgr must be clustered or the MQ failover is the bottleneck.
  2. BAR file in artifactory, not in the image. Same image, different BAR per environment. This is what makes promotion deterministic.
  3. HPA on CPU is fine for synchronous flows. For queue-driven flows, use a custom metric on queue depth — CPU lags behind a backlog.

Observability

ACE emits flow accounting and statistics in a structured form. Wire them into Prometheus via the metrics endpoint; into a SIEM via syslog. The signals that matter:

  • Per-flow throughput, latency p50/p99, error rate. Available from /apiv2/metrics on each integration server.
  • Queue depth on input MQ queues. A growing depth means the flow is falling behind; alert before the queue is full.
  • Per-node failure counts. Helps localise where in a flow failures originate — the parse node, the enrich call, the output.

Common pitfalls

The shared-flow library

A “common subflow” library that every team consumes turns into a coupling hazard within a year. One subflow change, fifty integrations to retest. Prefer copy-paste of small subflows over shared libraries unless you have rigorous regression coverage across consumers.

ESQL as a programming language

ESQL is excellent for short transformations and message routing. It is a poor general-purpose language. If a Compute node has more than 50 lines of logic, it belongs in a Java microservice that the flow calls, not inside the flow.

DLQ as a data store

A growing DLQ that nobody triages is a deferred outage. Build alerts on DLQ depth and dwell time; assign clear ownership; reprocess weekly at minimum.

When not to use

An integration platform like ACE is the right tool when:

  • You connect protocol-incompatible systems (MQ ↔ REST, COBOL ↔ JSON, file-drop ↔ Kafka).
  • Transformations are non-trivial — ISO 20022, SWIFT MT, EDIFACT — and the platform’s parsers save real effort.
  • Connectors to legacy or vendor systems are pre-built and certified.

It is the wrong tool when:

  • The integration is microservice-to-microservice over HTTPS with shared JSON contracts. A direct REST call is simpler than introducing a flow.
  • The logic is long-running, stateful, and stateful-correct — build a workflow service (Camunda, Temporal) instead.
  • You don’t already have ACE / IBM licensing. Standalone, the cost rarely pencils against open-source alternatives (Apache Camel, Kafka Connect).