Integrations
Integrating with systems that were never built to be integrated
Much of what we build sits next to a system someone else built years ago: a booking engine, an HR platform, a back office that runs the whole business. Building the new product is usually the easy part. The hard part is making it work reliably with a system that was never designed to be talked to.
Decide who owns what, and write it down
Before any code, settle one question for every piece of data: which system is the source of truth? In most of our projects the older system keeps owning the core records, such as customers, bookings, or employees, while the new product owns the experience around them.
That rule shapes everything else. The new product reads freely, caches what it needs, and writes back only at clear, deliberate moments, for example once a booking is confirmed. Fewer write paths mean fewer ways for the two systems to disagree.
Put a translation layer in between
Older systems come with their own vocabulary, odd field names, and quirks that made sense fifteen years ago. If those leak into your new codebase, every future change has to understand them.
We keep a thin layer between the two that translates the old system's model into the new product's model. The quirks live in one place, the rest of the code stays clean, and if the old system is ever replaced, only that layer has to change.
Sync as if it will fail, because it will
Old systems time out, rate-limit, go down for maintenance, and occasionally return something unexpected. A sync that assumes a clean run every time will eventually corrupt data. The ones that hold up share a few traits:
- Idempotent: running the same sync twice produces the same result, so retries are always safe.
- Incremental: it picks up where it left off instead of re-reading everything.
- Field-aware: it updates the fields it owns and leaves the rest alone. Overwriting a whole record can quietly undo work done elsewhere, like a vehicle assigned while a rental is already in progress.
Spread the load
It's tempting to schedule every job at the top of the hour. Then every stream hits the old system at once, it slows down, and jobs start timing out together. Staggering start times is a one-line change that makes a fragile system noticeably calmer.
Build a fake of the old system
Developing against a production system of record is slow and risky, and test environments for older platforms are often missing or out of date. A small local fake that mimics the old system's API, its slow responses and error cases included, lets the team build and test the integration properly long before it touches real data.
Know when it stops working
Integrations rarely crash loudly. They go stale, and nobody notices until a customer does. Measuring freshness per source, not just whether a service is up, is what turns a silent failure into an alert. We wrote more about that in Your integrations are failing silently.
Bottom line
A good integration is mostly decisions: who owns the data, where the translation happens, and what happens when things go wrong. Get those right and the old system stops being a constraint, and becomes a stable foundation the new product can build on.