Skip to content

Connectors and Distribution

A monolith is one deployable unit. You cannot split it, but you can run many instances of it behind a load balancer (see Load Balancer). That is horizontal scaling by replication.

Distribution is the other route. You split the system into separate processes, such as clients and services, that talk over a network. Each call now crosses a process boundary.

That boundary has a cost. One classic rule says not to distribute your objects. The reason is that a remote call differs from an in-process call, and an object boundary cannot hide the difference.

The main new risk is partial failure. A single service, container, virtual machine, or server can crash or become unreachable while the rest keeps running. A service may also answer late because it is overloaded or the network has problems.

A slow remote call can block the thread that made it. If a synchronous call has no timeout, blocked threads pile up. Eventually the application runs out of threads. A local problem then becomes a global outage.

Small unavailabilities add up. Even when every dependency is highly available, intermittent failure is guaranteed. For example, 50 dependencies at 99.99% availability still produce several hours of downtime each month.

Fault tolerance therefore has to be designed in. One recommended step is asynchronous communication between internal services. It limits how far a failure spreads and keeps services autonomous (see Synchronous vs. Asynchronous Communication).

An online shop runs as one program. When traffic grows, the team starts three identical copies behind a load balancer. Later the team splits out a payment service, and the shop now calls it over the network. One day payment slows down. Checkout requests wait for it and their threads stay blocked. Without timeouts, the whole shop stops responding, not just checkout. With asynchronous messages and timeouts, only payments are delayed.

Replicating a monolith is simple, but you can only scale it as one block. Splitting into separate processes gives more freedom, but every remote call can be slow, can fail, or can block callers. Many dependencies multiply these failures. Asynchronous communication limits the spread of failures, but the team must design for it.

How a slow remote call without a timeout can turn a partial failure into a global outage.

Diagram source
flowchart LR
A[Client calls service] --> B{Service responds in time?}
B -- yes --> C[Call completes]
B -- no, no timeout --> D[Thread stays blocked]
D --> E[More threads blocked]
E --> F[Threads run out]
F --> G[Whole application unresponsive]
  • Synchronous remote calls have timeouts, so a slow service cannot block all threads of its caller.
  • The document states how partial failure of a service or server is handled.
  • Communication between internal services is asynchronous where possible, or the document explains why not.
  • The availability of many chained dependencies is considered together, not one by one.