How to Connect a Legacy System to a New Product
If a new product depends on an old platform, the safest default is usually a legacy system integration layer. It gives the product a stable contract and stops legacy latency, brittle workflows, and strange failure modes from bleeding straight into the user experience. Direct integration only wins when the dependency is narrow, low-risk, and cheap to break.
The real issue is not whether the legacy system exposes an API, a database, or a file export. It is whether the new product can afford to inherit the old system's response times, maintenance windows, data quirks, and release constraints. In most customer-facing flows, it cannot.
Choose the integration pattern by use case
Teams get into trouble when they start with tools. An API gateway, iPaaS platform, or event bus does not tell you how the business flow should behave. A customer profile lookup, an invoice sync, and an order submission carry different failure costs, latency budgets, and recovery paths.
Most practical decisions fall into three use cases when connecting a legacy system to a new product.
Use case 1: The new product needs legacy data
This is the cleanest scenario. The new product needs account status, invoice history, pricing, inventory, or contract details, but it does not need to run the legacy workflow itself.
The key choice is direct read-through versus a replicated read store. Direct reads can work when the source is consistently responsive, traffic is modest, and the business can tolerate occasional failures. If the product needs a response in under one second and the legacy source regularly takes two to five seconds, the decision is already made. Do not put that dependency in the live path.
A replicated read store is usually the better fit when the source slows down under load, has maintenance windows, or makes every query operationally expensive. It also works well when the business can tolerate slight staleness. In many self-service flows, data that is 1 to 15 minutes old is acceptable if the interface shows freshness clearly.
That freshness marker matters. A field such as source_updated_at helps support teams separate stale data from a live processing issue. Without it, every mismatch turns into a vague incident.
A useful threshold question is simple: what happens if the legacy source is unavailable for 10 minutes during peak usage? If the answer is blocked self-service, lost revenue, or a support spike, build the read model.
One pattern shows up often in modernization work: teams ask for real-time data because it sounds safer, then discover the business would have accepted near-real-time data with a visible timestamp. They overestimate freshness needs and underestimate how much instability direct reads introduce.
In one retail billing rollout serving roughly 200,000 customer accounts, invoice status was exposed directly from an older finance platform because an API already existed. During end-of-period processing, response times degraded enough to break the portal when usage peaked. The real friction was not code volume. It was rollout delay caused by support escalation and release freezes around finance close. The fix was not more retries. It was a narrow read adapter with scheduled extraction, selective replication, and explicit freshness indicators.
Use case 2: The new product must trigger legacy transactions
This is where integration work becomes operationally serious. Order creation, account activation, shipment release, refund approval, and policy issuance are not just API calls. They involve retries, duplicate prevention, partial failure, and often manual exception handling on the legacy side.
The main decision is synchronous orchestration versus queue-based processing. Keep the flow synchronous only when the business truly needs an immediate answer and the legacy side can return a reliable result inside the product's latency budget. For most customer-facing journeys, that budget is a few seconds at most. If the old platform sometimes waits on a batch job, enters a lock window, or relies on manual review, promising an instant result is a product mistake, not a technical one.
Queue-based processing is usually the better fit when the transaction can take longer than a user should wait, duplicate submissions are expensive, or the old system has throughput bottlenecks. In those cases, the product should acknowledge receipt, issue a tracking identifier, and expose status honestly: submitted, pending, confirmed, failed.
Short version: if the legacy side cannot behave like an online system, stop designing the product as if it can.
The integration layer then owns retries, timeout policy, dead-letter handling, and idempotency. That last part is not optional when a duplicate action can create a second payment or second shipment. Stripe's public documentation on idempotent requests is still one of the clearest references for designing safe retries in systems where downstream failures are normal.
Some transactional flows also need compensating actions rather than rollback. If the new product creates a customer record and reserves stock but fails to complete the final order in the legacy system, the integration layer may need explicit reversal steps. That is middleware behavior. A thin gateway will not save you.
Here is the buying warning: many teams buy iPaaS too early for transactional legacy integration. It can work well for predictable SaaS synchronization. It is often a poor fit when the hard part is state management, ugly exception paths, and incident-grade observability. Connector catalogs look reassuring in procurement. They do not remove the need to design for failure.
Use case 3: Old and new systems will coexist for years
Long coexistence changes the job of the integration layer. It is no longer just an adapter for one launch. It becomes the operating boundary between two systems with different release cycles, ownership models, and data assumptions.
An API facade can help here, but only if it does more than forward requests. It should version contracts, normalize statuses, isolate authentication differences, and publish state changes in a form the new product can trust. If it only passes traffic through, the new product is still coupled to the old one.
The hardest issue in long coexistence is usually data ownership. If both systems can update the same customer field, pricing flag, or account status, synchronization failures are almost guaranteed unless one side is designated as the system of record for each field or aggregate. Many integration designs fail in production because ownership was never settled, not because the API was unavailable.
Martin Fowler's Strangler Fig pattern still holds up because it frames modernization as capability-by-capability replacement rather than a single migration event. That is the right model when the legacy platform still runs revenue-critical operations. If the broader roadmap is still unsettled, modernizing without a big-bang is usually the safer path.
What the integration layer should contain
Many teams make the integration layer too broad. It should not become a dumping ground for reporting logic, presentation formatting, or every future migration idea. Its job is narrower: protect the new product from legacy behavior and make the boundary operable.
, that usually means four things. First, stable contracts shaped around current business needs rather than legacy tables. Second, translation and validation for fields, statuses, and malformed requests before they hit the old system. Third, resilience controls such as timeouts, retries, queue back-pressure, and dead-letter handling. Fourth, observability with correlation IDs, structured logs, and alerts tied to business outcomes rather than just infrastructure metrics.
OpenTelemetry is a sensible early choice when requests need to be traced across the new product, middleware, and downstream systems. Mixed estates become hard to operate very quickly when every team uses a different tracing model.
Security belongs here too, but keep it concrete. The layer should broker authentication where the old system cannot support modern methods, reduce direct database exposure, redact sensitive fields in logs, and enforce least-privilege access to legacy operations.
Debatable claim: if your integration layer starts carrying presentation logic and cross-team exception handling because nobody wants to clean up the legacy process, you are not building a bridge anymore. You are building the next legacy system early.
What should stay out matters just as much: customer-facing formatting logic, broad analytics transformations, and random business rules that nobody wants to own elsewhere. Once the middleware starts absorbing unrelated logic, it becomes expensive to change and nearly impossible to retire.
Roll out one bounded flow first
Start with one commercially meaningful flow that is narrow enough to observe. Customer status lookup, invoice status retrieval, or account activation are usually better first moves than full checkout or pricing. If the first release touches five systems and two manual approvals, the team is trying to prove too much too early.
Define the target contract first. Set the fields, statuses, latency expectation, and ownership boundary from the product perspective. Then map source behavior honestly: lock windows, batch dependencies, duplicate patterns, and manual interventions. Instrument before launch, not after the first outage. Add correlation IDs, freshness markers, queue alerts, and retry visibility before production traffic arrives.
Run parallel validation where possible, then cut over by capability rather than by system. Move one bounded flow, stabilize it, and expand from there. That sequence sounds conservative. It is usually faster than cleaning up a rushed integration that shipped with no operational model.
Define exit criteria early as well. Some parts of the integration layer will become durable platform capabilities. Others should disappear as legacy functions are retired. If nobody can say which is which, temporary adapters tend to become permanent architecture.
For teams deciding whether to build this boundary internally or bring in outside help, a focused legacy modernization service usually makes sense when failure would hit revenue, compliance, or customer operations harder than the cost of building the layer properly.
Connecting a legacy system to a new product is rarely a connector selection problem. It is a boundary design problem. The right legacy system integration layer gives the new product a stable contract, absorbs legacy failure modes, and creates room to modernize without pretending the old estate behaves like a modern platform because it now sits behind an API.