Synchronous vs Asynchronous Communication

The post is a summary of the differences and use cases for synchronous (sync) and asynchronous (async) operations in programming.

Synchronous & Asynchronous

Synchronous (sync) operations happen one after the other, where you wait for each to finish.

Synchronous

Asynchronous (async) operations allow you to do multiple things at once without waiting for each to complete.

Asynchronous

Synchronous calls are well-suited for situations where the caller requires an immediate response and can’t proceed without the result.

Async events are suitable for scenarios where the caller doesn’t need an immediate response or can handle the response later.

Commands & Events

Commands are sent by one actor to another specific actor with the expectation that a particular thing will happen as a result.

Events are broadcast by an actor to all interested listeners.

Comamands can be synchronous or asynchronous.

Commands

Events are always asynchronous.

Events

Commands capture intent. They express our wish for the system to do something. As a result, when they fail, the sender needs to receive error information.

Events capture facts about things that happened in the past. Since we don’t know who’s handling an event, senders should not care whether the receivers succeeded or failed

Link: Architecture Patterns with Python - Commands & Events

Event vs Message terminology

Message driven item is sent to a fixed receiver.

Message

Event driven sent item is shared with any consumer.

Event

Event Driven vs Event Sourcing

Event driven is about program flow, event sourcing about state. Event sourcing doesn’t have anything to do with async communication.

Event driven is a programming paradigm where the flow of the program is determined by events.

Event Sourcing is a data modeling approach where the state of an application is determined by a sequence of events that have occurred over time.

If new to events, check e.g.: RabbitMQ Tutorials

Problems with Sync

  • Resilience: A single failed call can cause the entire process or operation to fail, and by default there is no way to retry failed calls.
    • Error recovery: “Unnecessary” failures when other system is unresponsive. In such cases, recovering from the error may require manual actions.
  • Complex logic: Services rely on each other’s responses and actions, and may need to call each other back and forth.
  • Blocking: Performance bottlenecks when a service is slow or unresponsive.
  • Scalability: Too many simultaneous requests can result in slow or unresponsive service.

Why Async?

  • Resilience: Retrying failed tasks and calling the next operation only after the previous one has succeeded. Link operations, so process can be continued from the next step.
  • Loose coupling of services: Each service relies on events, and there is no direct connection between services.
  • Scalability: Messages are executed one by one, and it is possible to add more consumers.

Problems with Async

  • Infrastructure setup is more complex as it often requires some intermediary service to handle the events.
  • Potential complexity increase if events trigger new events, etc.
  • Complicated debugging.

Common(-ish) Problem Cases

Case: Additional Data Is Required for Processing Event

Event processing requires additional data from the service sending the event, necessitating a call to the original service.

The event should contain all necessary data to execute the action.

Additional data

If this is not possible for some reason, then fetching the data is acceptable.

Case: Need to Wait for the Response Before Continuing Execution

NOTE: This is an example, so often moving from a single sync request to async without a good reason is simply overengineering.

Synchronous calls are necessary when an immediate response is required, and proceeding without it is impossible.

Sync required

This can be achieved with an asynchronous request/reply event wrapper, where the original request blocks the execution while waiting for the response from the executing services. This pattern is usually complex to implement.

Async wrapper

Consider whether processing could be divided into multiple parts, allowing event handling to continue once the initial event is processed. This approach might be easier to implement for background tasks rather than for immediate user requests.

With this approach, it doesn’t matter how long the processing takes, as the user request is handled immediately.

Async tasks

For immediate user requests, it might be beneficial to separate the actual processing from the request and for the client to poll if the task has been completed.

One option is to replace polling with some event-driven mechanism, e.g. WebSockets, where the client is notified when the processing is completed.

Async websocket

Case: Complex Call Chains

Synchronous calls would block the user or system for an unacceptable duration. Errors in each call can cause the entire process to fail.

Sync chain

By using asynchronous calls, the user can continue using the application while the processing is ongoing. Process execution is less error prona ad each step can be retried if it fails, and the process can continue from the last successful step.

Choreography

Case: Complex Flow and Transactional Operations Across Services

Asynchronous communication can involve multiple services, with each service responding to events triggered by others. Each service should know what to do upon receiving an event and how to respond to it. The flow is complex, and control of the flow is distributed among the services.

Choreography

In scenarios where a transaction spans multiple services, using asynchronous operations can make it hard to ensure that all steps either succeed or fail together, which is necessary for keeping data consistent. For example, when placing an order that requires both inventory management and payment processing, both steps must work in sync to maintain accuracy.

Saga

By using a Saga pattern where the control of the flow is handled by a single entity (Service A in this example). Each step of the transaction publishes an event upon completion and control entity decides what to do next.

Written on April 3, 2024