Architecture Patterns
Architecture patterns are structural choices evaluated against system constraints and expected change.
Jul 11, 2026
An architecture pattern describes a recurring way to divide responsibilities and connect parts of a system. A pattern is not a maturity level. Its value depends on the problem, failure model, team, and operational environment.
Choose structure by examining:
- deployment independence: which changes must be released separately;
- consistency boundaries: which invariants require one transaction;
- scaling shape: which workloads need different resources or scaling rates;
- failure isolation: which failures must not affect other capabilities;
- ownership: which teams can maintain clear boundaries;
- operational capacity: whether the organization can run the required infrastructure;
- expected change: where coupling creates repeated delivery friction.
Layered Architecture
A layered system groups code by technical responsibility, such as presentation, application, domain, and persistence. It can make dependency direction clear and works well when cross-cutting technical concerns dominate.
The main risk is change scattering: one business feature may require edits across every layer. Layers that merely forward calls add ceremony without enforcing a useful boundary. Open versus closed layers should be a deliberate dependency rule, not a workaround for empty abstractions.
Active Record is one possible persistence pattern inside a layered application; it is not the architecture itself.
Modular Monolith
A modular monolith is one deployable application divided into explicit domain modules. Each module owns behavior and exposes a controlled interface. A shared process does not require shared internal tables or unrestricted imports.
This is a strong default when one deployment cadence and one primary consistency boundary are acceptable. It keeps local calls, transactions, testing, and operations simpler while still forcing useful domain boundaries.
Module boundaries need enforcement through package rules, ownership, tests, and database access policy. Without enforcement, a “modular” monolith becomes a name for an ordinary coupled codebase.
Microservices
Microservices make selected boundaries independently deployable and usually give each service authority over its data. They introduce network failure, versioned contracts, partial availability, distributed observability, and cross-service consistency work.
Extract a service when measured pressure justifies those costs, for example:
- one capability needs materially different scaling;
- release coordination repeatedly blocks independent teams;
- a boundary needs stronger fault or security isolation;
- a capability has a distinct lifecycle or technology requirement;
- ownership is stable enough to operate it independently.
Microservices do not inherently produce lower recovery time, higher availability, or faster delivery. Those outcomes require sound boundaries, automation, capacity, and operational discipline.
Event-Driven Architecture
Event-driven systems publish facts about completed state changes and let consumers react asynchronously. This can decouple release timing and allow multiple consumers, but it creates eventual visibility, duplicate delivery, ordering limits, schema evolution, and replay concerns.
Events should represent facts, not hidden commands disguised in the past tense. Consumers must define idempotency and recovery. Cross-service workflows may use Saga Pattern, while request-reply interactions are covered by Asynchronous Request-Response.
Active Object is a concurrency pattern that also separates invocation from execution, but it does not by itself define a distributed event architecture.
Evolution Rule
Do not distribute a system because it has reached a particular team size, traffic number, or age. Start with the simplest structure that enforces the required boundaries. Record architectural decisions and extract components only when observed pressure outweighs the additional failure modes and operating cost.
A useful extraction path is:
- define and enforce a module boundary;
- measure the coupling or scaling problem;
- make data ownership explicit;
- introduce a versioned interface;
- add failure handling and observability;
- separate deployment only when the boundary can be operated independently.
Distribution is a trade, not an upgrade.