BONITSThink · Do · Achieve
Capability

Architecture Patterns

Twenty patterns we design and build with, each one set out the same way: what it actually is, the coding standards we hold teams to when we use it, and a real situation where it is the right answer. A pattern is a trade, not a badge — the value is in knowing what you are paying for it.

Five families of architecture pattern arranged left to right: structure, distribution, data and messaging, scale and resilience, and deployment and evolution, each listing the four patterns it contains.
Most systems use several of these at once. The families are a way to reason about them, not boxes a system has to sit in.

Architecture decisions are the expensive ones. They are made early, when the least is known, and they are the hardest to reverse once code, data and teams have grown around them. What follows is the working reference our engineers use on client projects — deliberately opinionated, because a standard that leaves everything to preference is not a standard.

Three things are worth saying before the list. First, patterns compose: a typical platform we build might be hexagonal inside each service, event-driven between them, with an API gateway at the edge and a circuit breaker on every outbound call. Second, every pattern has a bill — microservices buy independent deployment and charge you in operational complexity, and the mistake is not choosing one but choosing it without reading the price. Third, the standards matter more than the diagram. A pattern applied without its discipline gives you the cost and none of the benefit, which is how a distributed monolith happens.

Family one

Structure — how one system is arranged

These decide where a rule is allowed to live. They cost almost nothing to adopt on day one and are painful to introduce in year three, which is why they are worth being strict about from the first commit.

01

Layered architecture

Presentation, application, domain and data access stacked so that each layer calls only the one beneath it.

Where it fits

A claims back office where the rules change constantly but the shape of the system does not. Business rules sit in one layer, so a regulatory change is an edit in one place rather than a search across the codebase.

Coding standards
  • Dependencies point one way only. Enforce it with project references or an architecture test, not a code review.
  • Cross layers with data, not entities. Map at the boundary so a column rename cannot reach the UI.
  • No layer skipping. If presentation calls the repository directly, a use case is missing from the application layer.
  • Transactions belong to the application layer. A repository that opens its own makes composition impossible.
02

Hexagonal (ports and adapters)

Business logic sits in the middle and reaches the outside world only through interfaces that it defines itself.

Where it fits

A pricing engine that has to run behind a web API today, as a batch job tonight and as a message consumer next year. The rules are written once; each delivery mechanism is an adapter around them.

Coding standards
  • The port is named in the domain’s language. An interface shaped like the vendor SDK is not a port.
  • No framework types in the core. If the domain imports an HTTP request or an ORM entity, the isolation has already gone.
  • Core tests run with no network and no database. If they need either, something has leaked inward.
  • Mapping stays in the adapter. Translation code drifting into the core is how this pattern quietly dies.
03

Pipe-and-filter

Data moves through a chain of independent steps, each transforming it and handing it on.

Where it fits

A nightly supplier ingest: validate, normalise units, enrich from the product master, load. Each step can be tested, timed and replaced on its own, and a bad supplier file fails at a named stage instead of somewhere in a 2,000-line job.

Coding standards
  • A filter reads its input and writes its output. It does not reach into a store belonging to another step.
  • Version the format on the pipe. An undocumented tuple passed between steps becomes the real contract.
  • Every filter restarts from its input. A pipeline you cannot resume is one you rerun from the start at 3am.
  • Apply back-pressure instead of buffering without limit, or a slow step becomes an out-of-memory error.
04

Client-server

Many clients request work from a server that owns the data and the rules.

Where it fits

A dealer portal where several hundred branches read and update shared inventory. Keeping the rules on the server is what stops a branch inventing its own discount logic.

Coding standards
  • Treat every request as hostile. Validate on the server even when the client already did.
  • Keep requests stateless; where you cannot, put the session store outside the process so instances stay interchangeable.
  • Version the contract in the first release. v1 costs nothing on day one and cannot be retrofitted on day four hundred.
  • Set an explicit timeout and payload limit per endpoint. Framework defaults are usually far too generous.
Family two

Distribution — how services split and talk

This is where architecture stops being a drawing and starts appearing on the invoice. Each of these buys independence and charges in network calls, failure modes and people who have to be on call.

05

Microservices

One system delivered as small services that deploy independently and communicate over the network.

Where it fits

An insurer where quoting changes weekly and policy administration changes twice a year. Splitting them lets the quoting team ship on Tuesday without a regression pass over policy admin.

Coding standards
  • A service owns its data. No other service reads its tables — it asks over the API or subscribes to an event.
  • A boundary that needs a distributed transaction is the wrong boundary. Redraw it before reaching for a saga.
  • Every network call assumes failure: timeout, retry with backoff and jitter, and a breaker.
  • Propagate a correlation id on every hop and log it. Without one, an incident becomes archaeology.
  • Independent deployability is the test. Two services that must release together are one service with two names.
06

API gateway

A single entry point that routes, authenticates, rate-limits and aggregates on behalf of many services.

Where it fits

A bank’s mobile app talking to fifteen internal services. The gateway holds token validation and throttling so fifteen teams do not each write their own version of it.

Coding standards
  • Cross-cutting concerns only. The moment business rules appear in the gateway, it becomes the monolith you split up.
  • Authenticate at the gateway and still authorise in the service. An internal caller must not get a free pass.
  • Set timeouts and rate limits per route. One default across a report endpoint and a health check is wrong for both.
  • Gateway config lives in version control and deploys like code. Console-edited routing is undocumented production.
07

Backend for frontend

Each client type gets its own backend, shaped to what that client actually needs.

Where it fits

A retailer running a mobile app, a web store and an in-store terminal. Mobile wants three small payloads over a poor network; the terminal wants one large one. A single shared API serves neither well.

Coding standards
  • A BFF is owned by the team that owns its frontend. Shared ownership rebuilds the coordination cost you were escaping.
  • It aggregates and shapes; it does not hold business rules. Rules duplicated across BFFs drift inside a quarter.
  • Duplication between BFFs is acceptable and often correct. Resist consolidating them back into one.
  • Keep its timeouts tighter than its callers’. A BFF is a fan-out point and the first place a slow dependency shows.
08

Service mesh

A proxy beside each service handles retries, mutual TLS, routing and telemetry so application code does not.

Where it fits

A platform running forty services in four languages. Rather than four retry libraries and four TLS implementations, one mesh policy applies to all of them and is audited in one place.

Coding standards
  • Mesh policy is code. Traffic splits, retries and timeouts belong in version control with review.
  • Never retry in both the mesh and the application. Two layers of three retries is nine calls to a service already struggling.
  • Retry only what is safe to repeat. Mark non-idempotent routes explicitly so the mesh leaves them alone.
  • Budget for the sidecar. Every pod gains CPU, memory and a hop — measure it before rolling out estate-wide.
Family three

Data and messaging — how facts move

The four patterns that most often get adopted for the wrong reason. Each one trades immediate consistency for something else, and that trade has to be a decision the business has agreed to, not one an engineer made on a Thursday.

09

Event-driven architecture

Components publish facts about what happened; others react, without the publisher knowing who is listening.

Where it fits

Order capture in retail. One OrderPlaced event feeds fulfilment, loyalty, fraud screening and the warehouse forecast — and a fifth consumer is added without touching checkout at all.

Coding standards
  • Events are facts in the past tense — OrderPlaced, not PlaceOrder. A command in an event’s clothing rebuilds the coupling you removed.
  • Consumers must be idempotent. At-least-once delivery is the norm and the same event will arrive twice.
  • Version schemas and only add fields. Removing or retyping one breaks consumers you have never met.
  • Publish after the local commit, via an outbox. A publish inside the transaction can be rolled back after the message has left.
10

Broker

A middle component accepts requests and routes them to whichever provider can serve them, so the two never bind directly.

Where it fits

A payments layer in front of several acquirers. Adding an acquirer, or failing away from one that has started degrading, becomes a broker change rather than a change in every calling application.

Coding standards
  • The broker routes; it does not transform business meaning. Content rewriting inside it becomes logic nobody can find.
  • The message contract is owned by the domain, not the broker product, or a broker migration becomes a rewrite.
  • Instrument queue depth and consumer lag, and alert on both. A broker fails quietly, by growing.
  • Agree the dead-letter path and its owner before go-live. An unwatched DLQ is silent data loss.
11

CQRS

The model used to change data is separated from the model used to read it.

Where it fits

A trading dashboard read thousands of times a second while writes arrive a few times a minute. The read side can be denormalised and cached hard without distorting the model that enforces the rules.

Coding standards
  • Split only where read and write loads genuinely differ. CQRS over a CRUD screen buys nothing and costs two models.
  • Make the lag visible. If the read side is eventually consistent, the interface says so rather than showing stale numbers silently.
  • Commands return an acknowledgement and an id, never the projected read model — that invites callers to depend on synchronous projection.
  • Projections rebuild from scratch as a routine operation, not as a rescue.
12

Event sourcing

State is stored as the ordered sequence of changes rather than as the current row.

Where it fits

A general ledger, where “what was this balance on 3 March, and why” must be answerable years later. The events are the audit trail rather than a copy of it kept alongside.

Coding standards
  • Events are immutable. A correction is a new compensating event, never an update to a stored one.
  • No external lookups in a replay path. A handler that calls a pricing API will reprice last year’s events at today’s rates.
  • Snapshot long streams, but keep the snapshot derivable. It is an optimisation, not a source of truth.
  • Build the upcasting layer at the first schema change. The store outlives every deployment you will make.
Family four

Scale and resilience — staying up under load

These are the patterns that decide what your system does on its worst day. They are cheap to add early and are almost always retrofitted during an incident, which is the most expensive time to write anything.

13

Circuit breaker

Repeated failures to a dependency trip a switch that fails fast instead of queuing more calls into it.

Where it fits

Checkout calling a third-party fraud score. When the vendor slows to thirty seconds, the breaker opens and orders route to a review queue — instead of every request thread waiting until the whole site stops responding.

Coding standards
  • Always pair a breaker with a timeout. Without one, calls never fail fast enough to trip it.
  • Configure thresholds per dependency. A reporting service and a payment authoriser do not share a sensible setting.
  • Decide the open-state behaviour deliberately: cached value, queued work, or a clear error. Silently returning empty is the dangerous option.
  • Emit state transitions as events and alert on them. A breaker that opens nightly is telling you something nobody is reading.
14

Saga

A business transaction spanning several services, run as a sequence of local steps each with a compensating action.

Where it fits

Booking travel, where flight, hotel and car live in three systems. If the car fails, the hotel and flight are released by compensations rather than by a lock held open across all three.

Coding standards
  • Write the compensation with the step, in the same pull request. A step shipped without one is an incident waiting for a date.
  • Compensations are idempotent and tolerate being called for a step that never completed.
  • Saga state is durable and inspectable. Support must be able to see which step a stuck booking is on.
  • Orchestrate when the flow is a business process people discuss; choreograph when steps are genuinely independent. Mixing both in one flow makes it unreadable.
15

Master-worker

A coordinator splits work into tasks, distributes them to workers and assembles the results.

Where it fits

An overnight risk run across ten million positions. The coordinator shards by portfolio and fifty workers finish inside a window that one machine could not have met.

Coding standards
  • Tasks are idempotent and independently retryable. A worker will die mid-task; plan for it rather than detect it.
  • Every task gets a visibility timeout, a maximum attempt count and a dead-letter destination after that.
  • Never let the coordinator hold the only copy of progress. Persist assignment state so a restart resumes rather than redoes.
  • Watch for stragglers and cap them. One slow shard sets the length of the entire run.
16

Space-based architecture

Processing units hold working data in memory and replicate between themselves, so the database stops being the bottleneck.

Where it fits

A stadium ticket release — tens of thousands of people in a few minutes. Seat state lives in the grid and the database is written behind the scenes rather than on the hot path.

Coding standards
  • Decide the conflict rule before you start. Two nodes will write the same key; last-write-wins is a decision, not a default.
  • Keep the write-behind durable and monitored. Data that exists only in memory is data you have agreed to lose.
  • Size for the working set, not the request rate. A grid that spills to disk under load is slower than the database it replaced.
  • Rehearse a node loss under load. Rebalancing behaviour is where the whole risk in this pattern sits.
Family five

Deployment and evolution — how systems change

Patterns for what runs where, and for getting from the system you have to the one you want without a rewrite that has to land perfectly on a single weekend.

17

Serverless

Code runs per request or per event on infrastructure you neither size nor keep warm.

Where it fits

Image processing after upload — hundreds of invocations in a lunchtime burst and nothing overnight. Paying per execution beats paying for a virtual machine that is idle nineteen hours a day.

Coding standards
  • Assume a cold start and keep the init path small. Build heavy clients outside the handler so they survive between invocations.
  • Functions are idempotent. The platform will retry, and a duplicate charge is a support ticket.
  • Set a realistic timeout and a concurrency ceiling, or one bad upstream exhausts the account limit and takes unrelated functions with it.
  • State goes outside the function, secrets in a managed store. A function that needs local disk between calls is the wrong shape.
18

Sidecar

A helper container runs beside the application container, adding logging, proxying, secrets or security without changing the application.

Where it fits

Bringing a decade-old service onto a modern platform. A sidecar gives it TLS and structured telemetry without anyone having to rebuild a binary whose original authors have left.

Coding standards
  • The sidecar adds capability; it never holds business rules. Correctness must not depend on it.
  • Pin and roll sidecar versions deliberately. A latest tag is an estate-wide change nobody approved.
  • Declare its resource requests and limits separately, or it competes with the application for the pod’s memory.
  • Define startup and shutdown ordering. An application that starts before its proxy fails its first requests on every deploy.
19

Peer-to-peer

Nodes talk to each other directly and share the work, with no central coordinator in the path.

Where it fits

Distributing a large build artefact to hundreds of agents. Each agent that already holds a chunk serves it on, so the source is not asked to carry every download itself.

Coding standards
  • Authenticate peers and verify content by hash. Any node can lie; treat what arrives as untrusted until checked.
  • Bound fan-out and connection counts, or discovery traffic grows faster than useful work.
  • Design for churn as the normal case. Nodes leaving mid-transfer is expected behaviour, not an exception path.
  • Keep a deterministic tiebreak for conflicting state. Without one, two partitions reconcile into a third answer.
20

Strangler fig

A new system grows around the old one, route by route, until nothing is left pointing at the legacy.

Where it fits

Replacing a fifteen-year-old order system. Search moves first, then the basket, then checkout — each behind a routing switch, with the legacy still serving everything not yet moved. This is the pattern behind most of our modernisation work.

Coding standards
  • Put the routing facade in front before moving anything. Without a seam this is a big-bang rewrite with extra steps.
  • Move one capability at a time and keep it reversible for a release. Switching back is a config change, not a deployment.
  • Run old and new side by side and compare outputs on live traffic before cutting over.
  • Set a date to delete the legacy path and hold it. A strangler never finished is two systems to maintain forever.
Across every pattern

The standards that do not depend on your choice

Whatever the shape of the system, these are non-negotiable on a BONITS engagement. Most production incidents we are called into trace back to one of them being skipped rather than to the wrong pattern being chosen.

  • Every architectural decision is written down — a short decision record naming the choice, the alternatives and what would make us revisit it. Six months later, nobody remembers why.
  • Boundaries are enforced by the build, not by convention. An architecture test that fails the pipeline is worth more than a diagram nobody opens.
  • Every cross-process call has a timeout and a defined behaviour when it expires. An unbounded call is an outage that has not happened yet.
  • Correlation ids flow end to end and appear in every log line, so one request can be followed across every service it touched.
  • Configuration is code, reviewed and versioned. Anything changed in a console is production that nobody can reproduce.
  • Contracts are versioned from the first release, and tested against the consumers that depend on them rather than assumed.
  • Secrets never reach the repository. Managed identity first, a managed vault second, environment variables last.
  • Observability ships with the feature, not after it. Logs, metrics and traces are part of the definition of done.

Not sure which of these you actually need?

That is usually the right question. We run a short architecture review — the system you have, the constraints you are working under and where it hurts — and come back with the patterns that fit, the ones we would avoid in your situation, and what each would cost to adopt.