The definition the course uses is the classic one from Garlan and Shaw (1994):
“An architectural style defines a family of such systems in terms of a pattern of structural organization. More specifically, an architectural style determines the vocabulary of components and connectors that can be used in instances of that style, together with a set of constraints on how they can be combined.”
— David Garlan and Mary Shaw, An Introduction to Software Architecture, January 1994
Restated in the module’s own words: an architecture style is a specialization of element and relation types, together with a set of constraints on how they can be used. Architectural styles define types of components and connectors in a specified topology that are useful for structuring an application either logically or physically.
One property is worth underlining because it is what makes a catalogue possible at all: styles are technology and domain agnostic. This is the difference from a reference architecture, which provides a structure for applications in a specific domain and may embody one or more different styles.
Note also the continuity with Chapter 2: in building architecture, styles arose as a set of constraints put upon development in order to elicit particular desirable qualities. That is exactly what a software architectural style is — and it explains why the style you choose determines which quality attributes you get for free and which ones you will have to fight for.
The eight main examples used in the course: layered, port-and-adapters/hexagonal, pipeline, microkernel, service-based, event-driven, space-based, orchestration-driven, microservices.
Design patterns are conceptual solutions to recurring design problems that exist in a defined context. A pattern can be considered architectural when its use directly and substantially influences the satisfaction of some of the architectural drivers.
Buschmann et al. (1996): an architecture pattern “expresses a fundamental structural organization schema for software systems”. While design patterns originally focused on decisions at the object scale — instantiation, structuring, behaviour — today there are catalogues with patterns addressing decisions at varying levels of granularity, including architectural ones. The reference series is PoSA (Pattern-Oriented Software Architecture).
| Architecture pattern | Architectural style |
|---|---|
| An essential part is its focus on the problem and the context, as well as how to solve the problem in that context | Focuses on the architecture approach, with more lightweight guidance on when a particular style may or may not be useful |
The course’s own lab notes on MVC are a good illustration of the distinction. MVC is presented in full pattern form — context, problem, forces, solution, structure, dynamics — because what matters is when and why you would separate an interactive application into model, views and controllers. A style such as “layered” comes with far less situational guidance and far more structural commitment.
From [POSA1], since it recurs throughout the course: MVC divides an interactive application into three components — the model contains the core functionality and data; views display information to the user; controllers handle user input. Views and controllers together comprise the user interface, and a change-propagation mechanism ensures consistency between the user interface and the model.
The forces that motivate it: the same information is presented differently in different windows; the display must reflect data manipulations immediately; changes to the user interface should be easy, and even possible at run time; and supporting different look-and-feel standards or porting the interface should not affect code in the core of the application.
Remember from Chapter 4 that in the Clean Architecture the whole MVC triad lives in the interface adapters circle — it is a pattern for the outer rings, not for the domain.
Before the catalogue, the anti-catalogue. The Big Ball of Mud is the way architects refer to the absence of any discernible architectural structure. It is an anti-pattern defined by Brian Foote and Joseph Yoder in 1997:
“A Big Ball of Mud is a haphazardly structured, sprawling, sloppy, duct-tape-and-baling-wire, spaghetti-code jungle. These systems show unmistakable signs of unregulated growth, and repeated, expedient repair. Information is shared promiscuously among distant elements of the system, often to the point where nearly all the important information becomes global or duplicated. The overall structure of the system may never have been well defined. If it was, it may have eroded beyond recognition. Programmers with a shred of architectural sensibility shun these quagmires.”
— Brian Foote and Joseph Yoder
The classic visualisation is a dependency circle: each dot on the perimeter represents a class, each line a connection between classes, bolder lines indicating stronger connections. In such a code base, any change to a class makes it difficult to predict rippling side effects to other classes, making change a terrifying affair.
Note the causal claim, because it ties this chapter to Chapter 5: the Big Ball of Mud is the predicted outcome of ungoverned growth. That is precisely why the course introduces fitness functions — the JDepend example detects cyclic dependencies, described as “a main architectural anti-pattern that brings towards Big Balls of Mud”. Structure does not survive by itself; entropy wins unless energy is added.
Architecture styles can be classified into two main types [FSA]:
| Type | Definition | Styles |
|---|---|---|
| Monolithic | Single deployment unit of all code | Layered · Pipeline · Microkernel |
| Distributed | Multiple deployment units connected through remote access protocols | Service-based · Event-driven · Space-based · Service-oriented (orchestration-driven) · Microservices |
The historical arc that produced this split: through the various eras of hardware and software evolution, software and its hardware started as a single entity (unitary architecture), then split as the need for more sophisticated capabilities grew. Mainframe computers started as singular systems, then gradually separated data into its own kind of system; in the first personal computers much commercial development focused on single machines, but as networking PCs became common, distributed systems such as client/server appeared.
Generally, software systems tend to grow in functionality over time, requiring separation of concerns to maintain operational architecture characteristics such as performance and scale. Many architecture styles therefore deal with how to efficiently separate parts of the system — the main example being client-server.
Client/server architecture is the fundamental style that separates technical functionality between front end and back end; it is also called two-tier. Many flavours were introduced depending on the era and on computing capabilities.
| Flavour | Structure | Notes |
|---|---|---|
| Desktop + database server | Presentation logic residing on the desktop; more computationally intense action, both in volume and complexity, in more robust database servers | In the early days of personal computers, developers wrote rich desktop applications, separating data into a standalone database server that could connect via standard network protocols |
| Browser + web server | Web browser connected to web server, connected in turn to a database server | Architects often still consider this two-tier, because the web and database servers run on one class of machine within the operations centre while the user interface runs in the user’s browser. Similar separation of responsibilities, but with even thinner clients, allowing wider distribution inside and outside firewalls |
| Three-tier | A database tier using an industrial-strength database server; an application tier managed by an application server; a frontend coded in generated HTML and, increasingly, JavaScript | Appeared as application servers became popular in Java and .NET in the late 1990s |
Beyond client-server: when the business logic is complex, a server can become a monolith that is difficult to manage, scale or extend. We may need a further level of decomposition — Service-Oriented Architecture and microservices.
The three styles that deploy as a single unit. All three, by definition, have a quantum of one.
Also called n-tiered. Components are organised into logical horizontal layers, each with one technical role. The four standard layers are presentation, business, persistence, database; business and persistence are sometimes merged into three layers, and large applications may have five or more.
Deployment admits three physical variants: (a) presentation, business and persistence as one deployment unit with an external database; (b) presentation split into its own unit and business + persistence in a second, with an external database; (c) all four layers, including an embedded or in-memory database, in a single deployment — common for on-premises products.
It is technically partitioned, so a domain such as “customer” is spread across every layer, and consequently a domain-driven design approach does not work as well with this style.
| Concept | Meaning |
|---|---|
| Closed vs open layers | Closed: a request may not skip a layer. Open: a layer may be bypassed. |
| Layers of isolation | Changes in one layer do not affect the others provided contracts hold. It requires the layers in the main request flow to be closed, and it is what lets you swap, say, JSF for React.js without touching the business layer. |
| Services layer | An added layer marked open, holding shared business objects. |
| Architecture sinkhole anti-pattern | Requests pass through layers with no business logic being applied. The remedy is the 80-20 rule: about 20% of requests being sinkholes is acceptable; 80% means you chose the wrong style. |
| Architecture by implication / accidental architecture | The anti-patterns of ending up layered because nobody decided anything. |
Ratings stated in the book’s prose: testability 2 stars, reliability 3 stars, elasticity 1 star, scalability 1 star, performance 2 stars; deployability “very low”; overall cost and simplicity are the primary strengths; layered architectures do not support fault tolerance.
When to use it: small, simple applications or websites; tight budget and time constraints — it is perhaps one of the lowest-cost architecture styles; and as a good default starting point while you are still deciding the eventual style (keep reuse minimal and inheritance shallow, to make the later move easier). As applications grow, maintainability, agility, testability and deployability all degrade.
Also called pipes and filters. Two component types:
| Filter type | Role |
|---|---|
| Producer | The starting point, outbound only; also called the source |
| Transformer | Accepts input, optionally transforms some or all of the data, and forwards it — functional map |
| Tester | Accepts input, tests criteria, optionally produces output based on the test — likened to reduce |
| Consumer | The termination point: persists results to a database, or displays them |
The style underlies Unix shells, MapReduce, EDI tools, ETL tools, and mediators such as Apache Camel. Its named virtue is compositional reuse and architectural extensibility: you insert a new tester filter into the chain without touching the others.
Ratings stated in prose: reliability 3 stars, elasticity 1 star, scalability 1 star; overall cost, simplicity and modularity are the primary strengths; deployability and testability are only around average, rating slightly higher than the layered architecture; fault tolerance is not supported.
When to use it: simple, one-way processing tasks — EDI transformation, ETL flows, passing steps of a business process, streaming telemetry processing. When not: when you need elasticity, scalability or fault tolerance, since it is a monolith with a high mean time to recovery (start-ups of 2 to 15 minutes).
Also called the plug-in architecture. A relatively simple monolithic architecture with exactly two component types: a core system and plug-in components.
The core system is “the minimal functionality required to run the system”, or alternatively the happy path — the general processing flow with little or no custom processing. Moving cyclomatic complexity out of the core and into plug-ins buys extensibility, maintainability and testability. The core may itself be built as a layered architecture or a modular monolith. Typically the whole monolith shares a single database.
| Concept | Detail |
|---|---|
| Plug-in components | Standalone, independent, ideally with no dependencies on each other; they isolate highly volatile code |
| Compile-based vs runtime-based | Runtime plug-ins are managed by frameworks such as OSGi, Penrose, Jigsaw, Prism |
| Communication | Normally point-to-point: a method or function call to the plug-in’s entry-point class. Implemented as shared libraries (JAR, DLL, Gem) or as namespaces, with the recommended naming convention app.plug-in.<domain>.<context> |
| Remote plug-in access | An alternative via REST or messaging — better decoupling, scalability and asynchronicity, but it turns the style into a distributed architecture with all the attendant costs |
| Data | Plug-ins normally do not connect to the shared database — the core passes data in, for decoupling — but they may own their own private data store |
| Registry | The core’s record of available plug-ins: name, data contract, remote-access protocol details. Can be an internal Map or an external tool such as ZooKeeper or Consul |
| Contracts | Standard across a domain of plug-ins, covering behaviour plus input and output data. Third-party plug-ins usually get an adapter to the standard contract |
Ratings stated in prose: testability 3, deployability 3, reliability 3, modularity 3, extensibility 3, performance 3 stars; simplicity and overall cost are the main strengths. Partitioning: the microkernel is the only style that can be both domain partitioned and technically partitioned — most instances are technical, and domain partitioning arises through strong domain-to-architecture isomorphism.
When to use it: product-based applications shipped as a single monolithic install — Eclipse, PMD, Jira, Jenkins, Chrome, Firefox; problems needing different configurations per location or client; strong emphasis on user customisation and feature extensibility. It also works for large business applications whose complexity is rule-shaped: insurance claims processing (one plug-in per jurisdiction’s rules) and tax preparation software (the 1040 form is the core, each supporting form and worksheet is a plug-in).
Described as “a hybrid of the microservices architecture style… one of the most pragmatic architecture styles”. It is a distributed macro layered structure with three pieces: a separately deployed user interface, separately deployed remote coarse-grained domain services, and a monolithic, centrally shared database.
Services deploy like any monolith (EAR, WAR, assembly) and need no containerisation. Because they share one database, the number of services within an application context generally ranges between 4 and 12, averaging about 7. Usually there is a single instance per domain service, with more only for scale, failover or throughput.
common_entities_lib locked in version control under database-team control.Ratings stated in prose: “service-based architecture does not contain any five-star ratings” — agility 4, testability 4, deployability 4, fault tolerance/availability 4, scalability 3, elasticity 2. Partitioning: domain. Quanta: at least one — 4 to 12 services sharing one database and one UI is a single quantum; federating the UI and the database yields more.
When to use: when you want architectural modularity, agility and deployability without the cost, complexity and granularity pitfalls of microservices; a natural fit for domain-driven design; and the best distributed choice when you must preserve ACID transactions. When not: when you need fine-grained independent scaling and elasticity, or when domain services would have to call each other frequently.
A popular distributed asynchronous style made of decoupled event processing components that asynchronously receive and process events. It can be used standalone or embedded in another style. It is contrasted with the request-based model (a request orchestrator dispatching to request processors, deterministic and synchronous) — the event-based model instead reacts to a situation.
No central mediator: message flow is distributed across event processors “in a chain-like broadcasting fashion” through a lightweight broker such as RabbitMQ, ActiveMQ or HornetQ. Four components: initiating event, event broker, event processor, processing event. Each processor does its task and then advertises what it did as a new processing event. Brokers are usually federated — domain-based clustered instances — and use topics with publish-and-subscribe. The book’s analogy: a relay race.
Advantages: highly decoupled event processors, high scalability, high responsiveness, high performance, high fault tolerance. Disadvantages: workflow control, error handling, recoverability, restart capabilities, data inconsistency.
Components: initiating event, event queue, event mediator, event channels, event processors. The initiating event goes to an initiating event queue; the mediator knows the workflow steps and generates processing events onto dedicated event channels — usually queues, point-to-point. Processors do not advertise to the rest of the system: they report back to the mediator. Usually there are multiple mediators, one per domain.
Implementation tiers: simple mediators (Apache Camel, Mule ESB, Spring Integration) → BPEL engines (Apache ODE, Oracle BPEL Process Manager) for complex conditional workflows → BPM engines (jBPM) for long-running workflows with human intervention. The recommended mediator delegation model classifies events as simple, hard or complex and routes every event through a simple mediator that forwards on.
A precise terminological point: occurrences in the mediator topology are commands (must be processed); occurrences in the broker topology are events (can be ignored).
Advantages: workflow control, error handling, recoverability, restart capabilities, better data consistency. Disadvantages: more coupling of event processors, lower scalability, lower performance, lower fault tolerance, modelling complex workflows.
Ratings stated in prose: performance 5, scalability 5, fault tolerance 5, evolutionary 5 stars; simplicity and testability rate relatively low. Partitioning: primarily technical — a domain is spread across many processors, mediators, queues and topics. Quanta: one to many — processors sharing a single database instance fall in the same quantum, and request-reply ties processors into the same quantum even though communication is asynchronous.
When to use: choose the event-based model for flexible, action-based events requiring high responsiveness and scale with complex dynamic user processing; choose request-based for well-structured, data-driven requests where certainty and control over the workflow are needed. Within EDA: broker for high responsiveness and simple flows, mediator when you need workflow control, error handling and recoverability.
Named after tuple space — multiple parallel processors communicating through shared memory. It removes the central database as a synchronous constraint and instead uses replicated in-memory data grids: application data is held in memory and replicated among all active processing units, with updates sent asynchronously to the database, usually via messaging with persistent queues. Processing units start and stop dynamically with load.
The motivation is the “triangle-shaped” scalability limit of the classic web → app → database topology, where the database is the final limiting factor.
Readers and writers together form a data abstraction layer — processing units decoupled from the database schema via separate contracts, with transformation logic in the readers and writers — rather than a data access layer, which would be coupled to the underlying structures.
Replicated vs distributed caching: a replicated cache optimises performance, suits a small cache (<100 MB) of relatively static data with a low update frequency, and gives high fault tolerance; a distributed cache optimises consistency, suits a large cache (>500 MB) of highly dynamic data with a high update rate, and has low fault tolerance. Replicated caching is the standard model here and has no single point of failure. The hybrid near-cache — distributed cache as full backing cache, each in-memory grid as front cache, with an eviction policy (MRU, MFU or random replacement) — is explicitly not recommended for this style, because front caches sync to the backing cache but not to each other. Note also the risk of data collisions in active/active replicated caching, due to replication latency.
Ratings stated in prose: elasticity 5, scalability 5, performance 5, testability 1 star. It is called “a very complicated architecture style” and “relatively expensive”. Partitioning: both domain and technical. Quanta: vary, delineated by the association between user interfaces and processing units — and note that the database is not part of the quantum equation, because processing units never talk to it synchronously.
When to use: applications experiencing high spikes in user or request volume, and applications with throughput in excess of 10,000 concurrent users. Canonical examples: online concert ticketing systems, where volume jumps from hundreds to tens of thousands the moment tickets go on sale, and online auction systems, where one processing unit per auction ensures bidding consistency. When not: when you cannot tolerate the complexity, the eventual consistency of the system of record, the cost, or the near-impossibility of realistic load testing outside production.
A distributed architecture from the late 1990s enterprise era, driven by scarce and expensive compute, per-machine OS licences and Byzantine database licensing — so enterprise-level reuse became the dominant philosophy. It establishes a taxonomy of services, each layer with a specific responsibility, all wired through a central orchestration engine or service bus.
| Service type | Responsibility |
|---|---|
| Business services | The entry point at the top: domain behaviour such as ExecuteTrade or PlaceOrder. Litmus test: can you answer “are we in the business of…?” affirmatively. These definitions contain no code — just input, output, sometimes schema; they are defined by business users |
| Enterprise services | Fine-grained, shared implementations built by developers: CreateCustomer, CalculateQuote. The reusable building blocks composing the coarse-grained business services |
| Application services | One-off, single-implementation services not needing reuse — e.g. geolocation for one application; owned by a single application team |
| Infrastructure services | Operational concerns: monitoring, logging, authentication, authorization; owned by a shared infrastructure team |
| Orchestration engine | “The heart of this distributed architecture”: stitches business services together, handles transactional coordination and message transformation declaratively, defines service relationships and transaction boundaries, and acts as an integration hub. Tied to a single or few relational databases, not database-per-service |
All requests go through the engine, even internal calls: the service bus is the intermediary for everything.
The reuse trap. Aggressive reuse — extracting a canonical Customer service across insurance divisions — produced huge coupling: a change to Customer rippled to all consumers, forcing coordinated deployments and holistic testing. Second, the single canonical Customer had to carry every attribute any division needed. Third and most damaging, extreme technical partitioning meant a domain concept such as CatalogCheckout was “spread so thinly throughout this architecture that it was virtually ground to dust”: adding one address line could touch dozens of services across several tiers, plus a schema change.
Ratings: no numeric values survive in the extracted text. In prose: deployability and testability “score disastrously”; elasticity and scalability were supported despite the difficulties; performance “was never a highlight… and was extremely poor because each business request was split across so much of the architecture”; simplicity and cost “have the inverse relationship most architects would prefer”. Partitioning: perhaps the most technically partitioned general-purpose architecture ever attempted. Quantum: a single quantum despite being distributed, because of the one-or-few databases and because the orchestration engine is a giant coupling point — no part of the architecture can have different architecture characteristics than the mediator that orchestrates all behaviour.
The book’s verdict: this architecture “manages to find the disadvantages of both monolithic and distributed architectures”. It is presented historically, as an important milestone because it taught architects how difficult distributed transactions can be in the real world, and the practical limits of technical partitioning. The backlash against its disadvantages led to more modern architectures such as microservices.
Named early and popularised by Martin Fowler and James Lewis in their March 2014 blog post. Each service runs in its own process — physical machine, then VM, then container — and architects expect each service to include all necessary parts to operate independently, including databases and other dependent components. Service size is much smaller than in orchestration-driven SOA. An optional API layer sits between consumers and services. The whole style is the physical embodiment of the bounded context.
Ratings: no explicit star numbers survive. In prose: the high points are scalability, elasticity and evolutionary; high support for automated deployment and testability; fault tolerance gets a high rating under normal circumstances thanks to independent single-purpose services, though fault tolerance and reliability are impacted when too much interservice communication is used; performance is often an issue, because of network calls plus per-endpoint security checks. Partitioning: decidedly domain-centred. Quanta: the most distinct quanta of any modern architecture — in many ways it exemplifies what the quantum measure evaluates.
When to use: where decoupling, independent evolution, scalability and elasticity dominate, and where DevOps automation exists — “microservices could not exist without the DevOps revolution”. When not: where performance is paramount (many network hops), where you cannot avoid cross-service transactions, or where you would be forced to make services so small that you rebuild the communication links between them.
Distributed architecture styles are more powerful in terms of performance, scalability and availability than monolithic ones, but they introduce several issues that must be considered. A fallacy is something that is believed or assumed to be true but is not. The eight fallacies of distributed computing were first coined by L. Peter Deutsch and colleagues at Sun Microsystems in 1994 — and they apply to distributed architectures today.
| # | Fallacy | Reality, and what to do |
|---|---|---|
| 1 | The network is reliable | Networks have become more reliable over time but remain generally unreliable. All distributed styles rely on the network both to and from services and between services. Service B may be totally healthy but Service A cannot reach it; or A made a request and does not receive a response because of a network issue. The more a system relies on the network — such as microservices — the potentially less reliable it becomes. Tactics: timeouts, circuit breakers between services. |
| 2 | Latency is zero | When a local call is made via a method or function call, that time (t_local) is measured in nanoseconds or microseconds. When the same call is made through a remote access protocol — REST, messaging, RPC — the time (t_remote) is measured in milliseconds. t_remote will always be greater than t_local. |
| 3 | Bandwidth is infinite | Bandwidth is usually not a concern in monolithic architectures, where little or none is required to process a business request. Once systems are broken into services, communication to and between them significantly utilises bandwidth, causing networks to slow down and thus impacting latency (#2) and reliability (#1). |
| 4 | The network is secure | Architects and developers get so comfortable with VPNs, trusted networks and firewalls that they forget the network is not secure. Each and every endpoint to each distributed deployment unit must be secured so that unknown or bad requests do not reach the service. The surface area for threats and attacks increases by magnitudes when moving from monolithic to distributed — and having to secure every endpoint, even for interservice communication, is another reason performance tends to be slower in synchronous, highly distributed architectures. |
| 5 | The topology never changes | The overall network topology — routers, hubs, switches, firewalls, networks, appliances — changes all the time. Impact on latency and other aspects: it changes assumptions and invalidates tactics such as timeouts and circuit breakers. |
| 6 | There is only one administrator | There are dozens of network administrators in a typical large company. Who should the architect talk to about latency (#2) or topology changes (#5)? This fallacy points to the complexity of distributed architecture and the amount of coordination needed to get everything working correctly. Monolithic applications do not require this level of communication and collaboration. |
| 7 | Transport cost is zero | Transport cost here means actual cost in money associated with making a “simple RESTful call”. Distributed architectures cost significantly more than monolithic ones, primarily due to increased needs for additional hardware, servers, gateways, firewalls, new subnets and proxies. Architects must analyse the current server and network topology with regard to capacity, bandwidth, latency and security zones. |
| 8 | The network is homogeneous | Most companies have multiple network hardware vendors, and not all of those heterogeneous vendors play together well. Networking standards have evolved, making this less of an issue, but not all situations, loads and circumstances have been fully tested, and network packets occasionally get lost — which ties back into #1, #2 and #3, forming an endless loop of confusion and frustration that is unavoidable when using distributed architectures. |
When using any distributed architecture, architects must know the latency average to determine whether the architecture is feasible — especially for architectures with a fine-grained nature such as microservices, which imply more communication.
Two facts to internalise:
Three more concerns that the module raises alongside the fallacies, all of which return as full patterns in Part III.
| Concern | The problem |
|---|---|
| Distributed logging | Performing root-cause analysis to determine why a particular order was dropped is very difficult and time-consuming, due to the distribution of application and system logs. In a monolithic application there is typically only one log, making it easier to trace a request; distributed architectures contain dozens to hundreds of different logs, all located in a different place and all with a different format. → Chapter 13: log aggregation and distributed tracing. |
| Distributed transactions | Architects take transactions for granted in a monolithic world: standard commits and rollbacks executed from persistence frameworks leverage ACID transactions — atomicity, consistency, isolation, durability — to guarantee that data is updated correctly. Not so in distributed architectures, which rely on eventual consistency: the data processed by separate deployment units is, at some unspecified point in time, all synchronised into a consistent state. The main trade-off of distributed architecture: high scalability, performance and availability at the sacrifice of data consistency and data integrity. → Chapter 9: sagas. |
| Contract maintenance and versioning | A contract is behaviour and data that is agreed upon by both the client and the service. Contract maintenance is particularly difficult in distributed architectures, primarily due to decoupled services and systems owned by different teams and departments. Even more complex are the communication models needed for version deprecation. → Chapter 10: semantic versioning, backward-compatible changes, the robustness principle. |
If you are asked for “the main trade-off of distributed architectures”, the sentence the course wants is: high scalability, performance and availability at the sacrifice of data consistency and data integrity. Everything in Part III — sagas, CQRS, event sourcing, eventual consistency, the single writer principle — is an engineering response to that one sacrifice.
“An architectural style defines a family of such systems in terms of a pattern of structural organization. More specifically, an architectural style determines the vocabulary of components and connectors that can be used in instances of that style, together with a set of constraints on how they can be combined.” Note that styles are technology and domain agnostic — unlike reference architectures, which provide a structure for applications in a specific domain and may embody different styles.
An essential part of an architecture pattern is its focus on the problem and the context, as well as how to solve the problem in that context. An architectural style focuses on the architecture approach, with more lightweight guidance on when a particular style may or may not be useful. A pattern counts as architectural when its use directly and substantially influences the satisfaction of some architectural driver.
The way architects refer to the absence of any discernible architectural structure; an anti-pattern named by Foote and Yoder in 1997 — a haphazardly structured, sprawling, sloppy, spaghetti-code jungle where information is shared promiscuously among distant elements. The consequences are that change becomes increasingly difficult and that deployability, testability, scalability and performance all suffer. The cause: lack of governance around code quality and structure — which is why fitness functions exist.
Monolithic (single deployment unit of all code): layered, pipeline, microkernel. Distributed (multiple deployment units connected through remote access protocols): service-based, event-driven, space-based, service-oriented (orchestration-driven), microservices.
A closed layer may not be skipped by a request; an open layer may be bypassed. Layers of isolation is the property that changes in one layer do not affect the others provided contracts hold — it requires the layers in the main request flow to be closed, and it is what lets you swap JSF for React without touching the rest. The architecture sinkhole anti-pattern occurs when requests pass through layers where no business logic is applied; the remedy is the 80-20 rule — around 20% sinkholes is acceptable, 80% means the style is wrong for the problem.
Producer — the starting point, outbound only, also called the source. Transformer — accepts input, optionally transforms data and forwards it, like functional map. Tester — accepts input, tests criteria and optionally produces output, likened to reduce. Consumer — the termination point, persisting or displaying results. Filters are self-contained, independent, generally stateless and perform one task only; pipes are unidirectional and point-to-point, never broadcast.
The core holds the minimal functionality required to run the system — the happy path, the general processing flow with little or no custom processing; moving cyclomatic complexity out of the core into plug-ins buys extensibility, maintainability and testability. Plug-ins are standalone, independent components with ideally no dependencies on each other, isolating highly volatile code. The registry is the core’s record of available plug-ins — name, data contract, remote access protocol details — implemented as an internal map or via an external tool such as ZooKeeper or Consul.
Because the services are coarse-grained domain services sharing a single monolithic database — that constraint puts the count generally between 4 and 12, averaging about 7. Thanks to that coarse granularity it preserves ACID transactions better than any other distributed architecture: commit and rollback remain inside a single service.
Broker: no central mediator; message flow is distributed across event processors in a chain-like broadcasting fashion through a lightweight broker using topics and publish/subscribe; each processor advertises what it did as a new processing event. High decoupling, scalability, responsiveness, performance and fault tolerance, at the cost of workflow control, error handling, recoverability, restart and data consistency. Mediator: an event mediator knows the workflow steps and dispatches processing events onto dedicated channels, usually point-to-point queues; processors report back to the mediator rather than advertising. Better workflow control, error handling, recoverability, restart and consistency, at the cost of more coupling, lower scalability, performance and fault tolerance. Note also: mediator occurrences are commands, broker occurrences are events.
It removes the central database as a synchronous constraint, because the database is the final limiting factor in the classic web-app-database topology. Components: processing units (application logic plus in-memory data grid and replication engine); virtualized middleware (messaging grid, data grid, optional processing grid, deployment manager); data pumps (always asynchronous); data writers; data readers. Readers and writers together form a data abstraction layer.
For two reasons: it uses one or a few shared databases rather than database-per-service, and the orchestration engine is a giant coupling point — no part of the architecture can have different architecture characteristics than the mediator that orchestrates all behaviour. The book’s verdict is that it “manages to find the disadvantages of both monolithic and distributed architectures”, though it was an important milestone for teaching the real difficulty of distributed transactions and the limits of technical partitioning.
Purpose (extreme functional cohesion — one significant behaviour), transactions (entities that must cooperate in a transaction often mark a boundary), choreography (if services need extensive communication, bundle them back into a larger service). On transactions: “do not do transactions in microservices — fix granularity instead”, because cross-service transactions create connascence of value, the worst kind of dynamic connascence. Where unavoidable, use the saga pattern with compensating transactions — sparingly.
(1) The network is reliable. (2) Latency is zero. (3) Bandwidth is infinite. (4) The network is secure. (5) The topology never changes. (6) There is only one administrator. (7) Transport cost is zero. (8) The network is homogeneous. Coined by L. Peter Deutsch and colleagues at Sun Microsystems in 1994, and they apply to distributed architectures today.
Because of chaining and of the long tail. Chaining: at an average of 100 ms of latency per request, chaining 10 service calls for one business function adds 1,000 ms. Long tail: an average latency of 60 ms may hide a 95th percentile of 400 ms, and it is usually that long-tail latency that kills performance in a distributed architecture. Architects should know the 95th to 99th percentile, not just the mean.
High scalability, performance and availability at the sacrifice of data consistency and data integrity. Where a monolith gets ACID transactions from its persistence framework, distributed architectures rely on eventual consistency: the data processed by separate deployment units is at some unspecified point in time synchronised into a consistent state.