Sebastiano Druda

← Back to writing

Essay / Software Engineering

Complexity Is Usually a Coordination Problem Wearing a Technical Costume

Complexity grows when components, people and states must agree. A practical look at coupling, shared state and the coordination hidden inside architecture.

A price change can be a one-line edit and a week-long project. The calculation is straightforward. The difficulty is making the storefront, supplier feed, promotion engine and reporting system agree on what the new price means and when it becomes valid. Counting functions misses most of that work.

This is a useful place to look when a system feels disproportionately difficult. A complicated algorithm may deserve specialist attention, but many ordinary business applications spend their complexity budget on coordination. They need several facts to remain compatible while different people and processes change them independently.

A system becomes hard when too many things need to be true at the same time.

The practical question is which agreements the business genuinely needs, which agreements the implementation accidentally introduced, and which can be made local enough that changing one thing no longer requires negotiating with everything else.

Start with the agreement, not the diagram

Imagine a shop selling individual items and cases. A supplier reports stock in cases; checkout sells units; purchasing records incoming deliveries. A shared field called quantity cannot express the agreement between those systems. Its meaning depends on packaging, location, reservation state and time.

Splitting that field across three services does not resolve the ambiguity. Neither does moving all three operations into one database. Those choices affect where coordination happens, but the domain still needs a precise answer to what is available to sell.

Write the invariant in ordinary language first: an accepted order must reserve the requested number of sellable units at the relevant location. Then identify the operations capable of changing that fact. The resulting list is often more informative than an architecture diagram full of boxes named after technologies.

Some coordination is essential to the problem. You cannot eliminate the relationship between reservations and availability without changing the promise made to customers. Other coordination is accidental: two teams must update the same configuration because the system stores one business rule in two places. The distinction is a diagnostic aid, not a mathematical classification that every engineer will apply identically.

Shared state hides a meeting inside a variable

A shared mutable record looks economical. Every component can read and update the latest value. That convenience transfers work into questions about ownership: who may write, which update wins, whether readers can observe an intermediate state, and how a mistaken change is repaired.

Consider a product description that suppliers import nightly while editors improve it during the day. If both overwrite the same field, the conflict is organizational as well as technical. The supplier owns factual specifications; the editor owns the customer-facing explanation. A timestamp comparison cannot decide whose responsibility should prevail.

Separating supplier_description from published_description, with an explicit promotion step, can reduce coordination because the two activities stop competing for one value. It introduces another field, so a line-count metric might call the system larger. Operationally, it becomes easier to reason about.

That is why simplicity should be judged at the level of a change. How many facts must a maintainer reconstruct to perform it safely? How many owners must respond? How many intermediate states can users observe? A short implementation can still demand a remarkably long explanation.

Dependencies include people and calendars

A dependency is not limited to an import statement. A deployment that must happen after a supplier refresh and before an accounting export has a temporal dependency. A schema change requiring approval from another department has an ownership dependency. Neither appears in a package graph.

Conway’s original account of system design connects the structures organizations produce with their communication structures. It is useful as a lens for examining interfaces and ownership. It does not prove that every architecture is an inevitable organizational chart, or that reorganizing teams automatically repairs technical coupling.

For an actual change, map the handoffs. Who knows the constraint? Who can approve an exception? Who sees the failure first? If a team owns a service but cannot change its contract without coordinating with every consumer, ownership exists administratively while autonomy remains limited.

A practical review can follow one representative change from request to release. Record each pause and the information needed to continue. The expensive dependencies are frequently the ones whose rules exist only in a person’s memory. Making those rules explicit can help before any service boundary moves.

Independence requires an honest boundary

An asynchronous queue may remove a synchronous call without removing the underlying dependency. If the customer cannot proceed until the queued task finishes, the user experience still depends on the downstream system. The application now also needs a pending state, timeout behavior and a way to reconcile uncertain outcomes.

This can be a good trade. The queue may absorb bursts and isolate temporary failures. But describing the systems as decoupled should not conceal the business relationship that remains. Independence is specific: independent deployment, independent availability and independent data ownership are different properties.

A boundary reduces complexity when it makes an agreement smaller and more explicit.

A useful contract might say that the catalogue publishes an immutable revision and consumers acknowledge which revision they have loaded. That creates an observable relationship. A vague instruction to keep everything synchronized leaves the same agreement implicit and makes failures harder to locate.

The architecture trade-off essay develops this broader point: changing a boundary redistributes responsibilities. The design improves when that redistribution matches the actual problem, rather than a preference for a particular number of services.

A small coordination ledger

For a difficult workflow, write a compact ledger before proposing a rewrite:

AgreementOwnerFailure signalRecovery
A reservation refers to a sellable unitInventoryUnknown unit mappingReject and review mapping
Published prices use the approved revisionCatalogueRevision mismatchRepublish the approved revision
An export represents a complete batchOperationsMissing completion recordResume or regenerate the batch

These are illustrative entries, not universal rules. Their value is that each makes a hidden dependency discussable. A row with several owners and no failure signal deserves attention. A row whose recovery requires editing multiple databases by hand reveals a maintenance burden that a successful demo would not show.

The ledger can also prevent overengineering. If a small team publishes a static site through one controlled pipeline, introducing a distributed coordination service may add more agreements than it removes. A build-time check and a clear release boundary might be sufficient.

Remove agreements before optimizing their machinery

There are several ways to reduce coordination without changing the business promise. Derive values instead of storing competing copies. Give one process ownership of a field. Publish versioned snapshots when consumers can tolerate them. Replace a shared mutable configuration with an explicit input to a job.

Each option has limits. Derivation can be expensive; single ownership can become a bottleneck; snapshots introduce staleness. The point is to expose those costs where they can be evaluated. An optimization that makes synchronization faster but leaves its meaning unclear does little for the next maintainer.

Tests should exercise the agreement, including interruptions. A price import that succeeds only when every participant runs in the expected order is not robust merely because each function has unit coverage. Test a repeated message, a missing mapping and a consumer that remains on the previous revision.

Also measure whether the change became easier to operate. Can one person explain the failure from the available evidence? Can the owning team correct it without coordinating an emergency across unrelated components? Those observations are more directly connected to coordination cost than the number of abstractions introduced.

The next time a modest feature needs a surprisingly large meeting, trace the facts everyone is trying to keep aligned. Some meetings protect an essential business agreement. Others compensate for an agreement that could be removed, localized or made visible in the system itself. That difference is a productive starting point for architecture work.

Sources

  1. Conway on system design — checked 2026-10-14

Topics