skip to content

A new event-driven 'Shipping' service needs data from a legacy system that only exposes a synchronous SOAP API with no webhooks or change feed. The team builds an ACL that polls the legacy SOAP API on a schedule and publishes translated domain events onto the Shipping service's event bus whenever it detects a change. What is this ACL doing beyond data-shape translation, and what new failure modes does that introduce compared to a simple request/response ACL?

level: seniorimportance: nice to knowfreq 30%

answer

  1. bridges integration style, not just data shape
  2. poll interval trades latency vs legacy load
  3. stateful ACL needs its own store
  4. at-least-once vs exactly-once event delivery
  5. CDC/Debezium as a more robust alternative to polling

basics

~20 s

It's translating not just data shapes but integration style - turning a synchronous poll-based system into an event stream. This adds new failure risks: missed or duplicate events, detection lag between polls, and the ACL now holding state (what it last saw) instead of being stateless.

solid answer

~50 s

Beyond mapping fields, this ACL is bridging integration styles - converting a pull-based, synchronous, stateless SOAP call into a push-based, asynchronous event stream. That means the ACL now has to track state across calls to diff and detect changes, decide a polling interval that trades detection latency against legacy system load, and guarantee event delivery semantics (at-least-once vs exactly-once) that the legacy system itself makes no promises about. Compared to a stateless request/response ACL, failure modes multiply: a missed poll cycle can silently drop a change if the legacy system doesn't retain history; a crash between detecting a change and publishing the event can duplicate or lose it; and the ACL itself becomes a stateful component needing its own persistence, monitoring, and recovery story - effectively a small system in its own right, not just a translation function.

go deeper

for a junior

Not expected to reason about delivery guarantees; recognizing that 'checking on a schedule' is different from 'reacting instantly' is enough if prompted.

for a middle

Should recognize the ACL is now stateful and describe the polling-interval trade-off, without necessarily naming delivery-guarantee terminology precisely.

for a senior

Should name at-least-once/duplicate/missed-event risks explicitly and propose concrete mitigations like watermarks, idempotency keys, or an outbox pattern.

for a principal

Should weigh polling vs CDC as an architectural decision with organizational and infrastructure cost implications, not just a technical detail, and recognize when investing in CDC tooling is or isn't justified for a given legacy constraint.

## Bridging interaction patterns, not just fields When the two sides of an integration differ not just in data model but in integration style — synchronous request/response versus asynchronous events, pull versus push, batch versus streaming — the ACL has to do more than field mapping; it has to bridge interaction patterns. In this scenario, the ACL: 1. runs on a schedule and calls the legacy SOAP API; 2. compares the result against what it saw last time (a diff against stored state — a local snapshot, a hash of the last-seen record, or a 'last modified' timestamp field if the legacy system exposes one); 3. for anything that changed, constructs and publishes a domain event (e.g. `ShipmentStatusChanged`) onto Shipping's event bus in Shipping's own vocabulary. This is meaningfully more machinery than a stateless translator that takes a request in, calls out, converts the response, and returns — here the ACL owns a **scheduler**, a **state store** for 'what did I see last,' and a **publisher** with its own delivery guarantees. ## Why the style mismatch matters This exists because integration style mismatches are just as corrupting as data-model mismatches if left unaddressed. If Shipping's own architecture is event-driven — other services react to `ShipmentStatusChanged` events to trigger downstream workflows — then forcing every consumer of shipping status to instead poll a synchronous endpoint directly would corrupt Shipping's architectural style, not just its data types, undermining the loose coupling and reactive processing the rest of the system is built around. The ACL absorbing the sync-to-async translation keeps that architectural boundary consistent, the same way the field-level translator keeps the data model consistent. ## What it costs The cost here is qualitatively different from a stateless ACL's cost. - **Polling interval** is a direct trade-off: shorter intervals mean lower detection latency but more load on a legacy system that may not have been designed for high query volume, and possibly rate-limiting or throttling pushback. - The ACL also now needs somewhere **durable** to keep its 'last seen' state, which is itself an operational component with its own backup, scaling, and consistency concerns — the ACL has gone from a pure function to a small stateful service. - There's also a **business-logic cost**: deciding what counts as a meaningful change worth publishing, versus noise like a timestamp-only field flipping with no domain-relevant change, is itself nontrivial domain logic embedded in what looks like 'just a translator.' ## New failure modes New failure modes appear that a stateless request/response ACL never has to worry about. - **Missed-change risk**: if the legacy system doesn't retain history and a change happens and reverts between two poll cycles, the ACL never sees it and never publishes an event, silently losing information the legacy system itself never guaranteed to preserve. - **Duplicate/lost-event risk**: if the ACL crashes after detecting a change but before or during publishing, it can either publish the same event twice on restart (if it doesn't track publish success) or never publish it at all (if it marks the change as 'seen' before confirming publish) — a classic at-least-once vs at-most-once delivery problem, and getting to exactly-once generally requires transactional or idempotency-key tricks. - **State-store risk**: if the durable 'last seen' state is lost or corrupted, the ACL can either replay a flood of stale 'changes' on next poll or silently start from a blank slate and miss a real gap. - **Backpressure risk**: if the legacy system slows down or the poll takes longer than the interval, polls can queue up or overlap, and without careful design the ACL can end up hammering an already-struggling legacy system harder, exactly when it's least able to handle it. ## Where it shows up This pattern is common wherever a modern event-driven service has to sit in front of an older system that predates webhooks or change-data-capture (CDC) tooling — classic examples include polling a legacy ERP or mainframe order system that only exposes a nightly-batch-era SOAP/REST API, or a database-level version of the same problem solved with CDC tools like Debezium, which tail a legacy database's write-ahead log and publish change events instead of polling an API — a more robust but more infrastructure-heavy version of exactly this ACL pattern, avoiding the poll-interval and missed-change problems by capturing every committed change directly at the source.

  • How would you reduce the risk of missed changes between poll cycles without a legacy change feed?
    If the legacy system exposes any kind of 'last modified' timestamp or version/sequence number, using it as a watermark to fetch only what changed since the last successful poll narrows the window and reduces load, though it doesn't eliminate the risk if the legacy system doesn't retain enough history. Shortening the poll interval helps at the cost of legacy-system load, and if the legacy system has an underlying database, database-level change-data-capture tooling can eliminate the missed-change risk entirely by reading the transaction log directly instead of polling the API.
  • How do you make event publication effectively exactly-once given the ACL can crash mid-cycle?
    A common approach is the transactional outbox pattern: write the detected change and the intent to publish it in the same local transaction as updating the 'last seen' state, then have a separate process reliably deliver from that outbox to the event bus, retrying until acknowledged; consumers then de-duplicate using an idempotency key on the event itself, since true exactly-once delivery across a network is not achievable without idempotent consumers.
  • Would you recommend polling or CDC for this scenario, and why?
    If the legacy system exposes its underlying database and the team can safely attach a CDC tool without legacy vendor restrictions, CDC is generally more robust - it captures every committed change directly instead of relying on a fixed interval - but it's more infrastructure to operate and may not be feasible for a black-box SOAP-only legacy system. Polling remains the pragmatic default when there's no database access, accepting its latency and missed-change trade-offs as a known cost.

Like a courier service that has to convert a postal system that only accepts mail dropped off once a day at a fixed window into a same-day push-notification delivery service - the courier now has to remember what it already delivered, decide how often to check the mailbox, and handle what happens if a letter gets missed between checks.

saying these in an interview costs you the question

  • Treats this as just another field-mapping problem with no mention of state/statefulness
  • No mention of at-least-once/duplicate or missed-event risk
  • Assumes polling interval has no cost or trade-off
  • Doesn't recognize the ACL now needs its own durable storage
  • Never mentions CDC or an alternative to polling when discussing this scenario

context