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.
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.
| Pattern | What it does | ACE node |
|---|---|---|
| Content-based router | Route a message to one of several outputs based on its content | RouteToLabel + Filter |
| Message translator | Convert one format/schema into another | Mapping / Compute (ESQL) |
| Splitter | Break a composite message into individual elements | Compute with PROPAGATE |
| Aggregator | Combine related messages into a single one | AggregateControl + AggregateReply |
| Message enricher | Augment a message with data from a side lookup | HTTPRequest / DatabaseRetrieve |
| Dead letter channel | Capture unprocessable messages for triage | Failure 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.
<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:
-
Choose the input node
MQInputfor queue-driven,HTTPInputfor synchronous REST,KafkaConsumerfor streams. The input node defines the transaction boundary. -
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. -
Transform to canonical
A
Mappingnode is preferred overCompute(ESQL) for maintainability. Mapping is graphical and reviewable; ESQL is code that only one engineer remembers how to read. -
Enrich
Side calls to lookup services (account master, sanctions, KYC). Use
HTTPRequestwith a 2-second timeout and a circuit breaker; never let a downstream slowdown block the flow indefinitely. -
Route
A
RouteToLabelsets a label based on content; oneLabelnode per branch. Avoid deepFilterchains — they’re hard to read. -
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.
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 failure →
FAILUREterminal 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
PaymentRejectedevent (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:
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
- 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.
- BAR file in artifactory, not in the image. Same image, different BAR per environment. This is what makes promotion deterministic.
- 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/metricson 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
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 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.
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).