Skip to content
MJSCRIPT®

01  /  Engineering journal  ·  6 min read

Designing Integrations Across Independent Business Systems

How to connect applications that own their own data without turning them into one system: boundaries, REST versus events, webhooks, and queue-backed asynchronous work.

  • Distributed systems
  • REST
  • Webhooks
  • BullMQ
  • Redis
SYNCHRONOUS CALLCallerDependencyWAITSQUEUED JOBCallercontinuesQueueholds the jobWorkerworks later
Illustrative. Conceptual comparison of a blocking call and a queue-backed job. General pattern, not a specific deployment.Synchronous: the caller sends a request and waits for the dependency. Queued: the caller hands the job to a queue and continues; a worker processes it later.

About this article. This is a general technical explainer, not an implementation log. Recommendations here are general engineering guidance. The project-specific facts are limited to those in the TORA & TORY case study: separate applications with separate databases, connected through REST APIs, webhooks, and BullMQ with Redis. Nothing below claims a particular retry, idempotency, or monitoring setup in production.

When several businesses share an owner, their software tends to arrive in separate pieces: a storefront here, an operations tool there, an application per affiliated company. Each piece solves its own problem and keeps its own database. The question that follows is rarely “how do we merge these?” It is “how do we make them cooperate without merging them?”

The thesis of this article is that integration is a design problem about boundaries first and transport second. Choosing REST, webhooks, or a queue is the easy part once it is clear who owns which data and which interactions can tolerate waiting.

Start with ownership

An application boundary is worth keeping when the application has its own reasons to change: its own release schedule, its own data model, its own users. The cost of that independence is that no application may reach into another’s database. Doing so couples schemas together and quietly rebuilds the monolith, with fewer safeguards.

A useful rule is one writer per piece of data. An order belongs to the application that took it. Stock levels belong to whichever system is the source of truth for inventory. Other applications may hold copies or references, but changes go through the owner. Everything below follows from this rule.

Ask before you choose a transport

Three questions sort most interactions:

  1. Does the caller need the answer right now? A storefront that must show current stock needs a response. A report that refreshes overnight does not.
  2. Who should know that something happened? If one application announces a fact and several others might care, the announcer should not need to know who they are.
  3. What if the other side is down? If the interaction must succeed even when a dependency is unavailable, it cannot be a blocking call.

REST: asking and commanding

A REST API is request and response. It suits direct questions (“what is the state of this order?”) and direct commands (“create this record”). Its strengths are simplicity and immediacy: the caller learns the result in the same exchange.

Its cost is temporal coupling. The caller is as available as its dependency and as slow as it. Chains of synchronous calls multiply this: if A calls B and B calls C, A inherits the failure modes of all three.

Events: announcing facts

An event notification inverts the relationship. Instead of “do this,” the message says “this happened.” The sender does not wait for a result and does not need to know the audience.

That decoupling is the value, and also the difficulty. Because no single request carries the outcome, you must decide how to learn that downstream work finished, how to see failures, and how to deal with messages that arrive late or twice.

Webhooks: events over HTTP

A webhook is the simplest event mechanism for independent applications: when something happens, the source sends an HTTP request to an address the receiver registered. No shared infrastructure is required, and any application that can serve HTTP can receive them.

Good webhook handling follows a pattern: validate quickly, acknowledge quickly, work later. A receiver that does heavy processing before responding invites timeouts, and timeouts invite redelivery. The next section is the answer.

Asynchronous jobs: separating “received” from “done”

A queue lets a receiver record that work is needed and return immediately. A separate worker picks the work up when it can. In Node.js, BullMQ is a queue library that stores jobs in Redis, with workers that process them outside the request that created them.

// Pseudocode — illustrative, not production source.
on incoming event:
    if not valid(event): reject
    enqueue job { type, payload }
    respond "accepted"

worker:
    job = next job from queue
    perform the work (for example, call another application's API)
    mark job completed, or failed with a reason

This gives you three things: the sender gets a fast response, bursts of events are absorbed by the queue instead of overwhelming a downstream system, and slow or unavailable dependencies delay work rather than break requests.

When asynchronous is the wrong choice

Queues are not free. Prefer a direct call when:

  • The user is waiting on the result and a delay would be visible.
  • The operation is cheap, fast, and the dependency is reliable.
  • Ordering and immediate consistency matter more than throughput.

Asynchrony also moves complexity somewhere else. Instead of a stack trace you have a job that failed in a different process some time later. That is a trade, and it should be a deliberate one.

Failure scenarios worth designing for

These are general failure modes of event-driven integrations. Which of them matter, and how they are handled, depends on the system.

  • Duplicate delivery. Webhook senders and queues commonly deliver at least once. Handlers are usually written so that doing the same work twice is harmless, for example by keying work on a stable identifier.
  • Partial failure. A job may complete the first half of its work and fail. Work that spans systems needs a plan for what “half done” means and how to finish or undo it.
  • Poison messages. A job that can never succeed should not block the queue forever. Systems typically cap attempts and set such jobs aside where a person can look at them.
  • Out-of-order events. Two events about the same record can arrive in either order. Consumers need either ordering guarantees or logic that is tolerant of both.
  • Slow dependencies. A queue converts “slow” into “backlog.” A growing backlog is a signal worth watching.
  • Silent loss. The most dangerous failure is the one nobody sees. Asynchronous flows are harder to trace, so visibility (what was queued, what ran, what failed) deserves design time early rather than after the first incident.

Contracts and change

Once applications are independent, the shapes of their APIs and events are contracts. Adding an optional field is usually safe; renaming or removing one is a coordinated change. Versioning, tolerant readers, and written definitions of each event cost little at the start and save a great deal when a third application joins.

Consistency, honestly

Separate databases and asynchronous messages mean the system is eventually consistent: for a short time, applications may disagree. That is acceptable for many business flows and unacceptable for some. Decide per workflow which is which, and design the user experience around the window of disagreement instead of pretending it is not there.

Conclusion

Integrating independent systems is mostly a matter of deciding who owns what, then matching each interaction to the weakest coupling that still works: a direct call when you need an answer, an event when you are announcing a fact, a queue when the work can wait. Add the machinery for retries, visibility, and contracts in proportion to what a failure would cost.

For how these ideas show up in a real system, see the TORA & TORY case study, which describes the confirmed pieces of that architecture and labels everything else as conceptual.

Contact

Building something that has to ship?

Open to founding engineer and full-stack product engineering opportunities.