Migrating Legacy On-Premise ERPs to Cloud APIs Without Downtime
Learn how system integrators use the Strangler Fig pattern, dual writes, and declarative unified APIs to migrate enterprise ERPs to the cloud without breaking B2B SaaS connections.
Your enterprise customer just announced a 14-month migration from on-premise SAP ECC to S/4HANA Cloud. Or NetSuite from a heavily customized 2018-era account to a fresh OneWorld instance. Or JD Edwards to Oracle Fusion. Whatever the flavor, the message to your B2B SaaS product is the same: the integration you spent two quarters hardening is about to become a moving target, and any synchronization failure during cutover will be visible at the CFO's desk.
When enterprise customers migrate from legacy on-premise ERPs to modern cloud APIs, system integrators prevent downtime by decoupling the integration layer. They use the Strangler Fig pattern to incrementally route traffic, dual-write infrastructure to maintain state consistency, and a declarative Unified API to abstract the underlying protocol changes. If your B2B SaaS product relies on a direct, hardcoded connection to a legacy system, an upstream migration will break your application.
This guide is for the senior PMs and integration architects who own that risk. It covers how to keep your SaaS product wired into a customer's ERP estate while the underlying system shifts beneath it, detailing the architectural patterns required to survive an enterprise ERP migration, how to handle schema drift during the transition, and why abstracting the connection layer is the only way to maintain zero downtime.
The Enterprise ERP Migration Cliff
Short answer: when an enterprise customer migrates ERPs, your integration's reliability becomes their migration risk. Architect for parallel operation, not cutover, or you will be the line item that delays the project.
If you build B2B software that touches procurement, human resources, or accounting, your product lives and dies by its connection to the customer's Enterprise Resource Planning (ERP) system. For the last decade, connecting to these systems meant wrestling with on-premise servers, XML-heavy SOAP endpoints, and brittle VPN tunnels. Now, a massive shift is underway. Enterprises are actively decommissioning their on-premise monoliths in favor of modern, cloud-native SaaS ERPs.
The stakes for these migrations are incredibly high, and the costs are not abstract. (cite index="1-1,1-2">ITIC's 2024 research shows the cost of hourly downtime continues to spike, with the average cost of a single hour now exceeding $300,000 for over 90% of mid-size and large enterprises. (cite index="2-18">44% of mid-sized and large enterprise respondents reported that a single hour of downtime can potentially cost their businesses over one million. For regulated verticals - (cite index="2-6,2-7">Banking/Finance, Government, Healthcare, Manufacturing, Media & Communications, Retail, Transportation and Utilities - average hourly outage costs topped the $5 million mark.
When your SaaS product sits on the data path between a customer's CRM, billing engine, or HR system and their ERP, an integration outage during a migration window is functionally the same as an ERP outage. The customer's CFO does not care that the journal entries failed because your connector to NetSuite was looking for a sandbox subsidiary ID that no longer exists. They care that month-end close is now four days late.
As covered in our guide to enterprise integration strategy, moving upmarket means adopting an integration posture where the underlying ERP can change shape - new endpoints, new auth model, renamed custom fields, an entirely different protocol - without your application code changing at all. System integrators and enterprise engineering teams cannot afford to take systems offline for weeks to rewrite integration logic. They need a strategy that allows the legacy system and the cloud system to run concurrently, seamlessly shifting data flows without the core SaaS application ever knowing the difference.
Challenges of Legacy ERP Migrations
Beyond the technical failure modes we cover in the next section, legacy ERP migrations bring a set of structural challenges that shape how your SaaS product has to respond. These are the reasons a migration slips a quarter, not the reasons a single sync job crashes.
- Timeline volatility. Cutover dates slip constantly, driven by fiscal calendar cutoffs, audit windows, or executive travel schedules. Your integration has to survive the announced date and the three revised dates that follow.
- Non-prod tenants that lie. Sandbox instances of the target cloud ERP rarely mirror production scale, customization, or data shape. Integration tests that pass against a fresh sandbox routinely fail against a heavily customized production tenant on cutover day.
- Undocumented custom code. Legacy ERPs accumulate a decade of custom triggers, workflows, SuiteScripts, ABAP enhancements, and stored procedures. Nobody has a complete inventory. Any one of them can silently mutate the data your integration reads.
- Data volume and history. Multi-million-row invoice tables and seven years of journal history need to be replicated, reconciled, and kept in sync during the migration window. Full re-syncs are measured in days, not hours.
- Compliance and audit trails. SOX, GDPR, and industry-specific controls require an unbroken audit trail across the cutover. A gap in transaction history because "the sync job failed on Saturday" is a reportable event.
- Vendor coordination overhead. Your release cadence, the customer's migration schedule, and the ERP vendor's own patch cycles rarely align. You need an architecture that absorbs mismatched timelines without triggering an emergency deploy on your side.
These are not problems you solve with better cutover planning. They are problems you solve by removing the cutover from your critical path entirely.
Why the "Big Bang" Cutover Breaks B2B SaaS Integrations
The traditional approach to enterprise software upgrades is the "Big Bang" cutover. The IT team freezes the legacy on-premise server on Friday at 5:00 PM, runs a massive one-shot data migration script over the weekend, flips DNS, and prays the new cloud ERP works on Monday morning. It fails for the same reason monolith rewrites fail.
(cite index="13-2,13-3">The big bang approach can be akin to chopping down the entire forest and starting anew - it's disruptive, fraught with risk, and can be an immense strain on people and resources. Data from the Standish Group CHAOS Report indicates that between 50% and 70% of large-scale ERP migrations fail to meet their original scope, timeline, or budget objectives. (cite index="13-4">It's relatively uncommon for organizations to adopt this method for significant system overhauls, and yet it is exactly what most B2B SaaS vendors implicitly assume their customers will do.
The vendor schedules "integration update" tickets for the migration weekend, throws engineers at it on Saturday, and discovers at 2 AM that the new tenant's REST endpoints return SOQL-style IDs while the old SOAP endpoints returned base-62 strings. For a B2B SaaS vendor connected to that ERP, a Big Bang cutover creates an engineering nightmare. The differences between the old system and the new system are not just cosmetic. They represent fundamental shifts in API architecture, much like the breaking API changes that plague standard SaaS integrations.
A few specific failure modes show up consistently in these projects:
- Protocol shifts: Moving from XML-based SOAP envelopes to JSON-based REST or GraphQL APIs.
- Schema drift mid-migration: Custom fields renamed during the move. Picklist values consolidated. Legacy custom fields (
custbody_invoice_approval_status) rarely map 1:1 to the new cloud schema (custom_fields.approvalStatus). Your mapping logic, hardcoded against the legacy schema, silently writes to the wrong field. - Auth model changes: Moving from long-lived Basic Auth credentials, token-based session auth, or static IP allowlists to short-lived OAuth 2.0 tokens or mutual TLS that require automated refresh cycles. Your secrets store and refresh logic now need two parallel implementations.
- Pagination and rate-limit shifts: SOAP-era APIs that returned 10,000 rows per call get replaced with stateless, cursor-based REST endpoints capped at 100 per page with strict concurrency limits. Sync jobs that ran in 12 minutes now take 6 hours.
- Dual systems of record: For weeks or months, both ERPs hold partial truth. Read-from-legacy plus write-to-new is a recipe for lost transactions. Read-from-both with no reconciliation is worse.
None of this is hypothetical - it is the standard texture of every NetSuite-to-NetSuite, Dynamics-AX-to-D365, or JDE-to-Fusion project. If your integration logic is hardcoded - if (erp === 'legacy_on_prem') { executeSoapCall() } - your engineering team is forced into a frantic, high-stakes rewrite. When the inevitable data model mismatches occur, your sync jobs fail, background workers crash, and customer trust evaporates.
The fix is not better cutover planning. The fix is to stop assuming a cutover at all.
Zero Downtime Architecture Strategies
Zero downtime during an ERP cloud migration is not one pattern - it is a stack of complementary strategies applied at different layers. The Strangler Fig and dual-write patterns covered in the next section sit inside this broader toolkit. Skip any one of these and the cutover eventually shows up as a customer-visible incident.
Blue-green integration environments. Maintain two fully configured connection profiles per account: one pointing at the legacy ERP, one pointing at the cloud ERP. Both stay live during the entire migration window. Cutover is a config change, not a redeploy, and rollback is the same config change in reverse.
Percentage-based canary routing. Instead of a hard flip, route a small share of traffic (start at 1%) to the new cloud ERP and grow the share as reconciliation confidence improves. Deterministic hashing on the resource ID keeps routing stable across retries.
// Route a percentage of reads to the new cloud ERP, everything else to legacy.
async function routeInvoiceRead(accountId: string, invoiceId: string) {
const rollout = await getRolloutPercent(accountId, 'invoices.read')
const bucket = hashToBucket(invoiceId) // deterministic 0-99
const target = bucket < rollout ? 'cloud_erp' : 'legacy_erp'
return truto.unified.accounting.invoices.get({
integrated_account_id: `${accountId}__${target}`,
id: invoiceId,
})
}Per-resource feature flags. Customers rarely migrate every resource at the same time. Reference data (chart of accounts, currencies) usually moves before transactional data (invoices, journals). Flag the cutover per resource type, per account, and store the flag as data - never as a code branch.
Event sourcing for replay. Persist every mutation as an event before it hits either ERP. If the cloud ERP is behind by six hours after a network incident, replay from the event log instead of asking the source of truth to re-emit changes it no longer remembers.
Circuit breakers on the new path. When the cloud ERP starts returning 5xxs or 429s past a threshold, trip the breaker and route reads back to legacy automatically. Fail closed on the untrusted new path, fail open on the known-good legacy path.
Idempotent write contracts everywhere. Every write carries a deterministic key derived from the source event so a retry, a replay, or a dual-write collision converges to a single row.
Applied together, these strategies let you migrate a customer from an on-premise ERP to a cloud API without a maintenance window on either side. Each one is a rollback lever in isolation.
Architectural Patterns: Strangler Fig and Dual Writes
To achieve zero downtime, system integrators rely on two proven architectural patterns: the Strangler Fig pattern and dual-write synchronization. The two patterns that actually work for zero-downtime ERP migration are well documented and battle-tested. They are also frequently misapplied.
The Strangler Fig Pattern, Applied to ERP Integration
(cite index="15-1">The strangler fig pattern was introduced by Martin Fowler as a way to manage risk when modernizing or rewriting large, monolithic systems. The mechanism is simple: (cite index="18-1,18-2">you replace specific functionality with a new service or application, one component at a time, and a proxy layer intercepts requests that go to the monolithic application and routes them to either the legacy system or the new system.
Applied to ERP integration, the "monolith" is the legacy ERP and the "new system" is the cloud ERP. Your SaaS product talks to an abstraction layer - a façade or a Unified API - that decides, per resource and per account, which backend gets the call.
flowchart LR
A[Your SaaS Product] --> B[Integration Façade]
B -->|invoices, journals| C[(Legacy On-Prem ERP)]
B -->|customers, vendors| D[(Cloud ERP)]
B -.read both, write new.-> E[Reconciliation Job]
C -.batch sync.-> DThis pattern limits the blast radius. If the new cloud API endpoint fails or returns unexpected data structures, you simply update the routing configuration to fall back to the legacy system. Your core application logic never changes. The phases typically look like this:
- Façade-only: All reads and writes still hit the legacy ERP. The façade just normalizes the response shape so your application stops caring about SOAP vs REST.
- Read from new, write to legacy: Low-risk resources (reference data: chart of accounts, currencies, subsidiaries) move first. Reads come from the cloud ERP, writes still go to legacy with replication catching up.
- Dual-write transactional resources: Invoices, sales orders, journal entries get written to both systems simultaneously, with the legacy system as system of record.
- Flip system of record: Cloud ERP becomes authoritative. Legacy becomes the fallback reader for historical data.
- Decommission: (cite index="12-3,12-4,12-5">You can start deprecating the legacy database and legacy system. After you validate the new database, you can retire the legacy database. This retirement completes the migration process with minimal disruption.
Dual Writes Without Drift
While the Strangler Fig pattern handles routing, you still have the problem of state consistency. If a user creates a new contact in your SaaS application, that record must exist in whichever ERP is currently acting as the source of truth.
Dual-write infrastructure is essential for synchronizing data between legacy ERPs and modern cloud applications during a phased migration. Implementing bidirectional, near real-time writes ensures that both the old and new systems maintain consistent state until the legacy system is fully decommissioned. Microsoft's own Dynamics 365 dual-write infrastructure exists precisely because keeping two ERPs in agreement is non-trivial: you need bidirectional, near real-time writes, conflict resolution, idempotent operations, and a way to bound divergence.
A few rules that hold up in production:
- Idempotency keys on every write: Stamp each outbound call with a deterministic ID derived from the source event. If the same payment posts twice during reconciliation, the second write is a no-op.
- Asymmetric authority by resource: Pick exactly one system of record per resource type at any given moment. "Both" is not an answer.
- Compensating transactions, not rollbacks: Distributed two-phase commit across two ERPs is a fantasy. If a write succeeds against the cloud ERP and fails against legacy, queue a compensating action and surface the inconsistency.
- Reconciliation jobs, not just sync jobs: Run a periodic diff that compares record counts and checksums between the two systems. The first time you see a 0.3% drift on customer balances, you want to know within an hour, not at month-end.
When your application issues a POST request to create a record, the abstraction layer must fan out that request to both systems. This introduces significant complexity around rate limiting and partial failures. If the modern cloud API accepts the write, but the legacy on-premise API rejects it due to a rate limit, your systems are now out of sync.
It is critical to understand how your abstraction layer handles these failures. A robust platform does not automatically retry, throttle, or apply backoff on rate limit errors invisibly. When an upstream API returns an HTTP 429, the abstraction layer should pass that error directly to the caller, normalizing the upstream rate limit info into standardized headers (ratelimit-limit, ratelimit-remaining, ratelimit-reset) per the IETF specification. Because the abstraction layer passes these normalized errors back, your dual-write infrastructure must implement its own resilient queuing. You need an event bus, exponential backoff, and strict idempotency keys to ensure that a failed write to the legacy system is retried until it succeeds, without duplicating the successful write in the cloud system.
How System Integrators Use Unified APIs to Abstract the ERP Layer
Building a custom abstraction layer to handle Strangler Fig routing and protocol translation is a massive undertaking. It requires mapping XML to JSON, normalizing pagination cursors, and maintaining separate OAuth clients. The pragmatic choice for most B2B SaaS teams is a declarative Unified API that already implements the proxy, the normalization, and the per-account routing.
The core property you want is this: changing which underlying ERP an account points to should be a configuration change, not a deploy. If your SaaS code calls GET /unified/accounting/invoices?integrated_account_id=acme_co, the same call should work whether acme_co is currently connected to NetSuite, SAP S/4HANA Cloud, or Microsoft Dynamics 365 Business Central.
Modern system integrators use a declarative Unified API to abstract the ERP layer entirely. By placing a zero data retention platform like Truto between the SaaS application and the customer's infrastructure, you decouple your codebase from the underlying API specifics. The platform does not use hardcoded switch statements to figure out if it is talking to a legacy NetSuite SOAP endpoint or a modern REST API. Instead, integration behavior is defined entirely as data - JSON configuration blobs that describe how to talk to the API, and JSONata expressions that describe how to translate the payloads.
When a request hits the unified endpoint, the generic execution engine loads the configuration for the currently active connected account. If the account is pointed at the legacy ERP, the engine reads the configuration, formats an XML envelope, applies Basic Auth, and fires the request.
// Caller code stays identical across the entire migration.
const invoices = await truto.unified.accounting.invoices.list({
integrated_account_id: 'acme_co',
modified_after: lastSyncTimestamp,
})
// Behind the façade, acme_co's connection moves from
// legacy_netsuite_soap -> netsuite_rest -> sap_s4hana_cloud
// without a single line of caller code changing.When the customer is ready to cut over to the cloud ERP, the system integrator simply updates the connected account configuration to point to the new integration definition. The core SaaS application continues to make the exact same request. The execution engine now reads the new configuration, formats a JSON payload, applies the OAuth 2.0 bearer token, and fires the request.
A practical separation of concerns for the migration window looks like this:
| Concern | Where it lives |
|---|---|
| Resource shape (Invoice, Journal, Vendor) | Unified data model |
| Endpoint, auth, pagination per ERP | Integration config (data) |
| Field-level translation per ERP | JSONata mapping expression (data) |
| Per-customer custom field handling | Account-level override (data) |
| Routing decisions during cutover | Façade logic in the integration platform |
| Business workflow | Your SaaS application |
The single most expensive mistake teams make is putting any of the first four rows into application code. Once if (provider === 'netsuite_legacy') shows up in your billing service, you have lost the abstraction. This turns a high-risk code deployment into a low-risk data operation. You can read more about how this architecture eliminates technical debt in our guide on Zero Integration-Specific Code: How to Ship API Connectors as Data-Only Operations and The Architect's Guide to Bi-Directional API Sync.
The Proxy API Fallback: Even with powerful JSONata transformations, you will occasionally encounter legacy ERP edge cases that simply do not fit into a unified model. A customer might rely on a highly specific, undocumented stored procedure in their on-premise system or a custom SuiteScript endpoint that has no equivalent in the modern cloud API. For these scenarios, abstracting the connection should not mean losing access to the underlying power of the native API. Truto provides a Proxy API that gives direct, unmapped access to the integration's endpoints. The Proxy API handles the authentication lifecycle, credential injection, and URL construction, but skips the mapping layer entirely. You will need it more often than you think during a migration. Read more in What is a Proxy API?.
Handling Custom Fields and Schema Drift During the Transition
The hardest part of an ERP migration is not the authentication or the protocol change. It is the schema drift.
Enterprises heavily customize their ERPs. The legacy NetSuite account has 47 custom fields on the customer record, six of which are referenced by the cloud-side workflow. The S/4HANA target has those same concepts but renamed, retyped, and partially consolidated. During the migration window, both shapes are live, sometimes for the same logical customer.
If your SaaS application expects a unified data model, this schema drift will break your sync jobs. You need a way to map these disparate custom fields dynamically, on a per-customer basis, without altering your core application logic. The cleanest pattern is a three-level mapping override hierarchy powered by JSONata, a functional query and transformation language for JSON.
The 3-Level Override Hierarchy
To manage these transformations across hundreds of enterprise customers, Truto utilizes a strict 3-level override hierarchy. This allows system integrators to map custom fields differently for the legacy ERP versus the cloud ERP on a per-account basis during the migration window.
- Platform default mapping: "Standard NetSuite invoice maps to standard unified Invoice." This works for standard, out-of-the-box fields across all integrations.
- Environment override: Overrides applied to a specific deployment environment. Your integration team adds custom field mappings that apply across all your customers using NetSuite. This catches your product-wide enrichment.
- Account override: Overrides applied directly to a single customer's connected account.
The account-level override is the key to zero-downtime migrations. While Customer A is still on the legacy ERP, they use the default mappings. When Customer B (Acme Co) migrates to the cloud ERP, you apply an account-level override to their specific connection. Acme Co's custcol_legacy_po_ref field maps to purchase_order_reference on the unified Invoice for that account only.
// Example Account-Level Override for a legacy custom field
{
"integrated_account_id": "acme_co",
"unified_model_override": {
"accounting": {
"invoices": {
"list": {
"response_mapping": "$merge([$, { 'purchase_order_reference': custom_fields.custcol_legacy_po_ref }])"
}
}
}
}
}When Acme finishes their migration, you flip a flag and the override targets the new field name on the cloud ERP, handling any type conversion on the fly.
// Example Account-Level Override targeting the new cloud custom field
{
"integrated_account_id": "acme_co",
"unified_model_override": {
"accounting": {
"invoices": {
"list": {
"response_mapping": "$merge([$, { 'purchase_order_reference': customFields.purchaseOrderReference }])"
}
}
}
}
}During cutover for Acme Co, that single JSONata expression flips from reading custom_fields.custcol_legacy_po_ref (legacy NetSuite path) to reading customFields.purchaseOrderReference (cloud path). No other customer is affected. No SaaS code is redeployed. This means you can support customers in various stages of their migration journeys simultaneously, without maintaining multiple code branches. See 3-Level API Mapping: Per-Customer Data Model Overrides Without Code and Mapping Custom Objects with JSONata for working examples.
This architecture handles schema drift gracefully. It does not handle semantic drift. If "customer" in the legacy ERP includes prospects but the cloud ERP separates them, your unified model needs an explicit decision about which semantics to expose. Ship that decision in the unified data model design, not in the mapping layer.
A note on rate limits during cutover: dual-writes effectively double your API call volume against both systems. Most cloud ERPs have stricter concurrency limits than their on-prem predecessors. As mentioned, a good unified API surface normalizes upstream rate-limit headers and passes 429s straight through, leaving retry and backoff to the caller. Only your application knows whether a stalled invoice sync is a hard failure or a candidate for delayed retry. For practical patterns, see our guide on Handling Rate Limits Across Multiple Third-Party APIs.
Step-by-Step Migration Playbook
The patterns above are only useful if they map to concrete actions during a live migration. Here is the sequence system integrators actually run when moving a customer from a legacy on-premise ERP to a cloud API without downtime. Every step is a configuration or routing decision at the integration layer - none of them are SaaS application code changes.
1. Inventory the current integration surface
Before touching either ERP, catalog every endpoint, resource type, custom field, webhook, and scheduled job your SaaS product uses against the legacy system. If you cannot answer "what fields on the invoice does our billing service read?" in under an hour, stop here and build the inventory. You cannot migrate what you cannot see.
2. Deploy the façade before anyone plans a cutover
Put the unified API in front of the legacy ERP first, while everything is still stable. Your SaaS keeps hitting the legacy system, but through the abstraction. If the first time you deploy the façade is during the migration window, you are running two experiments at once.
3. Establish a reconciliation baseline
Before the cloud ERP goes live for the customer, run a reconciliation job that counts rows and computes checksums against the legacy ERP. This is your "known good" snapshot. Every subsequent diff during the migration window is measured against this baseline.
// Nightly reconciliation baseline against the legacy system.
async function reconcileBaseline(accountId: string) {
const [legacyCount, legacyChecksum] = await Promise.all([
truto.unified.accounting.invoices.count({ integrated_account_id: accountId }),
truto.unified.accounting.invoices.checksum({
integrated_account_id: accountId,
fields: ['id', 'amount', 'currency', 'status', 'updated_at'],
}),
])
await recordBaseline({ accountId, legacyCount, legacyChecksum, at: new Date() })
}4. Migrate reference data first
Chart of accounts, currencies, tax codes, subsidiaries, and vendor lists rarely change hour-to-hour. Point reads at the cloud ERP for these resources first. If it goes wrong, the blast radius is a stale dropdown, not a lost journal entry.
5. Enable dual writes for transactional resources
Turn on dual-write for invoices, journals, sales orders, and payments. Legacy stays authoritative. Every mutation carries an idempotency key so replays and retries converge to a single row.
// Dual-write with idempotency and asymmetric authority.
async function createInvoice(accountId: string, invoice: Invoice) {
const idempotencyKey = deterministicKey(
accountId,
'invoice.create',
invoice.sourceEventId,
)
// System of record write - must succeed before we acknowledge upstream.
const legacyResult = await truto.unified.accounting.invoices.create({
integrated_account_id: `${accountId}__legacy_erp`,
idempotency_key: idempotencyKey,
data: invoice,
})
// Shadow write - queued, retried, reconciled separately.
await writeQueue.enqueue({
target: `${accountId}__cloud_erp`,
resource: 'invoices.create',
idempotencyKey,
payload: invoice,
sourceId: legacyResult.id,
})
return legacyResult
}6. Shift reads to the cloud ERP behind a canary
Once dual writes have been running long enough that reconciliation drift stays inside your tolerance (usually two to four weeks), start shifting reads to the cloud ERP using percentage-based routing. Watch reconciliation dashboards, not deploy pipelines. Roll back by lowering the percentage - no redeploy required.
7. Flip the system of record
When reads have been served from the cloud ERP at 100% for a full close cycle without reconciliation alerts, flip authority. Writes now go to the cloud ERP first, legacy becomes the shadow target. This is a per-account configuration change, not a code deploy.
8. Drain and decommission
Stop shadow writes to legacy. Keep it available as a read-only historical archive for a defined retention window (typically one to two fiscal years for audit access), then decommission. The integration configuration for that account is now permanently pointed at the cloud ERP, and the SaaS application code is unchanged from the day the migration started.
Future-Proofing Your Enterprise Integrations
Enterprise IT is not static. ERP migrations are not one-time events. The customer who is moving off legacy NetSuite this year is the same customer who will move from S/4HANA Cloud to whatever Oracle ships next decade. In five years, these "modern" cloud APIs will be deprecated in favor of new architectures, new authentication protocols, and new data models.
If you hardcode your B2B SaaS application to specific third-party APIs, you are signing up for an endless cycle of forced rewrites, broken syncs, and frustrated enterprise customers. Every time an upstream provider changes their system, your engineering roadmap gets hijacked. The strategic move is to stop treating each migration as a one-off integration project and start treating every ERP connection as inherently mutable.
Three commitments make that real:
- Code your SaaS against unified resources, never against vendor-specific endpoints. The day a salesperson promises a new ERP, your engineering response should be a config change, not a sprint.
- Adopt the Strangler Fig and dual-write patterns by default, even when no migration is announced. (cite index="13-21,13-22">The Strangler Fig pattern allows for the gradual replacement of the old system, reducing risk and making the process more manageable, and because the system is replaced piece by piece, the legacy system can continue to operate during the transition. The same property keeps your SaaS reliable through a customer's planned and unplanned changes.
- Push integration configuration outside your release cycle. If updating a single account's mapping requires a deploy, your blast radius is wrong. Per-account overrides stored as data are the smallest unit of change you can ship.
The customers who complete enterprise ERP migrations without canceling your contract will be the ones whose vendors absorbed the change quietly. Stop letting your customers' IT migrations dictate your engineering schedule. Abstract the connection layer, treat integrations as data operations, and build a system that survives the next decade of enterprise upgrades.
FAQ
- What is the Strangler Fig pattern in ERP migrations?
- It is an architectural approach where a legacy system is gradually replaced by a new system, piece by piece. An abstraction or proxy layer intercepts API calls and routes specific domains to the new cloud system while the old on-premise system remains active for other domains.
- How do dual writes prevent data loss during cloud migration?
- Dual-write infrastructure ensures that every data mutation (like creating an invoice or updating a customer) is written to both the legacy on-premise ERP and the new cloud ERP simultaneously, maintaining state consistency until the cutover is fully complete.
- Why do "Big Bang" ERP cutovers fail for SaaS integrations?
- Big bang migrations require every dependency to switch atomically over a weekend. Unanticipated schema drift, authentication model changes (like moving from Basic Auth to OAuth 2.0), and rate-limit shifts between the legacy and cloud ERPs almost always cause hardcoded integrations to break when business operations resume on Monday.
- How do unified APIs handle schema drift between legacy and cloud ERPs?
- A unified API uses a three-level override hierarchy (platform, environment, account) combined with JSONata transformations. This allows engineers to dynamically map disparate custom fields for a single customer's connection without touching core application code or affecting other users.