← BLOGHOME

Aug 19, 2025 · 3 min read · Backend Engineering and Architecture

Moving an Integration Workflow to Asynchronous Processing

Moving work to a queue changes more than the architecture. It changes what we promise to the user, when an operation is accepted and how we handle work that’s still in progress.

We had an integration workflow that tried to finish every step before answering the user. It looked simpler that way. It also meant the user waited on every dependency in the chain. The request crossed multiple systems. When all dependencies responded quickly, the experience was simple: the user submitted the operation and received the final result. As the workflow grew, this became a problem. A slow external service made our API slow. A temporary outage made the whole operation unavailable. More traffic meant more requests waiting on resources we didn’t control. Moving the work to background processing made technical sense. It also meant we could no longer pretend that “request accepted” and “work completed” were the same thing.

Accepted and Completed

In a synchronous flow, a successful response usually suggests that the work is finished. In an asynchronous flow, the system may only be promising that the work has been accepted and will continue. That creates questions the architecture diagram doesn’t answer. Can the user continue with another task while processing happens? How will they know when it finishes? Can they cancel it? What happens if it takes longer than expected? Which status should another system treat as final? A queue doesn’t answer any of these questions. Before adding the queue, we needed to agree on the lifecycle of the operation. Otherwise, the backend could scale better while the user had no clear idea of what was happening.

The Operation States

We represented the operation through clear states rather than a single success-or-failure result. The API validated the request and recorded the accepted work. Workers performed the slower integration steps. The system updated progress and made failures visible when automatic recovery wasn’t possible. Retries handled temporary problems, but they weren’t hidden forever. A user shouldn’t see “processing” indefinitely because the backend doesn’t know how to describe a failed operation. This also affected support and operations. A clear status allowed the team to determine where in the process the work had stopped without searching through unrelated logs.

What the Queue Added

Asynchronous architecture can make an API respond faster, but the work still exists. The team now owns a backlog. We need to know if messages are being processed, delayed, duplicated or abandoned. We also need to plan for traffic spikes and deployments while work is still running. A queue moves the waiting from the request to another part of the system. That can be exactly the right decision, but only if the new waiting is observable and manageable. I’ve seen diagrams where adding a message broker appears to solve scalability by creating an arrow between two services. In production, that arrow represents retention, retries, ordering, monitoring, and recovery. For the integration platform, asynchronous processing separated the user-facing request from the availability of slower dependencies. It allowed the system to absorb temporary instability and process work when the dependency recovered. It also made the workflow easier to scale because workers could scale separately from the API.

The improvement only worked after we agreed on what “accepted” meant, which states the user could see and what happened to an operation that couldn’t finish automatically. The queue was only one part of that discussion. I remember spending more time on the meaning of each status than on the worker itself, because the backend could process everything correctly and still leave the user looking at “processing” without knowing if they should wait or try again.