Skip to main content
Fugen Services logo

Engineering

Integrate legacy systems without inheriting their problems

Legacy integrations fail when they couple to old schemas or logic. Use an anti-corruption layer and scope as a spike to reduce risk.

Fugen Services6 min read
Close-up of a computer screen displaying programming code in a dark environment.
Photo by luis gomes on Pexels

Why does a legacy system integration fail before it even starts?

The first failure happens when the new system shares a database with the legacy one. A 15-year-old order system often stores discount logic in stored procedures; the new application must either call those procedures or reimplement the rules. Either way, the shared database becomes the bottleneck before the app server does. Every query competes for locks, and schema changes in the legacy system ripple into the new codebase, forcing unplanned refactoring.

Direct database coupling locks you into legacy schema changes. A column rename or a new nullable field in the old system can break the new application’s assumptions. Worse, business logic duplicated across both systems drifts out of sync. The discount calculation in the stored procedure may diverge from the new service’s version, leading to inconsistent pricing and manual reconciliation.

The hidden cost is the syncing of business logic. Teams often underestimate the effort to keep two implementations aligned. A single rule change—such as a new loyalty discount—requires updates in both places. Without a clear boundary, the new system inherits the legacy’s complexity, and the integration becomes a maintenance burden rather than a modernisation step.

How can an anti-corruption layer keep legacy problems out of your new system?

An anti-corruption layer (ACL) translates legacy data models into your domain model at the boundary. For example, a legacy ‘CUST’ table with denormalised fields can be mapped to a modern Customer aggregate with value objects. The ACL isolates the new system from the legacy’s quirks, such as NULL values in unexpected columns or business rules embedded in triggers.

The trade-off is additional latency. Each call to the legacy system now passes through the ACL, adding a translation step. However, this cost is usually negligible compared to the risk of coupling. The real problem arises when the ACL becomes a thin wrapper. If it merely forwards requests without enforcing your domain model, legacy logic leaks in, and the new system ends up replicating the old one’s flaws.

What goes wrong in practice is scope creep. Teams start with a simple mapping but soon add conditional logic to handle edge cases. Before long, the ACL contains business rules that belong in the new system, defeating its purpose. The solution is to keep the ACL focused on translation and validation, not business logic.

What if the legacy system has no API?

When there is no API, you have three options, each with risks. Screen scraping with a headless browser can extract data from legacy UIs, but it is brittle. A change to the UI—such as a new CSS class or a reordered form—breaks the scraper. Maintenance becomes a recurring cost, as the team must update selectors every time the legacy system is patched.

Database polling with change data capture (CDC) tools is more reliable. Tools like Debezium can stream changes from transaction logs, allowing near-real-time integration. However, CDC depends on the database’s ability to expose these logs. Older systems, especially those running on mainframes or proprietary databases, may not support CDC, or the logs may be incomplete.

File-based integration is the third option. Batch files (e.g., CSV or XML) can be exchanged on a schedule, but this introduces latency. Real-time updates are impossible, and the new system must handle partial or malformed files. All three options fail if the legacy system’s UI or DB schema changes unexpectedly. A schema change can break CDC, and a UI redesign can invalidate scrapers.

How do you scope a legacy integration as a spike, not a fixed price?

A spike is a time-boxed exploration to reduce unknowns. For a legacy integration, the goal is to prove the approach works for a single critical workflow, not to build the full integration. Deliverables might include a working proof of concept (PoC) that fetches an order from the legacy system, applies the discount logic, and returns it in the new format. The spike should also document hidden dependencies, such as a COBOL call that the legacy system makes to a mainframe.

Fixed-price quotes for legacy work are often wrong by 2-3x because the scope is unknown. A spike reveals these unknowns early. For example, the team might discover that the legacy system’s discount logic depends on a third-party service that is no longer available. Without a spike, this would surface only after the fixed-price contract is signed, leading to costly renegotiations.

The mechanism is simple: use the spike to identify risks. If the ACL cannot handle a specific edge case, the spike will expose it. If the legacy system’s API is undocumented, the spike will force the team to reverse-engineer it. The output is a clearer picture of the effort required, which can then be used to scope the full integration accurately.

When does a facade pattern beat an anti-corruption layer?

A facade pattern is simpler when the legacy system’s API is stable and well-documented. The facade wraps the legacy API with a modern interface, such as REST over SOAP. This approach is faster to implement and easier to maintain, as long as the legacy API does not change. The trade-off is the risk of legacy changes breaking the facade. If the legacy system’s behaviour is inconsistent, the facade can become a leaky abstraction.

A concrete example is wrapping a SOAP service with a REST facade. The facade translates REST requests into SOAP calls and vice versa. This works well if the SOAP service is reliable and its contract is fixed. However, if the SOAP service introduces new fields or changes its error responses, the facade must be updated to handle these changes, or it will fail silently.

What goes wrong is when the facade hides complexity rather than isolating it. If the legacy API has quirks—such as a field that is sometimes a string and sometimes a number—the facade must handle these cases. If it does not, the new system inherits the legacy’s inconsistencies. In such cases, an anti-corruption layer is the better choice, as it can enforce a consistent domain model.

How do you test a legacy integration without breaking production?

Contract testing with tools like Pact verifies the ACL’s expectations. The ACL defines the expected request and response formats, and Pact checks that the legacy system adheres to these contracts. This allows you to test the integration without spinning up the legacy system in every environment. The trade-off is between mocking and a full legacy test environment. Mocking is faster but may miss edge cases, while a test environment is more accurate but expensive to maintain.

A concrete example is testing a discount calculation. The ACL can replay recorded legacy responses to verify that the new system handles them correctly. This works well for known scenarios, but unmocked edge cases—such as a rare discount code—can cause failures in production. To mitigate this, the team should log all unhandled cases and expand the test suite accordingly.

What goes wrong is when tests pass but fail in production. This happens when the mocks do not cover all possible legacy responses. The solution is to use a combination of contract testing and a small set of integration tests against a staging environment. This balances speed and accuracy, reducing the risk of production issues.

What are the hidden costs of a ‘quick and dirty’ integration?

Technical debt is the first hidden cost. A ‘quick’ integration often involves duplicating business logic in both the old and new systems. For example, a discount calculation might be implemented in both the legacy stored procedure and the new service. When the business rule changes, both implementations must be updated, doubling the maintenance effort.

Operational cost is the second. Manual reconciliation becomes necessary when data drifts out of sync. A ‘simple’ CSV import might require nightly manual fixes to correct mismatches between the legacy and new systems. This not only consumes time but also introduces errors, as manual processes are prone to mistakes.

A concrete example is a CSV import that fails to handle a new field in the legacy system. The team must either update the import script or manually map the new field, adding unplanned work. Over time, these ‘quick’ fixes accumulate, and the integration becomes a permanent, high-maintenance solution. The ‘quick and dirty’ approach often ends up being neither quick nor cheap in the long run.

Next step

Book a 30-minute call to discuss your legacy system and the best integration approach for your constraints.

Frequently asked

A spike for a legacy integration usually takes 1–3 weeks, depending on the system’s complexity. The goal is to prove the approach for one critical workflow, not to build the full integration. This time-boxed exploration reduces unknowns before committing to a larger project.

Yes, but the options are limited. Screen scraping, database polling with CDC, or file-based integration can work without modifying the legacy code. However, each has risks: scrapers break with UI changes, CDC may not be supported, and file-based integration introduces latency.

An anti-corruption layer translates between legacy and modern data models at the boundary, isolating the new system from legacy quirks. A hexagon adapter is a port/adapter pattern that separates core logic from external systems but does not necessarily enforce a domain model. The ACL is more prescriptive about data translation.

Use a gateway or proxy to translate modern authentication (e.g., OAuth) to the legacy protocol (e.g., basic auth or proprietary tokens). The ACL or facade can handle this translation, but it adds complexity. Ensure the gateway is secure and does not expose legacy credentials.

Debezium supports many databases, including older ones like PostgreSQL and MySQL. For mainframes, tools like IBM InfoSphere CDC or Attunity Replicate may be required. Check if the database’s transaction logs are accessible and complete before committing to CDC.

  • anti-corruption layer
  • integration
  • legacy systems
  • software modernisation
  • spike scoping
  • uk engineering

Want this applied to your situation?

General advice only goes so far. Tell us what you are dealing with and we will give you a straight answer about your case.

Get in touch