# Software Architecture Metapatterns Namespace Instructions This is a compiled namespace source under `pixi-vault/wikis/software-architecture-metapatterns/`. ## Rules - Follow the root `Wiki Compiler Maps/Namespace Wiki Compiler Map.md`. - Preserve source provenance and license ambiguity from `denyspoltorak/metapatterns`. - Treat imported pages as architecture reference material, not as commercial-clean training data. - Keep this namespace scoped to software architecture metapatterns and architecture-reasoning guidance. - Do not prescribe architectures by trend; retrieve the relevant source pages and compare forces first. - Update `wiki/index.md` and `wiki/log.md` whenever compiled pages are added or the import is refreshed. --- title: "Software Architecture Metapatterns" created: 2026-07-02 updated: 2026-07-02 type: namespace-overview status: active category: knowledge-systems namespace: software-architecture-metapatterns confidence: medium source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_license_note: "Repository root is CC BY-NC-SA 4.0; book/source directory and wiki indicate CC BY 4.0. Preserve attribution and no-endorsement guardrails." --- # Software Architecture Metapatterns > Software-architecture pattern-language reference for agents, compiled from Denys Poltorak's *Architectural Metapatterns: The Pattern Language of Software Architecture* GitHub wiki. ## Scope ### Covers The `software-architecture-metapatterns` namespace covers software architecture metapatterns: system topologies, layers, services, pipelines, proxies, orchestrators, plugins, hexagonal architecture, service-oriented architecture, polyglot persistence, pattern aliases, architectural forces, dependencies, performance tradeoffs, and architecture evolution paths. ### Not Covered This namespace does not cover every design pattern or programming idiom, prescribe architecture by fashion, replace project-specific code review, or establish commercial-clean training-data clearance. It is a reference and reasoning aid for architecture review, migration planning, and agent retrieval. ### Current As 2026-07-02 — Initial namespace release imports the public GitHub wiki Markdown corpus, preserves source URLs/provenance, adds an agent retrieval contract, and classifies the namespace under Knowledge Systems because it organizes software architecture knowledge into a navigable pattern language. ## Canonical Source Roots - Source repository: `https://github.com/denyspoltorak/metapatterns` - Source wiki: `https://github.com/denyspoltorak/metapatterns/wiki` - Source website: `https://metapatterns.io/` - Latest release inspected: `https://github.com/denyspoltorak/metapatterns/releases/tag/v1.2` ## Provenance and License Guardrail The repository root license is **Creative Commons Attribution-NonCommercial-ShareAlike 4.0**. The book/source directory and wiki copyright page indicate **Creative Commons Attribution 4.0**. This namespace keeps the ambiguity visible and follows a conservative public-use stance: preserve attribution, source links, license notes, and no-endorsement boundaries. Use this corpus for reference, critique, education, architecture review, and non-misleading agent retrieval. Do not present it as unrestricted commercial training data or as Jamie/Pixi-authored source material. ## Agent Use Contract - Start with [[summaries/for-agents-software-architecture-retrieval|For Agents — Software Architecture Retrieval]]. - Retrieve a bounded set of 3–8 relevant metapattern/source pages before recommending an architecture. - Compare patterns by structure, dependency direction, communication style, performance, team ownership, and likely evolution path. - Cite metapattern names and source pages in the recommendation so a human can inspect the reasoning. - Prefer "what forces are present?" over "what architecture is trendy?" Tiny systems do not need distributed cosplay. ## Public Output Contract When published to `pixi-wiki`, this namespace should expose: ```text /raw/software-architecture-metapatterns/README.md /raw/software-architecture-metapatterns/wiki/index.md /wiki/software-architecture-metapatterns/README.md.html /wiki/software-architecture-metapatterns/wiki/index.md.html /wiki/software-architecture-metapatterns/llms.txt ``` --- title: "Ambiguous patterns" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Analytics/Ambiguous patterns.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Analytics/Ambiguous%20patterns source_license_note: "See namespace README; preserve attribution and source links." --- # Ambiguous patterns > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Analytics/Ambiguous patterns.md`. We’ve seen a single pattern come under many names, as happens with [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]], and also one name used for multiple [[wiki/concepts/source/introduction/system-topologies|topologies]], as with [[wiki/concepts/source/basic-metapatterns/services|*Services*]], which may [[wiki/concepts/source/foundations-of-software-architecture/orchestration|orchestrate each other]], make a [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]], or be components of a [*Service-Oriented Architecture*]() (*SOA*). On top of that, there are several pattern names which are often believed to be unambiguous while each of them sees conflicting definitions in books or over the web, thanks to [*Semantic Diffusion*](https://martinfowler.com/bliki/SemanticDiffusion.html) or independent coining of the term by multiple authors. Let’s explore the last kind, which is the most dangerous both for your understanding of other people and for your time wasted in arguments. ### Monolith ![Diagrams of a Monolith as a single component, a co-deployed system, a synchronous distributed system, a Layered Architecture, and modules with a shared database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Ambiguous-Monolith.png) The old books, namely \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] and \[[wiki/concepts/source/appendices/books-referenced|[POSA1]]\], described a *tightly coupled* [[wiki/concepts/source/basic-metapatterns/monolith|*unstructured* system]], where everything depends on everything, as *monolithic*, which matched the meaning of the word in Latin – “single stone”. Then something evil happened – I believe that the proponents of [*SOA*](), backed by the hype and money which they had earned from corporations, started labeling any *non-distributed* system as *monolithic*, obviously to contrast the negative connotation of that word to their own most progressive design. It took only a decade for the karma to strike back – when the new generation behind [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]] redefined *monolithic* as a *single unit of deployment* – to call the now obsolescent *SOA* systems [*Distributed Monoliths*]() \[[wiki/concepts/source/appendices/books-referenced|[MP]]\] because their services used to grow so coupled that they had to be deployed together. The novel misnomers, [[wiki/concepts/source/basic-metapatterns/layers|*Layered Monolith*]] \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] and [[wiki/concepts/source/basic-metapatterns/services|*Modular Monolith*]] \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\], which denote an application partitioned by abstractness or subdomain, respectively, add to the confusion. ### Reactor ![Control flow diagrams for Reactor, Proactor, and Half-Sync/Half-Async.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Subtypes%20of%20Monolith.png) People [tend to call](http://ithare.com/category/reactors/) any event-driven service a *Reactor*. In fact, there are three patterns that describe different threading models for an event-handling system: - A [[wiki/concepts/source/basic-metapatterns/monolith|*Reactor*]] \[[wiki/concepts/source/appendices/books-referenced|[POSA2]]\] runs each task in a dedicated thread and blocks it on any calls outside of the component. That allows for normal *imperative programming* but is resource-consuming and not very responsive or flexible. - A [[wiki/concepts/source/basic-metapatterns/monolith|*Proactor*]] \[[wiki/concepts/source/appendices/books-referenced|[POSA2]]\] relies on a single thread to serve all the system’s tasks in an interleaved manner, just like an OS uses a single CPU core to run multiple processes. The resulting non-blocking code is fragmented (thus known as *callback hell*) but it can address any incoming event immediately. This suits [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|real-time control systems]]. - [[wiki/concepts/source/basic-metapatterns/monolith|*Half-Sync/Half-Async*]] \[[wiki/concepts/source/appendices/books-referenced|[POSA2]]\] is what we know better as *coroutines* or *fibers* – there are multiple *Reactor*-like lightweight threads that block on a *Proactor*-like engine which translates between synchronous calls from the user code and asynchronous system events. In most cases we’ll hear of *Proactor* being called *Reactor* – probably because *Reactor* was historically the first and the simplest of the three patterns, and it is similar in name to *reactive programming* found in *Proactor*. ### Microkernel ![Diagrams of Microkernel according to Pattern-Oriented Software Architecture and of Plugins Architecture](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Ambiguous-Microkernel.png) *Microkernel* is another notable case. The confusion over it goes all the way back to \[[wiki/concepts/source/appendices/books-referenced|[POSA1]]\] which used [[wiki/concepts/source/implementation-metapatterns/microkernel|operating systems]] as examples of [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugin Architecture*]]. I believe that it was a mismatch: - An operating system is mainly about sharing the resources of producers among consumers, where both the producers and consumers may be written by external teams. The *kernel* itself does not feature much logic – its role is to connect the other components together. - *Plugins*, on the other hand, extend or modify the business logic of the *core* – which is the sole reason for the system to exist and is in no way “*micro-*” as it got the bulk of the system’s code. In many such systems *plugins* are utterly optional – which cannot be said of *OS drivers*. Thus, here we have two architectural patterns of arguably similar structure ([[wiki/concepts/source/implementation-metapatterns/plugins|*Microkernel/Plugins*]] of \[[wiki/concepts/source/appendices/books-referenced|[SAP]], [[wiki/concepts/source/appendices/books-referenced|FSA]]\] omit 3 of 5 components of the original [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]] of \[[wiki/concepts/source/appendices/books-referenced|[POSA1]], [[wiki/concepts/source/appendices/books-referenced|POSA4]]\]) but very different intent and action known under the same name. ### Domain Services ![Diagrams of domain services according to Domain-Driven Design and Fundamentals of Software Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Ambiguous-DomainServices.png) I was told that [[wiki/concepts/source/basic-metapatterns/services|*Domain Services*]] of \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] are an incorrect term – because a *domain service* is always limited to the [[wiki/concepts/source/basic-metapatterns/layers|*domain* layer]] of \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\] while those of \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] also cover the [[wiki/concepts/source/basic-metapatterns/layers|*application*]] and, maybe, *infrastructure*. I believe that both definitions are technically correct, if the difference in the meaning of *domain* is accounted for. In \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] *domain* is almost synonymous with a *bounded context* of \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\], while \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\] more often uses that word for the name of its [[wiki/concepts/source/basic-metapatterns/layers|middle layer]] which contains *business rules*. ### Service-Based Architecture ![Diagrams of Service-Based Architecture, Microservices, and Service-Oriented Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Ambiguous-ServiceBasedArchitecture.png) \[[wiki/concepts/source/appendices/books-referenced|[DEDS]]\] calls anything made of services a *service-based architecture*. \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] differentiates [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]] and [*Service-Oriented Architecture*](), leaving whatever remains (large [[wiki/concepts/source/basic-metapatterns/services|subdomain-scale services]]) under the name of [[wiki/concepts/source/basic-metapatterns/services|*Service-Based Architecture*]]. Both definitions are technically correct. One is wider than the other. ### Front Controller ![Diagrams of Front Controller according to Patterns of Enterprise Application Architecture and Software Architecture: the Hard Parts.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Ambiguous-FrontController.png) \[[wiki/concepts/source/appendices/books-referenced|[PEAA]] and [[wiki/concepts/source/appendices/books-referenced|POSA4]]\] define [*Front Controller*](https://learn.microsoft.com/en-us/previous-versions/msp-n-p/ff648617(v=pandp.10)?redirectedfrom=MSDN) as an [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*MVC*]] (or, more likely, a [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*MVP*]]) derivative for backend programming. In this pattern multiple web pages share a request processing component which turns the incoming requests into commands and forwards them to appropriate page classes. The definition from \[[wiki/concepts/source/appendices/books-referenced|[SAHP]]\] is much more interesting – it describes an [[wiki/concepts/source/basic-metapatterns/pipeline|*Choreographed Event-Driven Architecture*]] with a [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Query Service*]] embedded in the first (client-facing) service. The [[wiki/concepts/source/extension-metapatterns/orchestrator|*Front Controller*]] subscribes to notifications from downstream services to know the status of every request which it has passed to the [[wiki/concepts/source/basic-metapatterns/pipeline|*pipeline*]]. ### Cells ![Diagrams of WSO2 Cells and Amazon Cells.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Ambiguous-Cells.png) The fresh *Cell-Based Architecture* also has multiple definitions. - [WSO2 defined](https://github.com/wso2/reference-architecture/blob/master/reference-architecture-cell-based.md) [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cell*]] as a group of services which is encapsulated from the remaining system by a [[wiki/concepts/source/extension-metapatterns/proxy|*Gateway*]] (for incoming traffic) and sometimes [[wiki/concepts/source/extension-metapatterns/proxy|*Adapters*]] (for outgoing traffic) and often uses a dedicated [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] – causing each *Cell*, though internally distributed, to be treated by other components as a single service. This makes designing and managing a large system much simpler by introducing a [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*hierarchy*]]. - Amazon [promotes](https://docs.aws.amazon.com/wellarchitected/latest/reducing-scope-of-impact-with-cell-based-architecture/what-is-a-cell-based-architecture.html) its [[wiki/concepts/source/basic-metapatterns/shards|*Cells*]] as [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]] of the whole system which run in multiple geographic regions. That grants fault tolerance and improves performance as each client has an instance of the system deployed to a nearby datacenter, but it does not have much impact on the organization and complexity of the code. This case looks like Amazon’s hijacking and redefining a popular emerging technology, though I may be wrong about that as I did not investigate the history of the term. ### Nanoservices ![Diagrams of Nanoservices as an API layer over a shared database, a pipeline, a Space-Based Architecture, actors, and a Service-Based Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Ambiguous-Nanoservices.png) The *Nanoservices* pattern is another emerging technology and it seems to have never been strictly defined. Most sources agree that a [[wiki/concepts/source/basic-metapatterns/services|*nanoservice*]] is a cloud-based function ([*FaaS*](https://en.wikipedia.org/wiki/Function_as_a_service)), similar to a *service* with a single API method but, just as with the old good [[wiki/concepts/source/basic-metapatterns/services|*services*]], they differ in the ways to use the novel technology: - Diego Zanon in *Building Serverless Web Applications* proposes a [[wiki/concepts/source/basic-metapatterns/services|single layer of nanoservices]], each implementing a method of the system’s public API, to be used as a thin backend. - [Here](https://increment.com/software-architecture/the-rise-of-nanoservices/) we have nanoservices [[wiki/concepts/source/basic-metapatterns/pipeline|comprising]] a [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]], similar to [[wiki/concepts/source/basic-metapatterns/pipeline|*Choreographed Event-Driven Architecture*]]. - [Another article](https://medium.com/@ido.vapner/unlocking-the-power-of-nano-services-a-new-era-in-microservices-architecture-22647ea36f22) proposes to [(re)use them]() in [*SOA*]() style. Moreover, there are a couple of sources that call a nanoservice something totally different: - [There is a concept](https://nanoservices.io/docs/docs/building/introduction/) of nanoservice as a module that can run both as a separate service and as a part of a binary – allowing for a team to choose if they want their system to run as a single process or become distributed. *Nano-* is because an in-process module is more lightweight than a [[wiki/concepts/source/basic-metapatterns/services|*microservice*]]. This idea resembles [[wiki/concepts/source/basic-metapatterns/services|*Modular Monolith*]] and [[wiki/concepts/source/basic-metapatterns/services|*Actor Frameworks*]]. - And [here](https://dev.to/siy/nanoservices-or-alternative-to-monoliths-and-microservices-12bb) we have something akin to [[wiki/concepts/source/implementation-metapatterns/mesh|*Space-Based Architecture*]] but it is also called *Nanoservices* – as the proposed framework makes new components so easy to create that programmers tend to write many smaller *nanoservices* instead of a single [[wiki/concepts/source/basic-metapatterns/services|*microservice*]]. In my opinion, the disarray happened because the notion of *making smaller microservices* got hyped but was never adopted widely enough to become an industry standard, therefore everybody follows their own vision about what “smaller” means. ### Summary Several names of architectural patterns cause confusion as the meaning of each of them changes from source to source. This book aims at identifying such issues and building a cohesive understanding of software and systems architecture, similar to the *ubiquitous language* of \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]. --- title: "Analytics" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Analytics/Analytics.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Analytics/Analytics source_license_note: "See namespace README; preserve attribution and source links." --- # Analytics > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Analytics/Analytics.md`. This part is dedicated to analyzing the architectural [[wiki/concepts/source/introduction/metapatterns|metapatterns]], for if this book’s taxonomy of patterns is a step forward from the state of the art, it should bear fruits for us to pick. I had no time to research every idea collected while the book was being written and its individual chapters published for feedback. A few of those pending topics, which may make additional chapters in the future, are listed below: - Some architectural patterns (*CQRS*, *Cache*, *Microservices*, etc.) appear under multiple metapatterns. Each individual case makes a story of its own, teaching about both the needs of software systems and uses of metapatterns. - There are different ways to split a component into subcomponents: [*Layers of Services*]() differ from [[wiki/concepts/source/fragmented-metapatterns/layered-services|*Layered Services*]]. This should be investigated. - An architectural quality may depend on the structure of the system. For example, a client request may be split into three subrequests to be processed sequentially or in parallel, depending on the [[wiki/concepts/source/introduction/system-topologies|topology]] (e.g. [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]], [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrated Services*]] or [[wiki/concepts/source/basic-metapatterns/shards|*Replicas*]]), thus it is topology which defines latency. We can even draw formulas like: - L = L1 \+ L2 \+ L3 for *Pipeline*, - L = MAX(L1, L2, L3) for *Orchestrated Services* which run in parallel, - L = MIN(L1, L2, L3) for *Replicas* with [*Request Hedging*](https://grpc.io/docs/guides/request-hedging/). Other smaller topics that I was able to look into made the following chapters: - [[wiki/concepts/source/analytics/comparison-of-architectural-patterns|Comparison]] of the ways different architectures [[wiki/concepts/source/analytics/sharing-functionality-or-data-among-services|share functionality or data]], [[wiki/concepts/source/analytics/pipelines-in-architectural-patterns|build pipelines]], and use [[wiki/concepts/source/analytics/dependency-inversion-in-architectural-patterns|dependency inversion]] and [[wiki/concepts/source/analytics/indirection-in-commands-and-queries|indirection]]. - [[wiki/concepts/source/analytics/ambiguous-patterns|Ambiguous patterns]] whose meaning differs from author to author. - [[wiki/concepts/source/analytics/architecture-and-product-life-cycle|Evolution of the software architecture]] during a product’s life cycle. - [[wiki/concepts/source/analytics/real-world-inspirations-for-architectural-patterns|Real-world examples]] that inspire metapatterns. - [[wiki/concepts/source/analytics/the-heart-of-software-architecture|Cohesion and decoupling]] as the main architectural drivers: their [[wiki/concepts/source/analytics/cohesers-and-decouplers|influence on forces]], [[wiki/concepts/source/analytics/deconstructing-patterns|interplay in patterns]] and, finally, a short guide on [[wiki/concepts/source/analytics/choose-your-own-architecture|choosing an architecture]] suitable for your project. ## Contents: - [[wiki/concepts/source/analytics/comparison-of-architectural-patterns|Comparison of architectural patterns]] - [[wiki/concepts/source/analytics/sharing-functionality-or-data-among-services|Sharing functionality or data among services]] - [[wiki/concepts/source/analytics/pipelines-in-architectural-patterns|Pipelines in architectural patterns]] - [[wiki/concepts/source/analytics/dependency-inversion-in-architectural-patterns|Dependency inversion in architectural patterns]] - [[wiki/concepts/source/analytics/indirection-in-commands-and-queries|Indirection in commands and queries]] - [[wiki/concepts/source/analytics/ambiguous-patterns|Ambiguous patterns]] - [[wiki/concepts/source/analytics/architecture-and-product-life-cycle|Architecture and product life cycle]] - [[wiki/concepts/source/analytics/real-world-inspirations-for-architectural-patterns|Real-world inspirations for architectural patterns]] - [[wiki/concepts/source/analytics/the-heart-of-software-architecture|The heart of software architecture]] - [[wiki/concepts/source/analytics/cohesers-and-decouplers|Cohesers and decouplers]] - [[wiki/concepts/source/analytics/deconstructing-patterns|Deconstructing patterns]] - [[wiki/concepts/source/analytics/choose-your-own-architecture|Choose your own architecture]] --- title: "Architecture and product life cycle" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Analytics/Architecture and product life cycle.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Analytics/Architecture%20and%20product%20life%20cycle source_license_note: "See namespace README; preserve attribution and source links." --- # Architecture and product life cycle > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Analytics/Architecture and product life cycle.md`. In my practice, a product’s architecture changes over its lifetime. For a R&D, when there is nobody with relevant experience on the team, it starts small, gradually gains flexibility through fragmentation, grows and restructures itself according to the ever-changing domain knowledge and business requirements, then it solidifies as the project matures, and dies because of performance optimizations and loss of experience as the seasoned programmers leave. In more mundane projects the first stages may be omitted, as little research needs to be done, and oftentimes a project is canceled way before its architecture succumbs under its own weight. Anyway, let’s observe the full life cycle. ### Infancy (proof of concept) – Monolith ![A diagram of a monolith.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Lifecycle-1.png) A project in an unknown domain starts humble and small, likely as a proof of concept. You need to write quickly to check your ideas about how the domain works without investing much time – as you may oftentimes be wrong here or there, making you rethink and rewrite. ### Childhood (prototype) – Layers ![Diagrams of Layers and Hexagonal Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Lifecycle-2.png) When you have the thing working, you may start reflecting on the rules and the code which you wrote. What belongs where, what can be subject to change, which tests will you need? At this point you clearly see the levels of abstractness: the high-level [[wiki/concepts/source/basic-metapatterns/layers|*application*]] (integration, orchestration) logic, the lower-level [[wiki/concepts/source/basic-metapatterns/layers|*domain*]] (business) rules, and the generic *infrastructure* \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]. Now that you know better the whats and the hows, you divide the code (either old or rewritten from scratch) into [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] or [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] to make it both structured and flexible, yet still without a heavy development overhead caused by interfaces between subdomains. ### Youth (development of features) – fragmented architectures ![Diagrams of Layered Services, Orchestrated Services, and Top-Down Hierarchy.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Lifecycle-3.png) As you acquire domain experience, you start discerning subdomains (or *bounded contexts* \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]) and isolating them to reduce the [[wiki/concepts/source/foundations-of-software-architecture/modules-and-complexity|complexity]] of your code. The layered structure turns into a system of subdomain-dedicated components: [[wiki/concepts/source/basic-metapatterns/services|modules]], [[wiki/concepts/source/basic-metapatterns/services|services]], [[wiki/concepts/source/basic-metapatterns/services|device drivers]] – whatever you used to name them throughout your career. The actual architecture follows the structure of the domain, with [[wiki/concepts/source/fragmented-metapatterns/layered-services|*Layered Services*]], [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrated Services*]], and [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Top-Down Hierarchy*]] among common options. The fragmentation of the system enables development by multiple teams with diverse technologies and styles, reduces the ripple effects of changes, and helps testability. However, use cases for the system as a whole become harder to understand and fix – if only because they traverse the parts of the code owned by multiple teams – which is not extremely bad given you have enough humanpower to do the work. ### Adulthood (production) – ad-hoc composition ![Layered Services evolve into a pragmatic architecture where the application layers of some services are merged while the domain layer of another service is subdivided.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Lifecycle-4.png) As the product enters the market, its development tends to slow down with more attention given to corner cases and user experience. Some (often the most active) people are going to get bored and leave the project, while your understanding of the domain changes again based on user experience and real-life business needs \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]. You may find that some of the components which you have designed as independent become strongly coupled, and you are lucky if they are small enough to be merged together – this is where the fragmentation from the previous stage pays off. Other parts of the system may outgrow the comfort zone of programmers and need to be subdivided. The architecture becomes asymmetrical and pragmatic. ### Old age (support) – back to Layers ![A diagram of Layers with multiple databases.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Lifecycle-5.png) When active development ceases, you lose even more people and funding as you drift into the support phase. You are unlikely to retain your best programmers – you’ll get novices or even an outsourced team instead. They will struggle to retain the structure of the system – with its mass of hacks from the previous years – against progressively more weird requests from the business and customers whose natural desires have already been satisfied. That will cause many more hacks to be added – and components coupled or merged for the hacks to land – bringing the architecture back to [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]], though this time heavily oversized layers. ### Death (the ultimate release) – Monolith ![A diagram of a monolith with multiple databases.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Lifecycle-6.png) If the project is allowed to die, it may still have a chance for a final release which aims at improving performance and leaving a golden standard for the generations of users to come. Heavy optimizations will likely require merging the layers to avoid all kinds of communication overhead, reverting the system back to [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]]. ### So it goes Even though I have observed the cycle of architecture expanding and collapsing in embedded software, I believe that these forces apply to most kinds of systems. First you need to go quickly and interfaces are a burden. Then you need the extra flexibility that they provide to reserve space for future design changes. And as the flow of changes ceases, you may optimize the flexibility away to make programming easier and the code smaller and faster. However, the last transition is not always applicable: a distributed system will oppose compacting if it was written in diverse programming languages or needs specialized hardware setups for proper operation. ### Going back in time It can happen that you need to step back through the life cycle – for example, when the domain itself changes drastically: a new standard emerges or the management decides that your application for washing machines fits coffee machines pretty well, as they are basically doing the same things: heating water, adding powder, and stirring – yet you have never wrote software for coffee machines before, thus you are back to the R&D phase. In such cases it may be easier to rewrite the affected components from scratch rather than try to rejuvenate and refit the old code. Remember that you keep your experience – what was originally implemented as an improvised hack will be accounted for in the redesigned architecture. This means that every time a component is rewritten adds to its longevity as its architecture fits the domain more closely and needs fewer hacks (which are inflexible and confusing by definition) to get to production. --- title: "Choose your own architecture" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Analytics/The heart of software architecture/Choose your own architecture.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Analytics/The%20heart%20of%20software%20architecture/Choose%20your%20own%20architecture source_license_note: "See namespace README; preserve attribution and source links." --- # Choose your own architecture > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Analytics/The heart of software architecture/Choose your own architecture.md`. Now that we’ve seen patterns decomposed into decoupling and cohesion, we can try reconstructing architecture based on your project’s needs. ## Project size The project’s expected size is among the main determinants of the project’s architecture as both overgrown components and excessive fragmentation handicap development and maintenance. A moderate number of components of moderate size is the desired zone of comfort. Therefore, a one day task will likely be [[wiki/concepts/source/basic-metapatterns/monolith|*monolithic*]], a man-month of work needs [[wiki/concepts/source/basic-metapatterns/layers|*layering*]], while anything larger than that calls for at least partial separation into *subdomain* [[wiki/concepts/source/basic-metapatterns/services|*modules*]] or [[wiki/concepts/source/basic-metapatterns/services|*services*]]. Very large projects may require further subdivision into [*Service-Oriented Architecture*]() or [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Cell-Based Architecture*]]. ![Diagrams of Monolith, Layers, Services, Service-Oriented Architecture, and Cell-Based Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Heart/Size-1.png) Any inherent decoupling within your domain is another factor to consider in the initial design. For example, the layer with [[wiki/concepts/source/basic-metapatterns/layers|*domain* logic]] is very likely to comprise independent subdomains which naturally make modules or services at next to no development or runtime cost (see [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]]). Likewise, [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Top-Down Hierarchy*]] is a good fit for a hierarchical domain. A domain that builds around stepwise processing of data or events may be modeled as a [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]], which is a very flexible architectural style. ![Diagrams of Sandwich, Top-Down Hierarchy, and Pipeline.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Heart/Size-2.png) The number of teams you start the project with is also important. For the teams to be as efficient as possible you want them to be almost independent. As every team gets ownership of one or two components, you must assure that the architecture has enough modules or services for the teams to specialize, because anything shared will likely become a bottleneck. For example, you can hardly employ more than 3 teams with a [[wiki/concepts/source/basic-metapatterns/layers|*layered architecture*]] as there are only so many layers in any system. Thus, having a large number of teams strongly hints at [[wiki/concepts/source/basic-metapatterns/services|*Services*]], [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]], [*SOA*](), or [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchy*]]. If not all the teams are available from day one, it is still preferable to initially set up component boundaries for the prospective number of teams because subdividing an already implemented component is a terrible experience. However, it may be [easier and safer](https://martinfowler.com/bliki/MonolithFirst.html) for now to leave all the components running within a single process (as [[wiki/concepts/source/basic-metapatterns/services|*modules*]]) to avoid the overhead of going distributed and have less trouble moving pieces of code around as needed (as new requirements often make a joke of your original design). You should be able to turn *modules* into [[wiki/concepts/source/basic-metapatterns/services|*services*]] through moderate effort once that becomes an imperative. ## Domain features We have already seen above that hierarchical or pipelined domains enable the use of corresponding architectures. There is more to it. Sometimes you expect to have many complex use cases which cannot be matched to your subdomains because every scenario involves multiple components, thus spreading over the entire system. You would usually collect the global use cases into a dedicated component – an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]]. And if the *Orchestrator* grows out of control, it is [[wiki/concepts/source/extension-metapatterns/orchestrator|subdivided]] into layers or services. ![Diagrams of Services with: a monolithic orchestrator, Backends for Frontends, an orchestrator per use case, a hierarchical orchestrator and a layered orchestrator.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Heart/Features-1.png) Other systems are built around data. You cannot split it into private databases because almost every service needs access to the whole, which necessitates a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]], or the highly performant [[wiki/concepts/source/extension-metapatterns/shared-repository|*Space-Based Architecture*]]. ![Diagrams of Services with a shared database and Space-Based Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Heart/Features-2.png) Once you go distributed, you will likely employ a [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] to centralize the communication between your services. And you will have various [[wiki/concepts/source/extension-metapatterns/proxy|*Proxies*]], such as a [[wiki/concepts/source/extension-metapatterns/proxy|*Firewall*]], a [[wiki/concepts/source/extension-metapatterns/proxy|*Reverse Proxy*]], and a [[wiki/concepts/source/extension-metapatterns/proxy|*Response Cache*]]. You may even deploy a *Proxy* per kind of client if the clients vary in their protocols, resulting in [*Backends for Frontends*]() (*BFF*). ![Diagrams of Services with a middleware, Services with a proxy, and Backends for Frontends.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Heart/Features-3.png) ## Runtime performance Moreover, there are non-functional requirements, such as performance and fault tolerance. High throughput is achieved by [[wiki/concepts/source/basic-metapatterns/shards|*sharding*]] or [[wiki/concepts/source/basic-metapatterns/shards|*replicating*]] your business logic or even your data. Sharding also helps process huge datasets while replication improves fault tolerance. [[wiki/concepts/source/extension-metapatterns/sandwich|*Space-Based Architecture*]] replicates the entire dataset in memory for faster access. ![Diagrams of stateless instances with a load balancer and a shared database, shards behind a sharding proxy, and replicas behind a load balancer.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Heart/Performance-1.png) Alternatively, you may use several specialized databases ([[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]]) or redesign a highly loaded part of your system as a self-scaling [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]]. ![Diagrams of Services with Polyglot Persistence and a Cell with a scaled pipeline.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Heart/Performance-2.png) Scalability under uneven load is achieved through [[wiki/concepts/source/basic-metapatterns/services|*Function as a Service*]] (*Nanoservices*), [[wiki/concepts/source/implementation-metapatterns/mesh|*Service-Mesh*]]-based [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]] and, to a greater extent, [[wiki/concepts/source/implementation-metapatterns/mesh|*Space-Based Architecture*]]. ![Diagrams of scaled single-layer Nanoservices, Microservices, and processing units of Space-Based Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Heart/Performance-3.png) Fault tolerance requires you to have [[wiki/concepts/source/basic-metapatterns/shards|*replicas*]] of every component, including databases, ideally over multiple data centers. If you are not that rich, be content with [[wiki/concepts/source/basic-metapatterns/services|*Actors*]] or [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]]. ![Diagrams of whole-system replicas of Services with an API Gateway, actors running in a distributed framework, and a peer-to-peer mesh.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Heart/Performance-4.png) Low latency makes you place simplified first response logic close to your input, leading to: - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Model-View-Presenter*]] or [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Model-View-Controller*]] pattern families for user interaction. - [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] with [[wiki/concepts/source/basic-metapatterns/layers|*strategy injection*]] for single hardware input. - A [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchy*]] for distributed [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|*control*]] systems such as [IIoT](https://en.wikipedia.org/wiki/Industrial_internet_of_things). ![Shortcuts in the control flow of Model-View-Presenter, Model-View-Controller, Layers optimized through business logic injection, and Top-Down Hierarchy in a control system.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Heart/Performance-5.png) ## Flexibility If your product needs customization, you go for [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]]. If it is to survive for a decade, you need [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] to be able to swap vendors. If you mediate between resource or service providers and consumers, you build a [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]]. ![Diagrams of Plugins, Hexagonal Architecture, and Microkernel.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Heart/Flexibility-1.png) When your teams develop services and you want them to be [[wiki/concepts/source/analytics/indirection-in-commands-and-queries|less interdependent]], you insert an [[wiki/concepts/source/extension-metapatterns/proxy|*Anticorruption Layer*]], [[wiki/concepts/source/extension-metapatterns/proxy|*Open Host Service*]], or [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*CQRS View*]] between them. ![Diagrams of Anticorruption Layer, Open Host Service, and CQRS View.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Heart/Flexibility-2.png) When you have built a large system and really need that thorough data analytics, consider implementing a [[wiki/concepts/source/basic-metapatterns/pipeline|*Data Mesh*]]. ![Data Mesh builds an extra graph of services that stream and process analytical data.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Data%20Mesh.png) ## Every domain is unique No one-size-fits-all. Embedded projects or single-player games don’t have databases and run in a single process. High Frequency Trading bypasses the OS kernel to save microseconds. *Middleware* and distributed databases care about quorum and leader election. Huge-scale data processing must account for [bit flips](https://en.wikipedia.org/wiki/Soft_error). A medical device should never crash. Banks store their history forever for external audits. There is no universal architecture. No silver bullet pattern. Patterns are mere tools. Know your tools and choose wisely. ## So it goes Software architecture lies lifeless in my hands, devoid of its magical colors, *like the dead iguana*. --- title: "Cohesers and decouplers" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Analytics/The heart of software architecture/Cohesers and decouplers.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Analytics/The%20heart%20of%20software%20architecture/Cohesers%20and%20decouplers source_license_note: "See namespace README; preserve attribution and source links." --- # Cohesers and decouplers > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Analytics/The heart of software architecture/Cohesers and decouplers.md`. Any project carries many constraints (*forces*), some of which want for certain parts of its code to be kept together (*cohesive*) while others push to have them torn apart (*decoupled*). Their balance and the resulting optimal architecture is very fluid as each of the forces in action depends on the current circumstances, the project’s history, and its expected evolution. ## Code structure and the level of pain Let’s explore how a force influences the structure of a project. Consider the *clarity of code* which determines *development velocity*: ![A chart that shows that unstructured code is the least painful solution for a tiny project while something large is unbearable if not decomposed into services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Heart/Pain.png) When you have 10 lines of business logic, you are likely to write them down as a simple script. Separating them into classes or deploying 5 services, each running 2 lines of code, is an overkill which would make the complexity of your infrastructure much higher than that of the task on hand. At 100 lines of code you are likely to be more comfortable with procedures or even classes to divide the code into, as keeping everything together starts to hurt. You switch from the most cohesive implementation to another one, decoupled to an extent. Though the latter is more complex at its core, it allows for less painful growth because it encompasses smaller components. A file of 5 000 lines is hard to read – you need to separate it into modules, each of which contains classes, which contain methods, which contain the code. You are building yet another level of the hierarchy to keep the number of items in each piece (lines in a method, methods in a class, classes in a module) comfortably small. At about 100 000 lines you may start considering further separation of your project into services – as, you know, there are merge conflicts, or it takes a while to compile and test the whole codebase… anyway, at that point very few people comprehend the whole thing in detail, thus the benefits of having the entire codebase co-located ([*monorepo*](https://en.wikipedia.org/wiki/Monorepo)) are diminishing while the new drawbacks emerge. ## Building a hierarchy As we see from the example above, code clarity favors *cohesiveness* (everything together) for smaller codebases but *decoupling* (parts separated with narrow interfaces) for larger projects. We can state that the direction the force under review (namely, clarity) pushes us in depends on the project’s size. That is not a unique case – many forces work this way, resulting in the famous [*Monolith* vs *Microservices* complexity diagram](https://martinfowler.com/bliki/MicroservicePremium.html). Such a behavior is common for architectural forces and, by the way, it is also the case with editing sorted data. Array is the most efficient data structure for a small collection (up to about 1 000 elements) while anything larger requires a hash map or [B-tree](https://en.wikipedia.org/wiki/B-tree) (hierarchy of arrays). Just as a database splits oversized arrays because they are too slow to edit, the human mind is inefficient with large collections of similar items and wants them to be restructured into a hierarchy. When we look into a service, we see only the classes it contains. When we examine a class, we check the list of its methods, not those of surrounding classes. And when we open a method, we try to understand how its lines of code work together. This is the way humans fight complexity – by selecting a segment at one level of abstraction and ignoring everything around. ![The hierarchical decomposition of logic into methods of classes in a service is compared to that of data in a B-tree.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Heart/Hierarchy.png) A hierarchy inherently adds some inconvenience – traversing levels of a B-tree slows down operations, and a project with many files takes time to grasp – which is why we avoid deep hierarchies in smaller projects (or datasets) – yet that is still a very low cost for having every individual component (a method, class, or module in a project; an array in a B-tree) stay reasonably small and simple thanks to the distribution of the overall complexity (or data) over the hierarchy. ## Bidirectional forces Among the forces that lead to a preference for decomposition of a project into segments of certain size are: - *Clarity* – as discussed above. - *Development velocity* – programmers are [very productive](https://realmensch.org/2018/05/04/we-are-all-10x-developers/) when they know their code and don’t waste their time on communication. Still, there is a productivity limit for a single person, and if you want your project to be developed faster than that, you need to divide it among several teams, sacrificing individual performance for brute force. - *Throughput* – on one hand, interservice communication is suboptimal because of the associated networking and serialization. On the other hand, there is a limit for a single server’s performance as more powerful hardware is too expensive. It is cheaper to install several commodity servers and accept a communication penalty than keep the entire system running on a single high-end machine to avoid networking. - *Latency* – it is poor for a distributed system, but is not much better for a large monolith that may deadlock. Ideally, you should extract the latency-critical part into a dedicated small component placed close to the input and output hardware. - *Security* – a single process is easier to audit and secure than a system of services. However, as a service grows, it may develop multiple interfaces and assimilate many libraries. Eventually, protecting that mishmash becomes much harder than creating a secure perimeter around a distributed system. ## Cohesers Other forces predominantly push you towards merging all your code and data together: - *Debuggability* – it is hard to debug a distributed system. You need to investigate logs and attach your debugger to several services which may be written in different programming languages. Debugging a single process, even 10 MLoC in size, is almost always easier (except when it is poorly documented). - *Data consistency* – when everything runs in a single thread, you don’t have to care about data races, lost packets, or [idempotence](https://dev.to/woovi/idempotence-what-is-and-how-to-implement-4bmc). The [CAP theorem](https://en.wikipedia.org/wiki/CAP_theorem) is on your side. - *Data analysis* – it is hard to collect data from multiple sources. You cannot use SQL joins. Nevertheless, at some point in the distant future, you may still reach the performance limit of your single database, in theory making data analysis a kind of bidirectional force. ## Decouplers And there can be forces which try to keep your code fragmented: - *Variability* – if your project needs to satisfy many [[wiki/concepts/source/foundations-of-software-architecture/forces-asynchronicity-and-distribution|conflicting requirements]], it is very hard to achieve that with a uniform codebase running in a single process. - *Location* – you may need to run parts of your system on [[wiki/concepts/source/foundations-of-software-architecture/forces-asynchronicity-and-distribution|its end users’ devices]], in [[wiki/concepts/source/basic-metapatterns/shards|regional data centers]], or even on [[wiki/concepts/source/implementation-metapatterns/microkernel|specialized hardware]]. - *Organizational structure* – according to [Conway’s law](https://en.wikipedia.org/wiki/Conway%27s_law), forcing everyone to work on a shared component is among the top team performance killers, especially when different time zones are involved. ## Expansion and contraction ![A monolith transforming into Layered Services, whose application layers partially merge and domain layers split, only to finally glue into Layers with multiple databases.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Heart/Lifecycle.png) As was [[wiki/concepts/source/analytics/architecture-and-product-life-cycle|discussed previously]], when you start a project by building a [PoC](https://en.wikipedia.org/wiki/Proof_of_concept#Software_development) or prototype, you have little code and must move quickly. Most of the *decouplers* are not there and the *bidirectional forces* favor cohesion, thus you don’t waste your time on extra interfaces or fine-grained services. You don’t have multiple teams to fall prey to Conway’s law. As soon as the prototype is approved, it’s time to prepare for iterative development of a project which nobody comprehends in advance. Requirements will change many a time. Libraries and frameworks may not work as expected. The external services which you rely on may die or change. Your team members may leave or, worse, be eager to try a novel technology. You need all the flexibility in the world, and flexibility comes through decoupling. But you also rely on your speed to remain ahead of competitors, therefore you cannot go too far because decoupling is not free. You try to imagine what may change in the future and prepare by isolating the would-be affected parts behind well-defined interfaces. As your software matures, the flow of changes slows down and the business becomes more predictable or, rather, more familiar. You observe that some of the interfaces which you’ve added have never been put to real use – they just make the project more complex – which means that you pay for decoupling without earning its benefits. Others have been found to be too restrictive and were removed. Thus the system begins to contract, burning the flexibility it no longer needs while also growing in directions that were not originally expected. Finally, you move to the support phase. The best programmers leave for more active projects. Whoever replaces them does not know the code. The remaining flexibility decomposes in favor of quick hackarounds. What remains is an ugly cohesive [evolutionary-shaped mess](http://www.laputan.org/mud/), created through generations of ad-hoc patches. --- title: "Comparison of architectural patterns" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Analytics/Comparison of architectural patterns/Comparison of architectural patterns.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Analytics/Comparison%20of%20architectural%20patterns/Comparison%20of%20architectural%20patterns source_license_note: "See namespace README; preserve attribution and source links." --- # Comparison of architectural patterns > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Analytics/Comparison of architectural patterns/Comparison of architectural patterns.md`. This chapter is a compilation of small sections, each of which examines one aspect of the [[wiki/concepts/source/appendices/index-of-patterns|architectural patterns included in this book]]. It shows the value of having the list of metapatterns to iterate over and analyze. ## Contents: - [[wiki/concepts/source/analytics/sharing-functionality-or-data-among-services|Sharing functionality or data among services]] - [[wiki/concepts/source/analytics/pipelines-in-architectural-patterns|Pipelines in architectural patterns]] - [[wiki/concepts/source/analytics/dependency-inversion-in-architectural-patterns|Dependency inversion in architectural patterns]] - [[wiki/concepts/source/analytics/indirection-in-commands-and-queries|Indirection in commands and queries]] --- title: "Deconstructing patterns" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Analytics/The heart of software architecture/Deconstructing patterns.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Analytics/The%20heart%20of%20software%20architecture/Deconstructing%20patterns source_license_note: "See namespace README; preserve attribution and source links." --- # Deconstructing patterns > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Analytics/The heart of software architecture/Deconstructing patterns.md`. Imagine a dungeon with dragons. It is made of halls connected by tunnels. Each hall is *cohesive*. Tunnels are narrow interfaces that *decouple* them. A hall is amorphous – it can have any shape but it cannot open to another hall except through a tunnel – such are the rules of the game. The tunnels both restrict the freedom of the halls and interconnect them. ## SOLID principles If *cohesion* and *decoupling* dictate software architecture, they should surface in its principles. Let’s take a look at [SOLID](https://en.wikipedia.org/wiki/SOLID): - The *single responsibility principle*, also known as [*do one thing and do it well*](https://en.wikipedia.org/wiki/Unix_philosophy#Do_One_Thing_and_Do_It_Well), is a general advice for keeping unrelated functionality decoupled. - The *open-closed principle* and *Liskov substitution principle* decouple the logic of the parent class or the code that uses it, respectively, from the functionality of its subclasses. - The *interface segregation principle* decouples independent parts of an object’s interface. - The *dependency inversion principle* decouples an object’s users from its implementation. Please beware that each of those principles in and of themselves invokes decoupling which is not free – your software may end up having too many moving parts and strict rules to remain easy to read and support. > When we choose between cohesion and decoupling, we choose between a single component and a pair of components connected through a constraint rule. The more decoupling, the more components and rules we have to handle. Sooner, rather than later, the number of individual components and rules will overwhelm any developer. ## Gang of Four patterns Let’s now discuss something more practical, namely the \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] [patterns](https://en.wikipedia.org/wiki/Design_Patterns#Patterns_by_type) which seem to be ingenious, yet hacky, ways for rearranging the roles in your code. They override ordinary OOP rules, which is useful when you need extra flexibility. For example, the *creational patterns* interfere with the normally cohesive *select type – create – initialize – use* sequence of operating an object. Some patterns provide a basic decoupling: - [*Adapter*](https://refactoring.guru/design-patterns/adapter) translates between two interacting components so that they may evolve independently. - [*Observer*](https://refactoring.guru/design-patterns/observer) decouples an event from the reactions which it causes by registering the event handlers at runtime. - [*Chain of Responsibility*](https://refactoring.guru/design-patterns/chain-of-responsibility) separates the method invocation from the method execution. A client’s calling a method of an object runs the corresponding method of another object. Others break the functionality or data of a class into two or more parts, juggling them at runtime: - [*Proxy*](https://refactoring.guru/design-patterns/proxy) separates an object’s representation from its implementation, enabling lazy loading or remote access. - [*Flyweight*](https://refactoring.guru/design-patterns/flyweight) extracts an immutable data member of a class and merges multiple instances of that identical data to save memory. - [*Strategy*](https://refactoring.guru/design-patterns/strategy) and [*Decorator*](https://refactoring.guru/design-patterns/decorator) decouple a dimension of an object’s functionality to allow runtime changes in or composition of the object's behavior, respectively. - [*State*](https://refactoring.guru/design-patterns/state) separates an object’s behavior into multiple classes based on the object’s current state. - [*Template Method*](https://refactoring.guru/design-patterns/template-method) decouples several aspects of a class’s behavior from its main algorithm and envelops the variations of those aspects into subclasses. - [*Bridge*](https://refactoring.guru/design-patterns/bridge) separates a high-level hierarchy of classes from their low-level implementation details which may comprise an orthogonal hierarchy. - [*Memento*](https://refactoring.guru/design-patterns/memento) decouples the lifetime of an object’s state from the object itself. On the other hand, several patterns gather separate components together: - [*Command*](https://refactoring.guru/design-patterns/command) collects all the data required to call a method. - [*Mediator*](https://refactoring.guru/design-patterns/mediator) is a cohesive implementation of multi-object use cases. - [*Composite*](https://refactoring.guru/design-patterns/composite) and [*Facade*](https://refactoring.guru/design-patterns/facade) represent multiple objects as a cohesive entity. A *Composite* broadcasts a call made to its interface to every object which it contains, while a *Facade* [[wiki/concepts/source/foundations-of-software-architecture/orchestration|orchestrates]] the subsystem which it wraps. - [*Abstract Factory*](https://refactoring.guru/design-patterns/abstract-factory) and [*Builder*](https://refactoring.guru/design-patterns/builder) encapsulate *type selection* and *initialization* for several related hierarchies, so that the client code gets objects from a consistent set of types. On top of that, a *Builder* cross-links the objects it creates into a cohesive subsystem, which is then returned to the *builder*’s client as a whole. The remaining patterns pick an aspect or two of an object’s behavior and move them elsewhere: - [*Iterator*](https://refactoring.guru/design-patterns/iterator) moves the code for traversal of a container’s elements from the container’s clients into the container’s implementation, decoupling the clients from the iteration algorithm. - [*Visitor*](https://refactoring.guru/design-patterns/visitor) aggregates the actions that a client needs to perform on each kind of object in a hierarchy, decoupling them from the classes that constitute the hierarchy. - *Interpreter* decouples client scenarios from the rest of the system by having them written in a dedicated language and run in a protected environment. - [*Prototype*](https://refactoring.guru/design-patterns/prototype) binds the *type selection* and *initialization* together and decouples them from the object *creation*. - [*Singleton*](https://refactoring.guru/design-patterns/singleton) binds the *creation* and *initialization* of a global object to every call of its methods. - [*Factory Method*](https://refactoring.guru/design-patterns/factory-method) decouples the *initialization* from *type selection* and hides both from the class’s users. As we see, every \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] pattern boils down to binding (making *cohesive*) and/or separating (*decoupling*) some kind of functionality or responsibilities. ## Architectural metapatterns Finally, let’s close the book by iterating over the metapatterns and looking into their roots through the lens of unification and separation. ![Diagrams of Monolith, Shards, Layers, Services, and Pipeline, with cohesive and decoupled components highlighted.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Heart/Basic.png) [[wiki/concepts/source/basic-metapatterns/basic-metapatterns|Basic architectures]]: - [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] keeps everything together for quick and dirty projects: - Total *cohesiveness* results in low latency, cost-efficient performance, and easy debugging. - [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]] slice a large-scale application into multiple instances: - *Decoupling* the instances enables scaling but sacrifices the consistency of shared data. - [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] separate the high-level code from the low-level implementation: - *Cohesion* within a layer makes it easy to implement and debug. - *Decoupled* layers may vary among themselves in technologies and properties, but are somewhat slower and hard to debug in-depth. - [[wiki/concepts/source/basic-metapatterns/services|*Services*]] divide a complex system into subdomains: - *Cohesiveness* within a service keeps it simple and efficient when it does not need to consult with other services. - *Decoupling* enables the development of larger codebases by multiple specialized teams but any global use cases become complicated. - [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]] segregates data processing into self-contained steps: - *Decoupling* simplifies reassembling or expanding the system but increases its latency. ![Diagrams of Services with a middleware, Services with a shared repository, Services with a proxy, Services with an orchestrator, and Sandwich, with cohesive and decoupled components highlighted.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Heart/Extension.png) [[wiki/concepts/source/extension-metapatterns/extension-metapatterns|Grouping related functionality]]: - [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] separates the implementation of communication and/or instance management from the business logic: - The *cohesive* communication layer is not only reliable, but also uniform, which makes it easy to learn. - *Decoupling* the communication concerns from the business logic simplifies the latter. - [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]] dissociates data from code, enabling [[wiki/concepts/source/foundations-of-software-architecture/shared-data|data-centric programming]]: - *Cohesive* data is consistent and easy to handle. - *Decoupled* business logic can be scaled or subdivided [[wiki/concepts/source/foundations-of-software-architecture/programming-and-architectural-paradigms|independently]] of the data. - [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]] mediates between a system and its clients, taking care of some aspects of their communication: - A *cohesive* edge component is easier to manage and secure. - *Decoupling* generic aspects simplifies the business logic but usually increases latency. - [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] collects a multitude of complex use cases into a dedicated layer: - *Cohesive* use cases are easy to comprehend and debug. - *Decoupling* the [[wiki/concepts/source/basic-metapatterns/layers|use cases]] from the [[wiki/concepts/source/basic-metapatterns/layers|domain logic]] allows for variation in technologies but increases latency and complicates in-depth debugging. - [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]] distantiates both control and data from the domain rules, which become segmented: - *Cohesive* use cases and data integrate the system. - *Decoupling* the subdomain components from each other and from the system-wide layers keeps every part of the system reasonably small and independent. ![Diagrams of Layered Services, Services with Polyglot Persistence, Backends for Frontends, Service-Oriented Architecture, and Top-Down Hierarchy, with cohesive and decoupled components highlighted.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Heart/Fragmented.png) [[wiki/concepts/source/fragmented-metapatterns/fragmented-metapatterns|Decoupled topologies]]: - [[wiki/concepts/source/fragmented-metapatterns/layered-services|*Layered Services*]] first decouple the subdomains, and then the layers within each subdomain: - *Decoupled* subdomains allow for multi-team development and large codebases but complicate global use cases. *Decoupled* layers enable variation in technologies within a subdomain and [[wiki/concepts/source/foundations-of-software-architecture/orchestration|limit interdependencies]] between subdomains to a single layer. - [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]] divides data among multiple data stores: - *Decoupling* improves performance through data store specialization at the cost of consistency. - [*Backends for Frontends*]() dedicate one or two components (a [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]] and/or [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]]) to each kind of client. - *Decoupling* allows for customization on a per-client-type basis but makes it hard to share functionality among the clients. - [*Service-Oriented Architecture*]() first segregates a large system into layers, then subdivides each layer into services: - *Decoupling* layers strangely enables reuse as any component of an upper layer can access every component below it. *Decoupling* services within the layers allows for multi-team development. Drawbacks include high latency, system complexity, and interdependencies. - [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchy*]] recursively separates general and specialized logic, tackling complexity: - *Cohesive* general and subdomain-specific business logic helps readability and debugging. - *Decoupled* layers and subdomains allow for modification and expansion of local functionality at the cost of performance. ![Diagrams of Plugins, Hexagonal Architecture, Microkernel, and Mesh, with cohesive and decoupled components highlighted.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Heart/Implementation.png) [[wiki/concepts/source/implementation-metapatterns/implementation-metapatterns|Component implementation]]: - [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]] separate customizable aspects of a system’s behavior: - *Decoupling* several aspects of a system allows for it to be fine-tuned but requires careful design and may lower performance. - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] isolates the business logic from its external dependencies: - *Decoupling* protects from vendor lock-in and supports automatic testing at the cost of lost optimization opportunities. - [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]] mediates between resource consumers and resource producers: - *Cohesive* resource management optimizes resource usage. - *Decoupling* allows for seamless replacement of resource providers. - [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]] aggregates distributed components into a virtual layer: - Virtual *cohesion* hides the complexity of distributed communication from the client code. - Actual *decoupling* (distribution) of the nodes enables scaling and fault tolerance. --- title: "Dependency inversion in architectural patterns" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Analytics/Comparison of architectural patterns/Dependency inversion in architectural patterns.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Analytics/Comparison%20of%20architectural%20patterns/Dependency%20inversion%20in%20architectural%20patterns source_license_note: "See namespace README; preserve attribution and source links." --- # Dependency inversion in architectural patterns > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Analytics/Comparison of architectural patterns/Dependency inversion in architectural patterns.md`. I am no fan of [*SOLID*](https://en.wikipedia.org/wiki/SOLID) – to the extent of being unable to remember what those five letters mean – thus I was really surprised to notice that one of its principles, namely [*dependency inversion*](https://en.wikipedia.org/wiki/Dependency_inversion_principle), is quite common with architectural patterns, which means that it is much more generic than *OOP* which it is promoted for. Let’s see how dependency inversion is used at the system level. ## Patterns that build around it ![Plugins depend on the core's SPIs. There are multiple versions of plugins. Adapters of Hexagonal Architecture depend on both the core's SPIs and APIs of the adapted components.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/DI-1.png) Both [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]] and the derived [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] rely on dependency inversion for the same reason – to protect the *core*, which contains the bulk of the code, from variability in the external components it uses. The *core* operates interfaces ([*SPI*](https://en.wikipedia.org/wiki/Service_provider_interface)s) which it defines so that it may not care exactly what is behind the interface. It is the nature of the polymorphic components that distinguishes those patterns: - [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]] allow for small pieces of code, typically contributed by outside developers, to provide customizable parts of the system’s algorithms and decision making. Oftentimes the core team has no idea of how many diverse plugins will be written for their product. - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] is about breaking dependency of the core on external libraries or services by employing [[wiki/concepts/source/extension-metapatterns/proxy|*adapters*]]. Each adapter depends both on the core’s SPI and on the API of the component which it adapts. As interfaces and contracts vary among vendors and even versions of software, which we want to be interchangeable, we need adapters to wrap the external components to make them look identical to our core. Besides that, [*stub* or *mock*](https://stackoverflow.com/questions/3459287/whats-the-difference-between-a-mock-stub) adapters help develop and test the core in isolation. ## Patterns that often rely on it ![In an operating system, device drivers depend on the kernel's SPIs. In a hierarchy, child nodes depend on their parent's SPI. Cell-Based Architecture uses adapters to break dependencies between Cells.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/DI-2.png) A few more patterns tend to use this approach to earn its benefits, even though dependency inversion is not among their integral features: - [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]], yet another metapattern derived from [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]], distributes resources of *providers* among *consumers*. Polymorphism is crucial for some of its variants, including [[wiki/concepts/source/implementation-metapatterns/microkernel|*Operating System*]], but may rarely benefit others, such as [[wiki/concepts/source/implementation-metapatterns/microkernel|*Software Framework*]]. - [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Top-Down Hierarchy*]] distributes responsibility over a tree of components. If the nodes of the tree are polymorphic, they are easier to operate, and there is dependency inversion. However, in practice, a parent node may often be so strongly coupled to the types of its children that polymorphism becomes impractical. - In another kind of [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchy*]], namely [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Cell-Based Architecture*]] (aka *Services of Services*), each [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cell*]] [may employ](https://github.com/wso2/reference-architecture/blob/master/reference-architecture-cell-based.md) a [[wiki/concepts/source/extension-metapatterns/proxy|*Cell Gateway*]] and outbound [[wiki/concepts/source/extension-metapatterns/proxy|*Adapters*]] or [[wiki/concepts/source/implementation-metapatterns/plugins|*Ambassador Plugins*]] to isolate its business logic from the environment – just like its parent [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] does for a monolithic core. ## Patterns that may use it ![Standard APIs are used between frontend and backend, and backend and database. CQRS views and adapters protect a service from dependencies on other services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/DI-3.png) Finally, two basic architectures, [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] and [[wiki/concepts/source/basic-metapatterns/services|*Services*]], may resort to something similar to dependency inversion to decouple their constituents: - We often see a higher layer to depend on, and a lower layer to implement a standardized interface, like POSIX or SQL, to achieve *interoperability* with other implementations (which is yet another wording for polymorphism). - A service may follow the concept of [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] by using an [[wiki/concepts/source/basic-metapatterns/services|*Anti-Corruption Layer*]] or [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*CQRS Views*]] as [[wiki/concepts/source/extension-metapatterns/proxy|*Adapters*]] that protect it from changes in other system components. ## Summary Many architectural patterns employ dependency inversion by adding: - an *interface* to enable polymorphism of their lower-level components or - [[wiki/concepts/source/extension-metapatterns/proxy|*Adapters*]] to protect a component from changes in its dependencies. These two approaches apply in different circumstances: - If you can enforce your rules of the game on the suppliers of the external components, you merely *define an SPI*, and expect the suppliers to implement and obey it. - If the suppliers are independent and it is your side that adapts to their rules, you should *add Adapters* to translate between your lovely SPI and their whimsical APIs. --- title: "Indirection in commands and queries" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Analytics/Comparison of architectural patterns/Indirection in commands and queries.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Analytics/Comparison%20of%20architectural%20patterns/Indirection%20in%20commands%20and%20queries source_license_note: "See namespace README; preserve attribution and source links." --- # Indirection in commands and queries > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Analytics/Comparison of architectural patterns/Indirection in commands and queries.md`. *We can solve any problem by introducing an extra level of indirection* – states the [old adage](https://en.wikipedia.org/wiki/Fundamental_theorem_of_software_engineering). We will not explain how this rule drives [deep learning](https://en.wikipedia.org/wiki/Deep_learning), at least for now. Instead, let’s concentrate our effort on indirection in communication between services. Each component operates its own [[wiki/concepts/source/basic-metapatterns/layers|*domain model*]] \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\] which translates into [[wiki/concepts/source/foundations-of-software-architecture/programming-and-architectural-paradigms|objects]] and/or [[wiki/concepts/source/foundations-of-software-architecture/programming-and-architectural-paradigms|procedures]] convenient for use in the component’s subdomain. However, should a system cover multiple subdomains, the best models for its parts to operate start to mismatch. Furthermore, they are likely to diverge progressively over time as requirements heap up and [[wiki/concepts/source/analytics/architecture-and-product-life-cycle|the project matures]]. If we want for each module or service to continue with a model that fits its needs, we have to protect it from the influence of models of its neighbor components by employing indirection – a translator – between them. > In a system of subdomain-dedicated [[wiki/concepts/source/basic-metapatterns/services|*Services*]] a service may need to operate entities that are defined in another service’s subdomain. For example, the financial and recruiting departments’ software operates employees, but the employee data which each department needs is different. Moreover, it also differs from the employee records in the HR department which is responsible for adding, editing, and discarding the employees. We don’t want our accountants to spend their nights seeking the correlation between salaries, birthday horoscopes from HRs, and MBTI test scores from the recruiters. ## Command (OLTP) systems ![Dependency diagrams for Anticorruption Layer, Open Host Service, and Orchestrated Services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Indirection-Command.png) More often than not our system consists of services that command each other: via [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call)s, requests, or even notifications – no matter how, one component makes a call to action which other(s) should obey. In such a case we employ an [[wiki/concepts/source/extension-metapatterns/proxy|*Adapter*]] between two services, or an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] when cooperation of several services is needed to execute our command: - An [[wiki/concepts/source/extension-metapatterns/proxy|*Anticorruption Layer*]] is an *Adapter* on the dependent service’s side: as soon as we call another service, we start depending on its interface, while it is in our interest to isolate ourselves from its peculiarities and possible future changes. Thus we should better write and maintain a component to translate the foreign interface, defined in terms of the foreign domain model, into terms convenient for use within our code. Even if we subscribe to notifications, we may also want to have an *Adapter* to transform their payload. - An [[wiki/concepts/source/extension-metapatterns/proxy|*Open Host Service*]] resides on the other side of the connection – it is an *Adapter* that a service provider team installs to hide the implementation details of their service from its users. It will typically translate from the provider’s domain model into a more generic (subdomain-agnostic) interface suitable for use by services that implement other subdomains. - An [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] (which can be an [[wiki/concepts/source/extension-metapatterns/orchestrator|*API Composer*]], [[wiki/concepts/source/extension-metapatterns/orchestrator|*Process Manager*]], or [[wiki/concepts/source/extension-metapatterns/orchestrator|*Saga Orchestrator*]]) spreads a command to multiple services, waits for each of them to execute its part, and cleans up after possible failures. It tends to be more complex than other translators because of the coordination logic involved. ## Query (OLAP) systems ![Dependency diagrams for CQRS View, Reporting Database, and Query Service.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Indirection-Query.png) There is often another aspect of communication in a system, namely, information collection and analysis. And it runs into a different set of issues which cannot be helped by mere interface translation. Each service operates and stores data in its own format and schema which matches its *domain model*, as discussed above. When another service needs to analyze the foreign data according to its own domain model, it encounters the fact that the foreign format(s) and schema(s) don’t allow for efficient processing – in the worst case it would have to read and re-process the entire foreign service’s dataset to execute its query. The solution employs an intermediate database as a translator from the provider’s to the consumer’s preferred data access mode, format, and schema: - A [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*CQRS View*]] resides in the data consumer and aggregates the stream of changes published by the data provider. This way the consumer can know whatever it needs about the state of the provider without making an interservice call. - [[wiki/concepts/source/basic-metapatterns/pipeline|*Data Mesh*]] is about each service exposing a general-use public interface for streaming and/or querying its data. Maintaining one often requires the service to set up an internal [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Reporting Database*]]. - A [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Query Service*]] aggregates streams from multiple services to collect their data together, making it available for efficient queries (joins). ## A mixed case ![A service which injects a plugin into another service and streams data for use by the plugin.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Indirection-Plugin.png) [[wiki/concepts/source/implementation-metapatterns/plugins|*Ambassador Plugins*]] ([called *Extensions* by Uber](https://www.uber.com/en-UA/blog/microservice-architecture/)) employ both logic and data indirection. When a service needs to modify the behavior of another service, it both writes a *Plugin* for the target service and extends its domain events stream with opaque data fields which the newly created *Plugin* can operate without consulting its origin service. The host (*Plugin*’s target) service passes the extra data fields to the *Plugin* which processes them and stores the data it needs in a dedicated table in its host’s database. When the service with *Plugins* has to make a decision, it calls every registered *Plugin* as a part of its workflow. The *Plugin* reads the data it saved to its host’s database, processes it according to the business rules or its origin subdomain, and returns results – without any slow and failure-prone interservice calls! ## Summary We see that though command-dominated (*operational* or *transactional*) and query-dominated (*analytical*) systems differ in their problems, the architectural solutions which they employ to decouple their component services match perfectly: - [[wiki/concepts/source/extension-metapatterns/proxy|*Anticorruption Layer*]] or [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*CQRS View*]] is used on the consumer’s side, - [[wiki/concepts/source/extension-metapatterns/proxy|*Open Host Service*]] or [[wiki/concepts/source/basic-metapatterns/pipeline|*Data Mesh*]]’s [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Reporting Database*]] is on the provider’s side, - [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] or [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Query Service*]] coordinates multiple providers. Which shows that the principles of software architecture are deeper than the [CQRS](https://en.wikipedia.org/wiki/Command_Query_Responsibility_Segregation) dichotomy itself. --- title: "Pipelines in architectural patterns" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Analytics/Comparison of architectural patterns/Pipelines in architectural patterns.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Analytics/Comparison%20of%20architectural%20patterns/Pipelines%20in%20architectural%20patterns source_license_note: "See namespace README; preserve attribution and source links." --- # Pipelines in architectural patterns > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Analytics/Comparison of architectural patterns/Pipelines in architectural patterns.md`. Several architectural patterns involve a unidirectional data flow – a [*pipeline*](https://en.wikipedia.org/wiki/Pipeline_(software)). Strictly speaking, every data packet in a pipeline should: - Move through the system over the same *route* with no loops. - Be of the same *type*, as a part of a *data stream*. - Retain its *identity* on the way. - Retain *temporal order* – the sequence of packets remains the same over the entire pipeline. Staying true to all of these points makes *Pipes and Filters* – one of the oldest known architectures. Yet there are other architectures that discard one or more of those conditions: ## [[wiki/concepts/source/basic-metapatterns/pipeline|Pipes and Filters]] ![Pipes and Filters where a data stream originates with the source, passes several filters, and ends in a sink.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Pipelineliness-PipesAndFilters.png) [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipes and Filters*]] is about stepwise [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|processing of a data stream]]. Each piece of data (a video frame, a line of text, or a database record) passes through the entire system. This architecture is easy to build and it has a wide range of applications, from hardware to data analytics. Though each pipeline specializes in a single use case, it is often possible to build many different pipelines from the same set of generic components, which is actually practiced by Linux admins in their use of shell scripts \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\]. ## [[wiki/concepts/source/basic-metapatterns/pipeline|Choreographed Event-Driven Architecture]] ![Parcel delivery example with different pipelines for individual parcels and trucks of parcels.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Pipelineliness-EventDrivenArchitecture.png) Relaxing the *type* and loosening the *identity* criteria opens the way to [[wiki/concepts/source/basic-metapatterns/pipeline|*Choreographed Event-Driven Architecture*]], in which a service publishes notifications about everything it does which may be of interest to other services. In such a system: - There are multiple kinds of events going in different directions, as if several branched pipelines were built over the same set of services. - A service may aggregate multiple incoming events to publish a single, seemingly unrelated, event later, when a certain condition is met. For example, a warehouse delivery collects individual orders till it gets a truckload of them, or until the evening comes and no new orders are accepted. This architecture covers way more complex use cases than [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipes and Filters*]], because multiple pipelines are present in the system and because processing an event is allowed to have loosely related consequences (as with the parcel and truck). ## [[wiki/concepts/source/fragmented-metapatterns/layered-services|Command Query Responsibility Segregation]] (CQRS) ![In CQRS data passes through a pipeline formed of the command backend, OLTP database, OLAP database, and the query backend.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Pipelineliness-CQRS.png) When data from events is stored for a future use (as with the aggregation above), both the *type* and the *temporal order* are ignored, but the data *identity* may be retained. A [[wiki/concepts/source/fragmented-metapatterns/layered-services|*CQRS*-based system]] separates the paths for write (*command*) and read (*query*) requests, making a kind of data processing pipeline with the database, which stores events for an indeterminate amount of time, in the middle. It is the database that reshuffles the order of events, as a record it stores may be queried at any time, maybe in a year from its addition – or never at all. ## [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Model-View-Controller]] (MVC) ![Events from the mouse pass to the controller and the model, and those from the model - to the view and display.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Pipelineliness-MVC.png) [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Model-View-Controller*]] completely neglects the *type* and *identity* limitations. It is a coarse-grained pattern where the input source produces many kinds of events that go to the main module which does something and outputs another stream of events which have no obvious relation to the input. A mouse click does not necessarily result in a screen redraw, while a redraw may happen on timer without any user actions. In fact, this pattern conjoins two separate, short pipelines. ## Summary There are four architectures with unidirectional data flow, which is characteristic of [[wiki/concepts/source/basic-metapatterns/pipeline|*pipelines*]]: - [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipes and Filters*]], - [[wiki/concepts/source/basic-metapatterns/pipeline|*Choreographed Event-Driven Architecture* (*EDA*)]], - [[wiki/concepts/source/fragmented-metapatterns/layered-services|*Command (and) Query Responsibility Segregation* (*CQRS*)]], - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Model-View-Controller* (*MVC*)]]. The first two, being true pipelines, are built around data processing and transformation, while for the others it is just an aspect of implementation – their separation of input and output yields pairs of streams. --- title: "Real world inspirations for architectural patterns" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Analytics/Real-world inspirations for architectural patterns.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Analytics/Real-world%20inspirations%20for%20architectural%20patterns source_license_note: "See namespace README; preserve attribution and source links." --- # Real world inspirations for architectural patterns > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Analytics/Real-world inspirations for architectural patterns.md`. As architectural patterns are generally technology-independent, they must mostly be shaped by the foundational principles of software engineering. And because the same principles are likely at work at every level of a software system, we may expect similar structures to appear on many levels of software, given similar circumstances – which are not always attainable, for the system-wide scope (which means that there are multiple clients and libraries) and distributed nature (which deals with faults of individual components) of many patterns of systems architecture don’t have direct counterparts in smaller single-process software. Thus we expect to observe the fractal nature for the more generic patterns while narrowly specialized ones are present at only one or two scopes of software design. Another thought to consider is that it’s not in human nature to invent something entirely new – we are much more adept in imitating and combining whatever we see around us. That is why it’s so hard to find a genuine xenopsychology in literature or movies – to the extent that the eponymous Alien is just an overgrown [parasitoid wasp](https://en.wikipedia.org/wiki/Parasitoid_wasp). Hence there is another pathway to pursue – identifying the patterns which we know from software engineering in the world around us, as the authors of \[[wiki/concepts/source/appendices/books-referenced|[POSA2]]\] did decades ago. Let’s go! ## [[wiki/concepts/source/basic-metapatterns/basic-metapatterns|Basic metapatterns]] The [[wiki/concepts/source/basic-metapatterns/basic-metapatterns|basic topologies]] lay the foundation for any system by paving ways to *divide* it into components to *conquer* its [[wiki/concepts/source/foundations-of-software-architecture/modules-and-complexity|complexity]]. We are going to observe them everywhere around us: ### [[wiki/concepts/source/basic-metapatterns/monolith|Monolith]] ![A diagram of Monolith, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Monolith.png) [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] stands for encapsulation – we use the thing without looking inside: - You interact with your dog (or your smartphone) through their interface without thinking of their internals. - A function exposes its name, arguments and, probably, some comments. The implementation is hidden from its users. - An object has a set of public methods. - A module or a library exports several functions for use by its clients. - A program is configured through its command line parameters and managed through its [CLI](https://en.wikipedia.org/wiki/Command-line_interface). We don’t care how the Linux utilities (e.g. *top* or *cat*) work – we just run them. - A whole distributed system may be [hidden behind](https://comic.browserling.com/full-stack.png) a web page in your browser – and you never imagine its complexity unless you have worked on something of a kind. ### [[wiki/concepts/source/basic-metapatterns/shards|Shards]] ![A diagram of Shards, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Shards.png) [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]] is about having multiple instances of something, which often differ in their data: - A company employs many programmers to accelerate development of its projects. - Carrying two mobile phones from different operators fits this pattern as well. - This is how they make modern processors more powerful: by adding more cores, not by clocking them faster. - Objects in OOP are the perfect example of having multiple instances that vary in their data. - Running several shells in Linux is a kind of sharding. - A client application of a multi-user online game is a shard as well. ### [[wiki/concepts/source/basic-metapatterns/layers|Layers]] ![A diagram of Layered Architecture, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Layers.png) [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] is the separation of responsibilities between external and internal components: - In winter we wear soft clothes on our body, a warm sweater over them, and a wind-proof jacket as the external layer. - An object comprises high-level public methods, low-level privates, and data. - An OS has a UI which runs over user-space software over an OS kernel over device drivers over the hardware. - Your web browser executes a frontend which communicates to a backend which uses a database. ### [[wiki/concepts/source/basic-metapatterns/services|Services]] ![A diagram of Services, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Services.png) [[wiki/concepts/source/basic-metapatterns/services|*Services*]] boil down to composition and [separation of concerns](https://en.wikipedia.org/wiki/Separation_of_concerns): - We have legs, arms, and other narrowly specialized members. - A gadget contains specialized chips for the activities which it supports. - \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] advocates for an object to incorporate smaller, specialized objects (*composition over inheritance*). - Applications often delegate parts of their logic to specialized modules or libraries. - An OS dedicates a driver for each piece of hardware installed. Moreover, it provides many tools to its users – instead of tackling all the user needs within the kernel. - \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\] describes the way to subdivide a large system into (hopefully) loosely coupled components. ### [[wiki/concepts/source/basic-metapatterns/pipeline|Pipeline]] ![A diagram of Pipeline, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Pipeline.png) [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]] is about the stepwise transformation of data: - The pattern got its name from real-world plumbing. - You’ll see similar arrangements in [cellular metabolism](https://en.wikipedia.org/wiki/Metabolism). - It is the foundation of [[wiki/concepts/source/foundations-of-software-architecture/programming-and-architectural-paradigms|functional programming]]. - Linux command line tools are often skillfully chained into pipelines. - Hardware is full of pipelines: from [CPU](https://en.wikipedia.org/wiki/Instruction_pipelining) and [GPU](https://en.wikipedia.org/wiki/Graphics_pipeline) to audio and video processing. - Finally, a UI wizard passes its users through a series of screens. ## [[wiki/concepts/source/extension-metapatterns/extension-metapatterns|Extension metapatterns]] An [[wiki/concepts/source/extension-metapatterns/extension-metapatterns|extension pattern]] encapsulates one or two aspects of the system’s implementation. It may appear only at the design levels which have those particular aspects: ### [[wiki/concepts/source/extension-metapatterns/middleware|Middleware]] ![A diagram of Services with a middleware, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Middleware.png) A [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] abstracts scaling and/or intercommunication: - The network of post offices is a middleware – you push a letter into a mailbox and it automagically appears at its destination’s door. - A [bus depot](https://en.wikipedia.org/wiki/Bus_depot) may mean a bus garage which deploys as many buses as needed to service the traffic or a bus station where people come to have a ride, regardless of the exact vehicle model they’ll take. - Hardware is full of another kind of [buses](https://en.wikipedia.org/wiki/Bus_(computing)) that unify means of communication. - TCP and UDP sockets hide the details of the underlying network. - A distributed [[wiki/concepts/source/basic-metapatterns/services|actor framework]] allows an actor to address another actor without knowing where it is deployed. ### [[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Repository]] ![A diagram of Services with a shared repository, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Shared%20Repository.png) A [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]] provides data storage and/or data change notifications: - Everybody in the room may use a ~black~whiteboard to express and exchange their ideas. - An Internet forum works in a similar way – people post their arguments there for others to see them and get notified on answers. - RAM and CPU caches are kinds of shared repositories. CPU caches are [kept synchronized through notifications](https://en.wikipedia.org/wiki/Cache_coherency_protocols_(examples)). - [*Observer*](https://refactoring.guru/design-patterns/observer) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] is about getting notified when a shared object changes. - Services or service instances may share a database. ### [[wiki/concepts/source/extension-metapatterns/proxy|Proxy]] ![A diagram of Services with a proxy, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Proxy.png) A [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]] isolates a system from its environment by translating between the internal and external protocols and/or implementing generic aspects of communication: - You may need a translator to understand foreign people or have a secretary to deal with routine tasks. A local guide combines both roles. - An adapter makes several hardware plugs (or software frameworks) mutually interoperable. - Your Wi-Fi router is a proxy between your laptop and the Internet. - A compiler is a kind of a proxy between source code and bytecode. ### [[wiki/concepts/source/extension-metapatterns/orchestrator|Orchestrator]] ![A diagram of Services with an orchestrator, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Orchestrator.png) An [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] integrates several components by implementing high-level use cases and/or keeping the components in sync: - A taxi driver orchestrates their car’s internals. - A [*Facade*](https://refactoring.guru/design-patterns/facade) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] provides a high-level interface for a system while a [*Mediator*](https://refactoring.guru/design-patterns/mediator) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] integrates a system by spreading the changes initiated by the system’s components. - A linker composes a working program out of disjunct modules. ### [[wiki/concepts/source/extension-metapatterns/sandwich|Sandwich]] ![A diagram of Sandwich Architecture, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Sandwich.png) In [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]] a varied and segmented layer with the most valuable code is operated and held in place by other, cohesive layers: - A sandwich is an obvious example. - This is how we make down jackets and thermal insulation in general. - A [cell membrane](https://en.wikipedia.org/wiki/Cell_membrane) which includes many transporters and receptors looks exactly like that. ## [[wiki/concepts/source/fragmented-metapatterns/fragmented-metapatterns|Fragmented metapatterns]] A [[wiki/concepts/source/fragmented-metapatterns/fragmented-metapatterns|fragmented topology]] uses small specialized components to approach a case which is hard to resolve with more generic means. The high degree of specialization limits the number of available examples: ### [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Polyglot Persistence]] ![A diagram of Services with Polyglot Persistence, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Polyglot%20Persistence.png) [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]] is about having multiple containers for data: - A warehouse or a cargo ship has dedicated storage areas with separate facilities for combustible, toxic, and frozen goods. - A computer has CPU caches, RAM, flash, and hard drives for temporary or permanent data storage. - There are map, list, and array – each with its pros and cons. A large class would often use two or three kinds of containers, and not without reason. ### [Backends for Frontends]() ![A diagram of Services with Backends for Frontends, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Backends%20for%20Frontends.png) [*Backends for Frontends*]() is about treating different kinds of clients individually: - A bank is likely to reserve a couple of employees to serve rich clients. - A Wi-Fi router has many management interfaces: web, mobile application, CLI, and probably [TR-069](https://en.wikipedia.org/wiki/TR-069). - A multiplayer game may provide both desktop and mobile client applications. ### [Service-Oriented Architecture]() ![A diagram of Service-Oriented Architecture, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Service-Oriented%20Architecture.png) [*SOA*]() applies OOP techniques, including component reuse, to deal with complex systems: - That’s what you have inside your car. Many of its internals rely on the car’s battery for power supply instead of having a small battery installed inside every component. - Cities are built in the same way – schools, markets, and railways serve multiple houses. - It’s the same with user space of operating systems: there is a shared UI framework which interfaces with as-many-as-needed applications, each of which calls shared libraries (DLLs). ### [[wiki/concepts/source/fragmented-metapatterns/hierarchy|Hierarchy]] ![A diagram of Hierarchy, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Hierarchy.png) [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchy*]] distributes system’s complexity over multiple levels: - This is how large companies and armies are managed. - Large projects [[wiki/concepts/source/analytics/cohesers-and-decouplers|are made]] of services which contain modules which contain classes which contain methods. ## [[wiki/concepts/source/implementation-metapatterns/implementation-metapatterns|Implementation metapatterns]] An [[wiki/concepts/source/implementation-metapatterns/implementation-metapatterns|implementation metapattern]] highlights the peculiar internal arrangements of a component. Such patterns are deeply specialized: ### [[wiki/concepts/source/implementation-metapatterns/plugins|Plugins]] ![A diagram of Plugins Architecture, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Plugins.png) [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]] make a component’s behavior flexible through delegating its parts to small external additions: - This is how we use tools for our work – a man becomes a digger when given a shovel. - [*Strategy*](https://refactoring.guru/design-patterns/strategy) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] is the thing. ### [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Hexagonal Architecture]] ![A diagram of Hexagonal Architecture, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Hexagonal%20Architecture.png) [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] protects the internals of a system from its environment: - A drill or a screwdriver has replaceable bits. - A living cell is encapsulated by its [membrane](https://en.wikipedia.org/wiki/Cell_membrane) and relies on [protein adapters](https://en.wikipedia.org/wiki/Membrane_protein) for interactions with its environment. [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|A kind of *Hexagonal Architecture*]] was named after it. - [[wiki/concepts/source/extension-metapatterns/proxy|*OS Abstraction Layer* and *Hardware Abstraction Layer*]] in embedded systems or [[wiki/concepts/source/extension-metapatterns/proxy|*Anti-Corruption Layer*]] in \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\] are all about that. - The [*impure/pure/impure sandwich*](https://blog.ploeh.dk/2020/03/02/impureim-sandwich/) of functional programming is closely related. Here also, the core of the system cannot change anything outside of itself directly (any external communication relies on *adapters*) and it is [deterministic if single-threaded](http://ithare.com/chapter-vc-modular-architecture-client-side-on-debugging-distributed-systems-deterministic-logic-and-finite-state-machines/). ### [[wiki/concepts/source/implementation-metapatterns/microkernel|Microkernel]] ![A diagram of Microkernel, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Microkernel.png) [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]] shares the goods of resource providers among resource users: - It’s like a bank that takes money from the rich to distribute them among the poor. - This is what an OS is for. Its scheduler shares the CPU, the memory subsystem shares RAM, while the device drivers provide access to the peripherals. - Cloud services are based on sharing computational resources among clients. ### [[wiki/concepts/source/implementation-metapatterns/mesh|Mesh]] ![A diagram of Services over a mesh, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Mesh.png) [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]] resembles grassroots movements – self-organization and survival through redundancy: - Ants and bees are small, autonomous, and efficient. Their strength comes from their numbers. - Road networks and power grids don’t collapse if some of their components are damaged as they are highly redundant. - Torrents, mobile communications, and the Internet infrastructure are known for their robustness. ## Summary Architectural patterns have parallels in the natural world, our society and/or different levels of computer hardware and software. Learning about them helps us feel the driving forces behind the patterns and be more flexible and creative in both using the patterns which we already know and in devising new ones. --- title: "Sharing functionality or data among services" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Analytics/Comparison of architectural patterns/Sharing functionality or data among services.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Analytics/Comparison%20of%20architectural%20patterns/Sharing%20functionality%20or%20data%20among%20services source_license_note: "See namespace README; preserve attribution and source links." --- # Sharing functionality or data among services > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Analytics/Comparison of architectural patterns/Sharing functionality or data among services.md`. Architectural patterns manifest several ways of sharing functionality or data among their components. Let’s consider a basic example: calls to two pieces of business logic need to be logged, while the logger is doing something more complex than mere console prints. Additionally, the business logic also needs to access a system-wide counter. ## Direct call The simplest way to use a shared functionality (an *aspect*) is to call the module which implements it directly. This is possible if the users and the provider of the aspect reside in the same process, as in a [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] or module-based (single application) [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]]. Sharing data inside a process is similar, but usually requires some kind of protection, such as an [RW lock](https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock), around it to serialize access from multiple threads. ![A logger and counter accessible for direct calls inside a monolith or in an infrastructure layer of Layered Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Sharing-DirectCall.png) ## Make a dedicated service In a distributed system you can place the functionality or data to share into a separate service to be accessed over the network, yielding [*Service-Oriented Architecture*]() for shared utilities or a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]] / [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]] for shared data. ![A shared logger deployed as a service in Service-Oriented Architecture. A shared counter deployed as a stand-alone shared database in Polyglot Persistence.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Sharing-DedicatedService.png) ## Delegate the aspect A less obvious solution is [delegating](https://datatracker.ietf.org/doc/html/rfc1925) our needs to another layer of the system. To continue our example of logging, a [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]] may log the user requests and a [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] – the interservice communication. In many cases one of these generic components is configurable to record every call to the methods which we need to log – with no changes to the code! In a similar way a service may [[wiki/concepts/source/foundations-of-software-architecture/programming-and-architectural-paradigms|behave as a function]]: receive all the data it needs in an input message and send back all its work as an output – and let the database access remain the responsibility of its caller. ![Message loggers in a proxy and middleware. A request counter in a gateway of a pipeline.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Sharing-Delegate.png) ## Replicate it Finally, each user of a component can get its own replica. This is done implicitly in [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]], and explicitly in [[wiki/concepts/source/implementation-metapatterns/mesh|*Service Mesh*]] of [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]] for libraries or [[wiki/concepts/source/extension-metapatterns/shared-repository|*Data Grid*]] of [[wiki/concepts/source/implementation-metapatterns/mesh|*Space-Based Architecture*]] for data. Another case of replication is importing the same code in multiple services, which happens in [[wiki/concepts/source/basic-metapatterns/services|single-layer *Nanoservices*]]. ![Replicated loggers in each instance of a service in Shards, in code imported by every Nanoservice, and in sidecars of Microservices. A replicated counter in a Data Grid.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Conclusion/Sharing-Duplicate.png) ## Summary There are four basic ways to share functionality or data in a system: - Deploy everything together – messy yet fast and simple. - Place the component in question into a shared service to be accessed over the network – slow and less reliable. - Let another layer of the system both implement and use the needed function on your behalf – easy but generic, thus it may not always fit your code’s needs. - Make a copy of the component for each of its users – fast and reliable, but the copies are hard to keep in sync. --- title: "The heart of software architecture" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Analytics/The heart of software architecture/The heart of software architecture.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Analytics/The%20heart%20of%20software%20architecture/The%20heart%20of%20software%20architecture source_license_note: "See namespace README; preserve attribution and source links." --- # The heart of software architecture > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Analytics/The heart of software architecture/The heart of software architecture.md`. As the visible world boils down to protons and neutrons which stick together in various combinations, so too does the entirety of software architecture as a field of endeavor grow from an interplay of *cohesion* and *decoupling*. ## Contents: - [[wiki/concepts/source/analytics/cohesers-and-decouplers|Cohesers and decouplers]] - [[wiki/concepts/source/analytics/deconstructing-patterns|Deconstructing patterns]] - [[wiki/concepts/source/analytics/choose-your-own-architecture|Choose your own architecture]] --- title: "Acknowledgements" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Acknowledgements.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Acknowledgements source_license_note: "See namespace README; preserve attribution and source links." --- # Acknowledgements > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Acknowledgements.md`. I remember Avraham Fraenkel of DSPG who showed me what a true manager is like. Thanks to Alexey Nikitin and Maxim Medvedev of Keenetic who let me design a subsystem from scratch and see how it fared through years of heavy changes. It was from discussion with Sherry Ignatchenko aka [IT Hare](http://ithare.com/) that I learned the difference between [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|control]] and [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|data processing]] systems. Mark Richards read my [previous series of articles](https://medium.com/itnext/introduction-to-software-architecture-with-actors-part-1-89de6000e0d3) and encouraged me to press on with the classification of patterns. Kiarash Irandoust noticed [my articles on Medium](https://medium.com/@denyspoltorak) and invited me to publish them in [ITNEXT](https://itnext.io/) where many more people could see them. Thanks to Max Grom and other participants of the Ukrainian software architecture chat for hours of heated discussions about the meaning of patterns, which resulted in several analytical chapters of the book, and also for guiding me through the intricacies of DDD. Thanks to Vitaliy Ovetskyy for reviewing this book. Many thanks to Lars Noodén for editing the entire book and all its new chapters. I must thank my mother and our neighbor Halyna for helping me throughout the war. Thanks to Yaroslav for his support. I want to thank everybody who prayed for me. This book was made possible by many people who sacrificed their happy years, their limbs, and their lives to protect those who stayed behind. --- title: "Appendices" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Appendices.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Appendices source_license_note: "See namespace README; preserve attribution and source links." --- # Appendices > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Appendices.md`. Though I don’t think that you should be reading this, this book contains a few useful appendices: - The [[wiki/concepts/source/appendices/books-referenced|list of books referenced]] in the text. Many of them are actually good to read. - The [[wiki/concepts/source/appendices/evolutions-of-architectures|list of evolutions]] of architectures. It is very boring to read and should rather be consulted as need arises. - A short [[wiki/concepts/source/appendices/format-of-a-metapattern|description of the structure of the metapattern chapters]]. - The [[wiki/concepts/source/appendices/glossary|glossary]], probably redundant. - The [[wiki/concepts/source/appendices/history-of-changes|history of changes]] (revisions). - The [[wiki/concepts/source/appendices/index-of-patterns|index of patterns]] described in the book. This one may be handy. ## Contents: - [[wiki/concepts/source/appendices/acknowledgements|Acknowledgements]] - [[wiki/concepts/source/appendices/books-referenced|Books referenced]] - [[wiki/concepts/source/appendices/copyright|Copyright]] - [[wiki/concepts/source/appendices/disclaimer|Disclaimer]] - [[wiki/concepts/source/appendices/evolutions-of-architectures|Evolutions of architectures]] - [[wiki/concepts/source/appendices/evolutions-of-a-monolith-that-lead-to-shards|Evolutions of a Monolith that lead to Shards]] - [[wiki/concepts/source/appendices/evolutions-of-a-monolith-that-result-in-layers|Evolutions of a Monolith that result in Layers]] - [[wiki/concepts/source/appendices/evolutions-of-a-monolith-that-make-services|Evolutions of a Monolith that make Services]] - [[wiki/concepts/source/appendices/evolutions-of-a-monolith-that-rely-on-plugins|Evolutions of a Monolith that rely on Plugins]] - [[wiki/concepts/source/appendices/evolutions-of-shards-that-share-data|Evolutions of Shards that share data]] - [[wiki/concepts/source/appendices/evolutions-of-shards-that-share-logic|Evolutions of Shards that share logic]] - [[wiki/concepts/source/appendices/evolutions-of-layers-that-make-more-layers|Evolutions of Layers that make more layers]] - [[wiki/concepts/source/appendices/evolutions-of-layers-that-help-large-projects|Evolutions of Layers that help large projects]] - [[wiki/concepts/source/appendices/evolutions-of-layers-to-improve-performance|Evolutions of Layers to improve performance]] - [[wiki/concepts/source/appendices/evolutions-of-layers-to-gain-flexibility|Evolutions of Layers to gain flexibility]] - [[wiki/concepts/source/appendices/evolutions-of-services-that-restructure-services|Evolutions of Services that restructure services]] - [[wiki/concepts/source/appendices/evolutions-of-services-that-add-layers|Evolutions of Services that add layers]] - [[wiki/concepts/source/appendices/evolutions-of-a-pipeline|Evolutions of a Pipeline]] - [[wiki/concepts/source/appendices/evolutions-of-a-middleware|Evolutions of a Middleware]] - [[wiki/concepts/source/appendices/evolutions-of-a-shared-repository|Evolutions of a Shared Repository]] - [[wiki/concepts/source/appendices/evolutions-of-a-proxy|Evolutions of a Proxy]] - [[wiki/concepts/source/appendices/evolutions-of-an-orchestrator|Evolutions of an Orchestrator]] - [[wiki/concepts/source/appendices/evolutions-of-a-sandwich|Evolutions of a Sandwich]] - [[wiki/concepts/source/appendices/format-of-a-metapattern|Format of a metapattern]] - [[wiki/concepts/source/appendices/glossary|Glossary]] - [[wiki/concepts/source/appendices/history-of-changes|History of changes]] - [[wiki/concepts/source/appendices/index-of-patterns|Index of patterns]] --- title: "Books referenced" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Books referenced.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Books%20referenced source_license_note: "See namespace README; preserve attribution and source links." --- # Books referenced > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Books referenced.md`. **DDD** – Domain-Driven Design: Tackling Complexity in the Heart of Software. *Eric Evans. Addison-Wesley (2003).* (Most of these patterns are also well-described in \[[LDDD](#lddd)\]) **DDIA** – Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems. *Martin Kleppmann. O’Reilly Media, Inc. (2017).* **DDS** – Designing Distributed Systems: Patterns and Paradigms for Scalable, Reliable Services. *Brendan Burns. O’Reilly Media, Inc. (2018).* **DEDS** – Designing Event-Driven Systems: Concepts and Patterns for Streaming Services with Apache Kafka. *Ben Stopford. O’Reilly Media, Inc. (2018).* **EIP** – Enterprise Integration Patterns. *Gregor Hohpe and Bobby Woolf. Addison-Wesley (2003).* **FSA** – Fundamentals of Software Architecture: An Engineering Approach. *Mark Richards and Neal Ford. O’Reilly Media, Inc. (2020).* **GoF** – Design Patterns: Elements of Reusable Object-Oriented Software. *Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Addison-Wesley (1994).* **LDDD** – Learning Domain-Driven Design: Aligning Software Architecture and Business Strategy. *Vlad Khononov. O’Reilly Media, Inc. (2021).* (Duplicates \[[DDD](#ddd)\] thus I marked as \[LDDD\] only patterns not covered by \[[DDD](#ddd)\]) **MP** – Microservices Patterns: With Examples in Java. *Chris Richardson. Manning Publications (2018)*. **PEAA** – Patterns of Enterprise Application Architecture. *Martin Fowler. Addison-Wesley Professional (2002).* **POSA1** – Pattern-Oriented Software Architecture Volume 1: A System of Patterns. *Frank Buschmann, Regine Meunier, Hans Rohnert, Peter Sommerlad and Michael Stal. John Wiley & Sons, Inc. (1996).* **POSA2** – Pattern-Oriented Software Architecture Volume 2: Patterns for Concurrent and Networked Objects. *Douglas C. Schmidt, Michael Stal, Hans Rohnert, Frank Buschmann. John Wiley & Sons, Inc. (2000).* **POSA3** – Pattern-Oriented Software Architecture Volume 3: Patterns for Resource Management. *Michael Kircher, Prashant Jain. John Wiley & Sons, Inc. (2004).* **POSA4** – Pattern-Oriented Software Architecture Volume 4: A Pattern Language for Distributed Computing. *Frank Buschmann, Kevlin Henney, Douglas C. Schmidt. John Wiley & Sons, Ltd. (2007).* **POSA5** – Pattern Oriented Software Architecture Volume 5: On Patterns and Pattern Languages. *Frank Buschmann, Kevlin Henney, Douglas C. Schmidt*. *John Wiley & Sons, Ltd. (2007).* **SAHP** – Software Architecture: The Hard Parts: Modern Trade-Off Analyses for Distributed Architectures. *Neal Ford, Mark Richards, Pramod Sadalage, and Zhamak Dehghani. O’Reilly Media, Inc. (2021).* **SAP** – Software Architecture Patterns. *Mark Richards. O’Reilly Media, Inc. (2015).* (All of the architectures referenced here are in \[[FSA](#fsa)\] as well, but \[SAP\] is free) --- title: "Copyright" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Copyright.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Copyright source_license_note: "See namespace README; preserve attribution and source links." --- # Copyright > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Copyright.md`. ## Attribution 4.0 International By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. ### Section 1 – Definitions. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. Licensor means the individual(s) or entity(ies) granting rights under this Public License. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. ### Section 2 – Scope. License grant . -- Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: -- -- reproduce and Share the Licensed Material, in whole or in part; and -- -- produce, reproduce, and Share Adapted Material. -- Exceptions and Limitations . For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. -- Term . The term of this Public License is specified in Section 6(a) . -- Media and formats; technical modifications allowed . The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. -- Downstream recipients . -- -- Offer from the Licensor – Licensed Material . Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. -- -- No downstream restrictions . You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. -- No endorsement . Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i) . Other rights . -- Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. -- Patent and trademark rights are not licensed under this Public License. -- To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. ### Section 3 – License Conditions. Your exercise of the Licensed Rights is expressly made subject to the following conditions. Attribution . -- If You Share the Licensed Material (including in modified form), You must: -- -- retain the following if it is supplied by the Licensor with the Licensed Material: -- -- -- identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); -- -- -- a copyright notice; -- -- -- a notice that refers to this Public License; -- -- -- a notice that refers to the disclaimer of warranties; -- -- -- a URI or hyperlink to the Licensed Material to the extent reasonably practicable; -- -- indicate if You modified the Licensed Material and retain an indication of any previous modifications; and -- -- indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. -- You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. -- If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. -- If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License. ### Section 4 – Sui Generis Database Rights. Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. ### Section 5 – Disclaimer of Warranties and Limitation of Liability. Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You. To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. ### Section 6 – Term and Termination. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: -- automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or -- upon express reinstatement by the Licensor. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. Sections 1 , 5 , 6 , 7 , and 8 survive termination of this Public License. ### Section 7 – Other Terms and Conditions. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. ### Section 8 – Interpretation. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. --- title: "Disclaimer" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Disclaimer.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Disclaimer source_license_note: "See namespace README; preserve attribution and source links." --- # Disclaimer > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Disclaimer.md`. THIS BOOK IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTY OF FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, FAILING AN INTERVIEW, BEING RIDICULED, OR LOSING YOUR JOB) ARISING IN ANY WAY OUT OF THE USE OF THIS BOOK, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --- title: "Evolutions of a Middleware" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Evolutions of architectures/Evolutions of a Middleware.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Evolutions%20of%20architectures/Evolutions%20of%20a%20Middleware source_license_note: "See namespace README; preserve attribution and source links." --- # Evolutions of a Middleware > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Evolutions of architectures/Evolutions of a Middleware.md`. A [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] is unlikely to be removed (though it still may be replaced) once it is built into a system. There are few evolutions as a *Middleware* is a third-party product and is unlikely to be messed with: - If the *Middleware* in use does not fit the preferred mode of communication between some of your services, there is an option to deploy a second specialized *Middleware*. - If several existing systems need to be merged, that is accomplished by adding yet another layer of *Middleware*, resulting in a [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Bottom-Up Hierarchy (Bus of Buses)*]]. ## Add a secondary Middleware ![A specialized middleware added to a system that already has a generic middleware.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Middleware%20add%20Middleware.png) Patterns: [[wiki/concepts/source/extension-metapatterns/middleware|Middleware]]. Goal: support specialized communication between [[wiki/concepts/source/basic-metapatterns/shards|scaled]] services. Prerequisite: the system relies on a *Middleware* for scaling. If the current *Middleware* is too generic for the system’s needs, you can add another one for specialized communication. The new *Middleware* does not manage the instances of the services. Pros: - Supports specialized communication with no need to write code for tracking the instances of services. Cons: - You still need to notify the new *Middleware* when an instance of a service is created or dies. - There is an extra component to administer. ## Merge two systems by building a Bottom-Up Hierarchy ![A low-level middleware interconnects several higher-level middlewares.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Middleware%20to%20Bus%20of%20Buses.png) Patterns: [[wiki/concepts/source/fragmented-metapatterns/hierarchy|Bottom-up Hierarchy]] ([[wiki/concepts/source/fragmented-metapatterns/hierarchy|Hierarchy]], [[wiki/concepts/source/extension-metapatterns/middleware|Middleware]]). Goal: integrate two systems without a heavy refactoring. Prerequisite: both systems use *Middleware*s. If we cannot change the way each subsystem’s services use its *Middleware*, we should add a new *Middleware* to connect the existing *Middleware*s. Pros: - No need to touch anything in the existing services. Cons: - Performance suffers from the double conversion between protocols. - There is a new component to fail (miserably). --- title: "Evolutions of a Monolith that lead to Shards" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Evolutions of architectures/Evolutions of a Monolith that lead to Shards.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Evolutions%20of%20architectures/Evolutions%20of%20a%20Monolith%20that%20lead%20to%20Shards source_license_note: "See namespace README; preserve attribution and source links." --- # Evolutions of a Monolith that lead to Shards > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Evolutions of architectures/Evolutions of a Monolith that lead to Shards.md`. One of the main drawbacks of the monolithic architecture is its lack of scalability – a single running instance of your system may not be enough to serve all its clients no matter how many resources you add in. If that is the case, you should consider [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]] – *multiple instances* of a monolith. There are following options: - Self-managed [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]] – each instance owns a part of the system’s data and may communicate with every other instance (forming a [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]]). - *Shards* with a [[wiki/concepts/source/extension-metapatterns/proxy|*Sharding Proxy*]] – each instance owns a part of the system’s data and relies on the external component to choose the right shard for the client. - A [[wiki/concepts/source/basic-metapatterns/shards|*Pool of stateless instances*]] with a [[wiki/concepts/source/extension-metapatterns/proxy|*Load Balancer*]] and a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Database*]] – any instance can process any request, but the database limits the throughput. - A [[wiki/concepts/source/basic-metapatterns/shards|*stateful instance per client*]] with an external persistent storage – each instance owns the data related to its client and runs in a virtual environment (i.e. web browser or an [[wiki/concepts/source/basic-metapatterns/services|actor framework]]). ## Implement a Mesh of self-managed shards ![Several instances of a monolith are run as intercommunicating shards, each of which holds a subset of the system's data.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Mesh%20of%20Shards.png) Patterns: [[wiki/concepts/source/basic-metapatterns/shards|Sharding]] ([[wiki/concepts/source/basic-metapatterns/shards|Shards]]), [[wiki/concepts/source/implementation-metapatterns/mesh|Mesh]]. Goal: scale a low-latency application with weakly coupled data. Prerequisite: the application’s data can be split into semi-independent parts. It is possible to run several instances of an application (*shards*), with each instance owning a part of the data. For example, a chat may deploy 16 servers, each responsible for a subset of users whose hashed names end in specific 4 bits (0 to 15). However, some scenarios (e.g. renaming a user or adding a contact) may require the shards to intercommunicate. And the more coupled the shards become, the more complex a *mesh engine* is required to support their interactions, up to implementing distributed transactions, at which point you will have written a distributed database. Pros: - The system scales to a predefined number of instances. - Perfect fault tolerance if [[wiki/concepts/source/basic-metapatterns/shards|replication]] and error recovery are implemented. - Latency is kept low. Cons: - Direct communication between the shards (the mesh engine logic) is likely to be quite complex. - Intershard transactions are slow and/or complicated and may corrupt the data if undertested. - A client must know which shard owns its data to benefit from low latency. An [[wiki/concepts/source/extension-metapatterns/proxy|*Ambassador*]] [[wiki/concepts/source/extension-metapatterns/proxy|*Sharding Proxy*]] may be used on the client’s side. ## Split the data into isolated shards and add a Sharding Proxy ![Multiple instances of a monolith, each a subset of the system's data, are run behind a sharding proxy.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Isolated%20Shards%20with%20Load%20Balancer.png) Patterns: [[wiki/concepts/source/basic-metapatterns/shards|Sharding]] ([[wiki/concepts/source/basic-metapatterns/shards|Shards]]), [[wiki/concepts/source/extension-metapatterns/proxy|Sharding Proxy]] ([[wiki/concepts/source/extension-metapatterns/proxy|Proxy]]), [[wiki/concepts/source/basic-metapatterns/layers|Layers]]. Goal: scale an application with sliceable data. Prerequisite: the application’s data can be sliced into independent, self-sufficient parts. If all the data a user operates on, directly or indirectly, is never accessed by other users, then multiple independent instances (*shards*) of the application can be deployed, each owning an instance of a database with data for a subset of the system’s users. A special kind of *Proxy*, called *Sharding Proxy*, redirects a user request to a shard that has the user’s data. Pros: - Perfect static (predefined number of instances) scalability. - Failure of one shard does not affect the users of the other shards. - [*Canary Release*](https://martinfowler.com/bliki/CanaryRelease.html) is supported. Cons: - The *Sharding Proxy* is a single point of failure unless [[wiki/concepts/source/basic-metapatterns/shards|*replicated*]], and it also increases latency unless deployed as an [[wiki/concepts/source/extension-metapatterns/proxy|*Ambassador*]]. ## Separate the data layer and add a Load Balancer ![A monolith is transformed into stateless instances which run behind a load balancer and access a shared database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Stateless%20Shards%20with%20Shared%20DB.png) Patterns: [[wiki/concepts/source/basic-metapatterns/shards|Pool]] ([[wiki/concepts/source/basic-metapatterns/shards|Shards]]), [[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Repository]], [[wiki/concepts/source/extension-metapatterns/proxy|Load Balancer]] ([[wiki/concepts/source/extension-metapatterns/proxy|Proxy]]), [[wiki/concepts/source/basic-metapatterns/layers|Layers]]. Goal: achieve limited scalability and elasticity with little effort. Prerequisite: the persistent data is of manageable size. As data moves into a dedicated layer ([[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Database*]] or [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared File System*]]), the application becomes stateless and instances of it can be created and destroyed dynamically depending on the system’s load. However, the *Shared Repository* becomes the system’s bottleneck unless [[wiki/concepts/source/extension-metapatterns/shared-repository|*Space-Based Architecture*]] is used. Pros: - Easy to implement. - Dynamic scalability. - Failure of a single instance only affects a few users transiently. - [*Canary Release*](https://martinfowler.com/bliki/CanaryRelease.html) is supported. Cons: - The database limits the system’s scalability and performance. - The *Load Balancer* and *Shared Repository* increase latency and are single points of failure. ## Dedicate an instance to each client ![Each user is allocated a temporary instance of a subsystem which loads their data at the start of the session and persists any changes to the database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Instance%20per%20Client.png) Patterns: [[wiki/concepts/source/basic-metapatterns/shards|Create on Demand]] ([[wiki/concepts/source/basic-metapatterns/shards|Shards]]), [[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Repository]], [[wiki/concepts/source/implementation-metapatterns/microkernel|Virtualizer]] ([[wiki/concepts/source/implementation-metapatterns/microkernel|Microkernel]]), [[wiki/concepts/source/basic-metapatterns/layers|Layers]]. Goal: very low latency, dynamic scalability, and failure isolation. Prerequisite: each client’s data is small and independent of other clients. Each client gets an instance of the application which preloads their data into memory. This way all the data is instantly accessible and a processing fault associated with one client never affects the other clients. As systems tend to have thousands to millions of clients, it is inefficient to spawn a process per client. Instead, more lightweight entities are used: a web app in a browser or an *actor* in a [[wiki/concepts/source/basic-metapatterns/services|distributed framework]]. Pros: - Nearly perfect dynamic scalability (limited by the persistence layer). - Good latency as everything happens in memory. - Fault isolation is one of the features of distributed frameworks. - The frameworks are available out of the box. Cons: - Virtualization frameworks tend to introduce a performance penalty. - You may need to learn an uncommon technology. - Scalability and performance are still limited by the shared persistence layer. ## Further steps In most cases *sharding* does not change much inside the application, thus the common evolutions for [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] (to [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]], [[wiki/concepts/source/basic-metapatterns/services|*Services*]], and [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]]) remain applicable after sharding. We’ll focus on the scalability of the resulting architectures: - [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] can be scaled (often to a dramatic extent) and deployed individually, as [[wiki/concepts/source/foundations-of-software-architecture/forces-asynchronicity-and-distribution|exemplified by the *Three-Tier Architecture*]]. - [[wiki/concepts/source/basic-metapatterns/services|*Services*]] allow for subdomains to scale independently with the help of [[wiki/concepts/source/extension-metapatterns/proxy|*Load Balancers*]] or a [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]]. They also improve performance of the data storage as each service uses its own database which is often chosen to best fit its specific needs. - Granular scaling also applies to [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipelines*]], but in many cases it does not make much sense because pipeline components tend to be lightweight and stateless, making it easy to scale the pipeline as a whole. ![Diagrams of Layers with individual scaling, Services with a middleware and individual scaling, and pipeline scaled as a whole.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Shards%20-%20Further%201.png) A few specific evolutions of [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]] deal with the drawbacks: - [[wiki/concepts/source/implementation-metapatterns/mesh|*Space-Based Architecture*]] reimplements [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]] with a [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]]. Its main goal is to make the [[wiki/concepts/source/basic-metapatterns/layers|data layer]] dynamically scalable, but the results are limited by the [CAP theorem](https://en.wikipedia.org/wiki/CAP_theorem) thus, depending on the mode of action, it can either provide very high performance with no consistency guarantees for a small dataset, or reasonable performance for a huge dataset. It blends the best features of stateful [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]] and [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Database*]] (being an option for either pattern to evolve to) but may be quite expensive to run and lacks support for analytical queries. - [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]], a mirror image of [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Database*]], is another option for implementing use cases that deal with data of multiple shards without the need for the shards to intercommunicate. Stateless *Orchestrators* scale perfectly but may corrupt the data if two of them write to an overlapping set of records. ![Diagrams of Space-Based Architecture that replicates data and Shards with multiple orchestrators.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Shards%20-%20Further%202.png) --- title: "Evolutions of a Monolith that make Services" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Evolutions of architectures/Evolutions of a Monolith that make Services.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Evolutions%20of%20architectures/Evolutions%20of%20a%20Monolith%20that%20make%20Services source_license_note: "See namespace README; preserve attribution and source links." --- # Evolutions of a Monolith that make Services > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Evolutions of architectures/Evolutions of a Monolith that make Services.md`. The final major drawback of [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] is the cohesiveness of its code. The rapid start of development begets a major obstacle for project growth: every developer needs to know the entire codebase to be productive, and changes made by individual developers overlap and may break each other. Such distress is usually solved by dividing the project into components along *subdomain boundaries* (which tend to match [*bounded contexts*](https://martinfowler.com/bliki/BoundedContext.html) \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]). However, that requires a lot of work, and good boundaries and APIs are [hard to design](https://martinfowler.com/bliki/MonolithFirst.html). Thus many organizations prefer a slower iterative transition. - A *Monolith* can be split into [[wiki/concepts/source/basic-metapatterns/services|*Services*]] right away. - Or only the new features may be added as new services. - Or the weakly coupled parts of existing functionality may be separated, one at a time. - Some domains allow for sequential [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|data processing]] best described by [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipelines*]]. ![In Services a single component executes a client request while in Pipeline there is no use case owner.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith_%20Services%20and%20Pipeline.png) ## Divide into Services ![A monolith is subdivided into services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Services.png) Patterns: [[wiki/concepts/source/basic-metapatterns/services|Services]]. Goal: facilitate development by multiple teams, improve the code, and decouple the qualities of subdomains. Prerequisite: there is a natural way to split the business logic into loosely coupled subdomains, and the subdomain boundaries are sure to never change in the future. Splitting a [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] into *Services* by subdomain [is risky in the early stages of a project](https://martinfowler.com/bliki/MonolithFirst.html) while the domain understanding is evolving ([[wiki/concepts/source/basic-metapatterns/services|in-process *modules*]] are less risky but provide fewer benefits). However, this is the way to go as soon as the codebase becomes unwieldy due to its size. Pros: - Supports multiple, relatively independent and specialized development teams. - Lowers the penalty imposed by the project’s size and complexity on the velocity of development and product quality. - Each team may choose the best fitting technologies for its component. - The services can differ in their [qualities](https://en.wikipedia.org/wiki/List_of_system_quality_attributes). - Flexible deployment and scaling. - A certain degree of error tolerance for asynchronous systems. Cons: - It takes a lot of work to split a *Monolith*. - Any future changes to the overall structure of the domain will be hard to implement. - Sharing data between services is complicated and error-prone. - System-wide use cases are hard to understand and debug. - There is a moderate performance penalty for system-wide use cases. ## Add or split a service ![A service is split from a monolith.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20Split%20Service.png) Patterns: [[wiki/concepts/source/basic-metapatterns/services|Services]]. Goal: [stop digging](https://en.wikipedia.org/wiki/Law_of_holes), get some work for novices who don’t know the entire project. Prerequisite: the new functionality you are adding or the part you are splitting is weakly coupled to the bulk of the existing *Monolith*. If your [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] is already hard to manage, but a new functionality is needed, you can try dedicating a separate service to the new feature(s). This way the *Monolith* does not become larger – it is even possible that you will move a part of its code to the newly established service. If you are not adding a new feature but need to change an old one – use the chance to make the existing *Monolith* smaller by first separating the functionality which you are going to change from its bulk. At the very minimum this two-step process lowers the probability of breaking something unrelated to the required changes of behavior. Pros: - The legacy code does not increase in size and complexity. - The new service is transferred to a dedicated team which does not need to know the legacy system or use the old technologies. - The new service can be experimented with and even rewritten from scratch. - The likely faults of the new service won’t crash the main application. - The new service can be tested and deployed in isolation. - The new service can be scaled independently. Cons: - The new service will have a hard time sharing data or code with the main application. - Use cases that involve both the new service and the old application are hard to debug. - There is a moderate performance penalty for using the service. Further steps: - [Continue disassembling](https://martinfowler.com/bliki/StranglerFigApplication.html) the *Monolith*. ## Divide into a Pipeline ![A Monolith is transformed into a pipeline.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Pipeline.png) Patterns: [[wiki/concepts/source/basic-metapatterns/pipeline|Pipeline]] ([[wiki/concepts/source/basic-metapatterns/services|Services]]). Goal: decrease the complexity of the code, make it easy to experiment with the steps of data processing, and distribute the task over multiple CPU cores, processors, or computers. Prerequisite: the domain can be represented as a sequence of coarse-grained data processing steps. If you can treat your application as a chain of independent steps that transform the input data, you can rely on the OS to schedule them, and you can also dedicate a development team to each of the steps. This is the default solution for a system that [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|processes a stream]] of a single type of data (video, audio, or measurements). It has excellent flexibility. Pros: - Nearly abolishes the influence of the project size on development velocity. - The project’s teams become almost independent. - Flexible deployment and scaling. - Naturally supports event replay for reproducing bugs, testing, or benchmarking individual components. - It is possible to have multiple implementations for each of the steps of data processing. - Does not require any manual scheduling or thread synchronization. Cons: - Latency may skyrocket. - As the number of supported scenarios grows, so does the number of components and *pipelines*. Soon there’ll be nobody who understands the system as a whole. ## Further steps As your knowledge of the domain and your business requirements change, you may need to move some functionality between the services to keep them loosely coupled. Sometimes you have to merge two or three services together. So it goes. Systems of [[wiki/concepts/source/basic-metapatterns/services|*Services*]] or [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipelines*]] are quite often extended with special kinds of [[wiki/concepts/source/basic-metapatterns/layers|*layers*]]: - [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] takes care of deployment, [[wiki/concepts/source/basic-metapatterns/layers|intercommunication]], and scaling of services. - [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]] lets services operate on and communicate through [[wiki/concepts/source/foundations-of-software-architecture/shared-data|shared data]]. - [[wiki/concepts/source/extension-metapatterns/proxy|*Proxies*]] are ready-to-use components that add generic functionality to the system. - [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] encapsulates [[wiki/concepts/source/basic-metapatterns/layers|use cases]] that involve multiple services, so that the services don’t need to know about each other. ![Diagrams of Services with a proxy, Services with an orchestrator, Services with a middleware, and Services with a shared database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Services%20-%20Further%201.png) Each service, being a smaller *Monolith*, may evolve on its own. Most of the evolutions of [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] are applicable. The most common examples include: - [[wiki/concepts/source/basic-metapatterns/services|*Scaled (Sharded) Service*]] with a [[wiki/concepts/source/extension-metapatterns/proxy|*Load Balancer*]] and [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Database*]] to support high load. - [[wiki/concepts/source/basic-metapatterns/services|*Layered Service*]] to improve the code structure and decouple the deployment of parts of the service. - [[wiki/concepts/source/basic-metapatterns/services|*Cell*]] (*Service of Services*) to involve multiple teams and technologies within a single subdomain. - [[wiki/concepts/source/basic-metapatterns/services|*Hexagonal Service*]] to escape vendor lock-in. ![Diagrams of a scaled service, layered service, Cell, and a service that implements Hexagonal Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Services%20-%20Further%202.png) --- title: "Evolutions of a Monolith that rely on Plugins" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Evolutions of architectures/Evolutions of a Monolith that rely on Plugins.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Evolutions%20of%20architectures/Evolutions%20of%20a%20Monolith%20that%20rely%20on%20Plugins source_license_note: "See namespace README; preserve attribution and source links." --- # Evolutions of a Monolith that rely on Plugins > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Evolutions of architectures/Evolutions of a Monolith that rely on Plugins.md`. The last group of evolutions which we will review does not really change the monolithic nature of the application. Instead, its goal is to improve the *customizability* of the [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]]: - Vanilla [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]] are the most direct approach which relies on tailorable bits of logic. - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] is a subtype of *Plugins* which is all about isolating the main code from any third-party components which it uses. - [[wiki/concepts/source/implementation-metapatterns/microkernel|*Scripts*]] is a kind of [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]] – yet another subtype of *Plugins* – which gives users of the system full control over its behavior. ## Support plugins ![Plugins customize the monolith's behavior.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Plugins.png) Patterns: [[wiki/concepts/source/implementation-metapatterns/plugins|Plugins]]. Goal: simplify the customization of the application’s behavior. Prerequisite: several aspects need to vary from customer to customer. *Plugins* create points of access to the system that allow engineers to collect data and govern select aspects of the system’s behavior without having to learn the system’s implementation. Pros: - The system’s behavior can be modified by internal and external programmers who don’t know its internal details. - Customized versions become much easier to release and support. Cons: - Extensive changes in the code may be required to expose the tunable aspects of the system. - Testability becomes poor because of the large number of possible variants. - Performance is likely to degrade. ## Isolate dependencies with Hexagonal Architecture ![The database, external libraries, and a protocol support component are separated from the business logic and isolated with adapters.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Hexagonal.png) Patterns: [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Hexagonal Architecture]] ([[wiki/concepts/source/implementation-metapatterns/plugins|Plugins]]). Goal: isolate the business logic from its external dependencies. Prerequisite: there are third-party or frequently changing components in the system. The main business logic will communicate with any external component through an API or SPI defined in the terms of the business logic itself. This way it will not depend on anything at all, and any component will be replaceable with another implementation or a [stub/mock](https://stackoverflow.com/questions/3459287/whats-the-difference-between-a-mock-stub). Pros: - Vendor lock-in is ruled out. - A component may be replaced through to the very end of the system’s life cycle. - Stubs and mocks are supported for testing and local or early development. - It is possible to provide multiple implementations of a component. Cons: - Some extra effort is required to define and use the interfaces. - There is performance degradation, mostly due to lost optimization opportunities. ## Add an Interpreter (support Scripts) ![The high-level logic is rewritten as scripts which are run by an interpreter.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Interpreter.png) Patterns: [[wiki/concepts/source/implementation-metapatterns/microkernel|Scripts aka Interpreter]] ([[wiki/concepts/source/implementation-metapatterns/microkernel|Microkernel]] ([[wiki/concepts/source/implementation-metapatterns/plugins|Plugins]])). Goal: allow the system’s users to implement their own business logic. Prerequisite: the domain is representable in high-level terms. *Interpreter* lets the users develop high-level business logic from scratch by programming interactions of pre-defined building blocks, which are implemented in the core of the system. That provides unparalleled flexibility at the cost of degraded performance and design complexity. Pros: - Perfect flexibility and customizability for every user. - The high-level business logic is written in high-level terms, making it fast to develop and easy to grasp. Cons: - Requires much effort to design correctly. - There may be a heavy performance penalty if the API is overly fine-grained. - Testability may be an issue. --- title: "Evolutions of a Monolith that result in Layers" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Evolutions of architectures/Evolutions of a Monolith that result in Layers.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Evolutions%20of%20architectures/Evolutions%20of%20a%20Monolith%20that%20result%20in%20Layers source_license_note: "See namespace README; preserve attribution and source links." --- # Evolutions of a Monolith that result in Layers > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Evolutions of architectures/Evolutions of a Monolith that result in Layers.md`. Another drawback of [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] is its … er … monolithism. The entire application exposes a single set of qualities and all its parts (if they ever emerge) are deployed together. However, life awards flexibility: parts of a system may benefit from being written in varying languages and styles, and deployed with different frequency and amount of testing, sometimes to specific hardware or end users’ devices. They may need to [[wiki/concepts/source/foundations-of-software-architecture/forces-asynchronicity-and-distribution|vary in security and scalability]] as well. Enter [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] – the subdivision by the *level of abstractness*: - Most *Monoliths* can be divided into three or four [[wiki/concepts/source/basic-metapatterns/layers|*layers*]]. - It is common to see the database separated from the main application. - [[wiki/concepts/source/extension-metapatterns/proxy|*Proxies*]] (e.g. [[wiki/concepts/source/extension-metapatterns/proxy|*Firewall*]], [[wiki/concepts/source/extension-metapatterns/proxy|*Cache*]], and [[wiki/concepts/source/extension-metapatterns/proxy|*Reverse Proxy*]]) are common additions to the system. - An [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] adds a layer of indirection to simplify the system’s API for its clients. ## Divide into Layers ![A monolith is split into application, domain and database layers.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Layers.png) Patterns: [[wiki/concepts/source/basic-metapatterns/layers|Layers]]. Goal: let parts of the system vary in qualities, improve the structure of the code. Prerequisite: there is a natural way to separate the high-level logic from the low level implementation details and dependencies. Most systems apply *layering* by default as it grants a lot of flexibility at very little cost. [[wiki/concepts/source/basic-metapatterns/layers|Common sets of layers]] are: [[wiki/concepts/source/basic-metapatterns/layers|UI]], [[wiki/concepts/source/basic-metapatterns/layers|tasks]] (orchestration), [[wiki/concepts/source/basic-metapatterns/layers|domain]] (detailed business rules) and infrastructure (database and libraries) or [[wiki/concepts/source/basic-metapatterns/layers|frontend, backend and data]]. Pros: - It is a natural way to specialize and decouple two or three development teams. - The layers may vary in virtually any quality: - They are deployed and scaled independently. - They may run on different hardware, including client devices. - They may vary in programming language, paradigm, and release cycle. - Most changes are isolated to a single layer. - Layering opens a way to many evolutions of the system. - The code [[wiki/concepts/source/foundations-of-software-architecture/modules-and-complexity|becomes easier to read]]. Cons: - Dividing an existing application into *Layers* may take some effort. - There is a small performance penalty. ## Use a database ![The data of a monolithic system is moved to a database, leaving the business logic stateless.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20add%20Database.png) Patterns: [[wiki/concepts/source/basic-metapatterns/layers|Layers]], [[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Database]] ([[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Repository]]). Goal: avoid implementing a database. Prerequisite: the system needs to query (and maybe also edit) a large amount of data. A database is non-trivial to implement. While ordinary files are good for small volumes of data, as your needs grow so needs to grow your technology. Deploy a database. Pros: - A well-known database is sure to be more reliable than any in-house implementation. - Many databases provide heavily optimized algorithms for querying data. - You can choose specific hardware to deploy the database to. - Your (now stateless) application will be easy to scale. Cons: - Databases are complex and require fine-tuning. - You cannot adapt the database engine to your evolving needs. - Most databases do not scale. - You are stepping right into [vendor lock-in](https://en.wikipedia.org/wiki/Vendor_lock-in). Further steps: - Deploy multiple [[wiki/concepts/source/basic-metapatterns/shards|*instances*]] of your application behind a [[wiki/concepts/source/extension-metapatterns/proxy|*Load Balancer*]]. - Continue the transition to [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] by separating the high-level and low-level business logic. - [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]] improves performance of the data layer. - [[wiki/concepts/source/fragmented-metapatterns/layered-services|*CQRS*]] handles read and write requests in separate services. - [[wiki/concepts/source/extension-metapatterns/shared-repository|*Space-Based Architecture*]] has low latency and allows for dynamic scalability of the whole system, including the data layer. - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] will allow you to switch to another database in the future. ## Add a Proxy ![A part of generic functionality of a monolith is moved to a proxy.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20add%20Proxy.png) Patterns: [[wiki/concepts/source/basic-metapatterns/layers|Layers]], [[wiki/concepts/source/extension-metapatterns/proxy|Proxy]]. Goal: avoid implementing generic functionality. Prerequisite: Your system serves clients (as opposed to [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|controlling hardware]]). A *Proxy* is placed between your system and its clients to provide generic functionality that otherwise would have to be implemented by the system. The kinds of *Proxy* to use with [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] are: [[wiki/concepts/source/extension-metapatterns/proxy|*Firewall*]], [[wiki/concepts/source/extension-metapatterns/proxy|*Cache*]], [[wiki/concepts/source/extension-metapatterns/proxy|*Reverse Proxy*]], and [[wiki/concepts/source/extension-metapatterns/proxy|*Adapter*]]. Multiple *Proxies* can be deployed. Pros: - You save some time (and money) on development. - A well-known *Proxy* is likely to be more secure and reliable than an in-house implementation. - You can choose the hardware to deploy the *Proxy* to. Cons: - Latency degrades, except for [[wiki/concepts/source/extension-metapatterns/proxy|*Response Cache*]] where it depends on frequency of identical requests. - The *Proxy* may fail, which increases the chance of total failure of your system. - Beware of [vendor lock-in](https://en.wikipedia.org/wiki/Vendor_lock-in). Further steps: - Another kind of *Proxy* may be added. - Some systems employ a *Proxy* per client, leading to [*Backends for Frontends*](). ## Add an Orchestrator ![An orchestrator is added to a monolithic system, allowing for higher-level client requests.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20add%20Orchestrator.png) Patterns: [[wiki/concepts/source/basic-metapatterns/layers|Layers]], [[wiki/concepts/source/extension-metapatterns/orchestrator|Orchestrator]]. Goal: provide a high-level public API with improved developer experience and performance. Prerequisite: the API of your system is fine-grained and there are common [[wiki/concepts/source/basic-metapatterns/layers|use cases]] which repeat certain sequences of calls to your API. A well-designed *Orchestrator* should provide a high-level API which is intuitive, easy to use, and coarse-grained to minimize the number of interactions between the system and its clients. An old way to access the original system’s API may still be maintained for uncommon use cases or legacy client applications ([[wiki/concepts/source/extension-metapatterns/orchestrator|*open* orchestration]]). As a matter of fact, you program the common application logic on behalf of your clients. Pros: - Client applications become easier to write. - Latency improves. Cons: - You get yet another moving part to design, test, deploy, and observe; and lots of design meetings between the development teams for a bonus. - The new coarse-grained interface will likely be less powerful than the original one. Further steps: - [*Backends for Frontends*]() use an *Orchestrator* per client type. ## Further steps Applying one of the evolutions discussed above does not prevent you from following another one of them, or even the same one for a second time: - A layer can sometimes be split into two layers. - A database can be added. - Multiple kinds of *Proxies* are OK. - If you don’t have an *orchestration layer* yet, you may add one. Those were evolutions from *Layers* to [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]]. Another set of evolutions stems from splitting one or more *layers* into [[wiki/concepts/source/basic-metapatterns/services|*Services*]]: - Splitting a [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]] and/or [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] yields [*Backends for Frontends*]() where requests from each kind of client are processed by a dedicated component. - Splitting the layer with the main business logic results in [[wiki/concepts/source/basic-metapatterns/services|*Services*]], possibly augmented with layers of [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]], [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Database*]], [[wiki/concepts/source/extension-metapatterns/proxy|*Proxies*]] and/or [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]]. If all the other layers beside the business logic layer remain monolithic, it is a [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]]. - Splitting the [[wiki/concepts/source/basic-metapatterns/layers|*database layer*]] leads to [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]] with specialized storages. - If all the layers share the domain dimension and are split along it, [[wiki/concepts/source/fragmented-metapatterns/layered-services|*Layered Services*]] (or its subtype [[wiki/concepts/source/fragmented-metapatterns/layered-services|*CQRS*]]) emerge. - If each layer is split along its own domain, the system follows [*Service-Oriented Architecture*]() that is built around component reuse. - Finally, some domains support [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchy*]] – a tree-like architecture where each layer takes a share of the system’s functionality. ![Diagrams of Backends for Frontends over Layers, Service-Oriented Architecture, Sandwich, Layered Services, Hierarchy, and Layers with Polyglot Persistence.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Layers%20-%20Further%201.png) In addition, - Distributed systems usually allow for the [[wiki/concepts/source/basic-metapatterns/shards|scaling]] of one or more layers. - A layer may employ [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]] for better customizability. - The UI and infrastructure layers may be split and abstracted according to the rules of [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] (or its subtype [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Separated Presentation*]]). - The system can often be extended with [[wiki/concepts/source/implementation-metapatterns/microkernel|*Scripts*]], resulting in a kind of [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]]. ![Diagrams of Layers with plugins, Layers with scripts, and Hexagonal Architecture with a layered core.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Layers%20-%20Further%202.png) --- title: "Evolutions of a Pipeline" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Evolutions of architectures/Evolutions of a Pipeline.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Evolutions%20of%20architectures/Evolutions%20of%20a%20Pipeline source_license_note: "See namespace README; preserve attribution and source links." --- # Evolutions of a Pipeline > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Evolutions of architectures/Evolutions of a Pipeline.md`. [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]] [[wiki/concepts/source/basic-metapatterns/services|inherits its set of evolutions from *Services*]]. Components can be added, split in two, merged or replaced. Many systems employ a [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] (pub/sub or pipeline framework), [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]] (which may be a database or file system), or [[wiki/concepts/source/extension-metapatterns/proxy|*Proxies*]]. There are a couple of *Pipeline*-specific evolutions: - The first service of the *Pipeline* can be promoted to [[wiki/concepts/source/extension-metapatterns/orchestrator|*Front Controller*]] which tracks status updates for every request it handles. - Adding an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] turns a [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]] into [[wiki/concepts/source/basic-metapatterns/services|*Services*]]. As the high-level business logic moves to the [[wiki/concepts/source/basic-metapatterns/layers|orchestration layer]], the services don’t need to interact directly anymore, the interservice communication channels disappear, and the system turns into ordinary [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrated Services*]]. ## Promote a service to Front Controller ![The first service of a pipeline subscribes to notifications from other services and thus becomes a Front Controller.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Services/Pipeline%20promote%20Front%20Controller.png) Patterns: [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Front Controller]] ([[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Polyglot Persistence]], [[wiki/concepts/source/extension-metapatterns/orchestrator|Orchestrator]]), [[wiki/concepts/source/basic-metapatterns/pipeline|Pipeline]] ([[wiki/concepts/source/basic-metapatterns/services|Services]]). Goal: allow for clients to query the state of their requests. Prerequisite: request processing steps are slow (may depend on human action). If the request processing steps require heavy calculations or manual action, then clients may want to query the status of their requests, and analysts may want to see bottlenecks in the *Pipeline*. Let the first service in the *Pipeline* track the state of all the running requests by subscribing to status notifications from other services. Pros: - The state of each running request is readily available. Cons: - The first service in the pipeline becomes more complex and starts depending on every other service. Further steps: - The *Front Controller* may be further promoted to [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] if there is a need to support many complex scenarios. ## Add an Orchestrator ![Adding an orchestrator transforms a pipeline into Orchestrated Services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Services/Pipeline%20use%20Orchestrator.png) Patterns: [[wiki/concepts/source/extension-metapatterns/orchestrator|Orchestrator]], [[wiki/concepts/source/basic-metapatterns/services|Services]]. Goal: support many use cases. Prerequisite: performance degradation is acceptable. When a [[wiki/concepts/source/foundations-of-software-architecture/choreography|*choreographed*]] system is gradually extended with more and more use cases, it is very likely to fall into integration hell where nobody understands how its components interrelate. Extract the [[wiki/concepts/source/basic-metapatterns/layers|workflow logic]] into a dedicated service. Pros: - New use cases are easy to add. - Complex scenarios are supported. - Error handling becomes trivial. - The services don’t depend on each other. - There is a single client-facing team, other teams are not under pressure from the business. - It is easier to run actions in parallel. - Global scenarios become debuggable. - The services don’t need to be redeployed when the high-level logic changes. Cons: - The number of messages in the system doubles, thus its performance may degrade. - The *Orchestrator* may become a development and performance bottleneck, or a single point of failure. Further steps: - If there are several clients that strongly vary in their workflows, you can apply [*Backends for Frontends*]() with an *Orchestrator* per client. - If the *Orchestrator* grows too large, it can be [[wiki/concepts/source/extension-metapatterns/orchestrator|divided]] into layers, services, or both, with the latter option resulting in a [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Top-Down Hierarchy*]]. - The *Orchestrator* can be [[wiki/concepts/source/extension-metapatterns/orchestrator|scaled]] and can have its own database. --- title: "Evolutions of a Proxy" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Evolutions of architectures/Evolutions of a Proxy.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Evolutions%20of%20architectures/Evolutions%20of%20a%20Proxy source_license_note: "See namespace README; preserve attribution and source links." --- # Evolutions of a Proxy > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Evolutions of architectures/Evolutions of a Proxy.md`. It usually makes little sense to get rid of a [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]] once it is integrated into the system. Its only real drawback is a slight increase in latency for user requests which may be helped through creation of [[wiki/concepts/source/extension-metapatterns/proxy|bypass channels]] between the clients and a service which needs low latency. The other drawback of the pattern, namely the *Proxy*’s being a single point of failure, is countered by deploying multiple instances of the *Proxy*. As *Proxies* are usually third-party products, there is very little we can change about them: - We can add another kind of a *Proxy* on top of the existing one. - We can use a stack of *Proxies* per client, making [*Backends for Frontends*](). ## Add another Proxy ![A proxy is added on top of an existing proxy.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Proxy%20add%20Proxy.png) Patterns: [[wiki/concepts/source/extension-metapatterns/proxy|Proxy]], [[wiki/concepts/source/basic-metapatterns/layers|Layers]]. Goal: avoid implementing generic functionality. Prerequisite: you don't have this kind of *Proxy* yet. A system is not limited to a single kind of *Proxies*. As a *Proxy* represents your system without changing its function, *Proxies* are transparent, thus they are stackable. It often makes sense to colocate software *Proxies* or use a multifunctional *Proxy* to reduce the number of network hops between the clients and the system. However, in a highly loaded system *Proxies* may be resource-hungry, thus in some cases colocation strikes back. Pros: - You get another aspect of your system implemented for you. Cons: - Latency degrades. - More work for admins. - Another point of possible failure. ## Deploy a Proxy per client type ![A proxy is subdivided into Backends for Frontends.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Proxy%20to%20Backends%20for%20Frontends.png) Patterns: [[wiki/concepts/source/extension-metapatterns/proxy|Proxy]], [Backends for Frontends](). Goal: let the aspects of communication vary among kinds of clients. Prerequisite: your system serves several kinds of clients. If you have internal and external clients, or admins and users, you may want to vary the setup of *Proxies* for each kind of client, sometimes to the extent of physically separating network communication paths, so that each kind of client is treated according to its bandwidth, priority, and permissions. Pros: - It is easy to set up various aspects of communication for a group of clients. Cons: - More work for admins as the *Proxies* are duplicated. --- title: "Evolutions of a Sandwich" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Evolutions of architectures/Evolutions of a Sandwich.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Evolutions%20of%20architectures/Evolutions%20of%20a%20Sandwich source_license_note: "See namespace README; preserve attribution and source links." --- # Evolutions of a Sandwich > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Evolutions of architectures/Evolutions of a Sandwich.md`. Unique evolutions of a [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]] involve the system’s domain logic or its topology: - The [[wiki/concepts/source/basic-metapatterns/layers|*domain-level*]] *services* are independent enough to be easily added or removed. - In most cases they share technologies, allowing for splitting or merging of the services. - If the services are found to be strongly coupled, they can be merged into a monolithic layer, likely to be subdivided in a better way later on. - Alternatively, the subdomains can be further decoupled. ## Add or remove a domain-level service ![One of the domain-level services is removed and another one is added.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Sandwich%20add%20remove%20Service.png) Patterns: [[wiki/concepts/source/extension-metapatterns/sandwich|Sandwich]]. Goal: maintain effective development. Prerequisite: a new subdomain emerges or an old one becomes obsolete. Though the *Sandwich* architecture allows for subdomains to be pretty independent in their logic, you should update your system’s high-level structure whenever there are drastic changes in the domain knowledge or functional requirements. Creation or deletion of a component often means forming or disbanding a team (see [*Inverse Conway Maneuver*](https://martinfowler.com/bliki/ConwaysLaw.html)). Pros: - The system’s architecture remains clear as it follows the domain knowledge. - The development teams remain narrowly specialized, thus effective. - Dead domain-level code is easily identified and removed. Cons: - You may need to update your database schema. - A newly established team takes time to learn its area of responsibility, while disbanding an old team disrupts almost everyone on the project. ## Split or merge domain-level services ![One domain-level service is split in half while two other services are merged together.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Sandwich%20split%20merge%20Services.png) Patterns: [[wiki/concepts/source/extension-metapatterns/sandwich|Sandwich]]. Goal: maintain effective development. Prerequisite: the business requirements gradually diverge from your original vision. [[wiki/concepts/source/foundations-of-software-architecture/modules-and-complexity|Ideally]], each service should be kept cohesive, while the services should be decoupled from each other. However, business likes to mess up your plans. If you ignore the results, your teams will be slowed down by mutual dependencies or become overburdened by the size of the components which they maintain. Therefore, restructure both the system and teams once the divergence between the domain knowledge and system’s architecture starts to negatively impact development. > If an architecture is misaligned with the domain which it models, some components implement functions which don’t properly belong to them, while others need a lot of help from their neighbors. Many unnecessary dependencies emerge between components and that both increases complexity (you cannot develop a component without knowing other components) and slows down the system (calls between components tend to be inefficient). Pros: - The system’s architecture is realigned with the domain knowledge. - The system components remain internally cohesive and decoupled from each other. - The development teams stay narrowly specialized, thus effective. Cons: - You will have to update the database schema and integration logic ([[wiki/concepts/source/basic-metapatterns/layers|use cases]]). - Splitting or merging teams disrupts them. ## Merge all the domain-level services together ![The entire domain layer is merged, resulting in Layers.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Sandwich%20to%20Layers.png) Patterns: [[wiki/concepts/source/basic-metapatterns/layers|Layers]]. Goal: improve the system performance and, possibly, development efficiency. Prerequisite: the project is small but found to be strongly coupled. Often the project grows in an unexpected manner. If you see that the domain-level services interact intensely, that likely means that you chose a wrong architecture. Revert to *Layers* to remove the artificial interfaces. You may also consider merging the domain-level teams if there are not too many people in them. Pros: - Less indirection and boilerplate code. - Improved performance which can be further optimized. Cons: - Now all the teams face higher system complexity. - The teams will share the codebase which means a high level of interdependency. ## Subdivide both shared layers ![The integration and data layers are divided into subdomains, producing Three-Layered Services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Sandwich%20to%20Layered%20Services.png) Patterns: [[wiki/concepts/source/fragmented-metapatterns/layered-services|Three-Layered Services]] ([[wiki/concepts/source/fragmented-metapatterns/layered-services|Layered Services]]). Goal: fine-grained scalability, database performance optimization, and limited fault tolerance. Prerequisite: the subdomains are loosely coupled in both [[wiki/concepts/source/basic-metapatterns/layers|use cases]] and [[wiki/concepts/source/basic-metapatterns/layers|data]]. It is natural to divide a *Sandwich* into [[wiki/concepts/source/basic-metapatterns/services|*Services*]], but only if your domain is not [[wiki/concepts/source/foundations-of-software-architecture/programming-and-architectural-paradigms|data-centric]] (built around a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]]) and your use cases are not too complex (requiring an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]]). Pros: - Independent scaling and deployment of the services. - Database technologies can be chosen on a per service basis. - Simpler application and database components. - Limited fault tolerance – if one of the services fails, others may still respond to clients. Cons: - Complex use cases are hard to implement or debug. - Poor latency for use cases that involve multiple subdomains. - Any coupling in the data impairs performance and increases costs. - Now you’ll have much more work for your DevOps. --- title: "Evolutions of a Shared Repository" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Evolutions of architectures/Evolutions of a Shared Repository.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Evolutions%20of%20architectures/Evolutions%20of%20a%20Shared%20Repository source_license_note: "See namespace README; preserve attribution and source links." --- # Evolutions of a Shared Repository > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Evolutions of architectures/Evolutions of a Shared Repository.md`. Once a database appears, it is unlikely to go away. I see the following evolutions to improve performance of the data layer: - [[wiki/concepts/source/basic-metapatterns/shards|*Shard*]] the database. - Use [[wiki/concepts/source/extension-metapatterns/shared-repository|*Space-Based Architecture*]] for dynamic scalability. - Divide the data into a private database per service. - Deploy specialized databases ([[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]]). ## Shard the database ![The shared database is sharded so that each database instance holds a subset of data,](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Shared%20Database_%20Shard.png) Patterns: [[wiki/concepts/source/basic-metapatterns/shards|Sharding]] ([[wiki/concepts/source/basic-metapatterns/shards|Shards]]), [[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Repository]], maybe [[wiki/concepts/source/extension-metapatterns/proxy|Sharding Proxy]]. Goal: increase capacity and performance of the database. Prerequisite: the data is shardable (consists of independent records). If your database is overloaded and the data which it contains describes independent entities (users, companies, or sales) you can deploy multiple instances of the database with subsets of the data distributed among them. You will need to deploy a *Sharding Proxy* or the services will have to find out which database *shard* to access by themselves, likely through hashing the record’s *primary key* \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\] or with the help of an [[wiki/concepts/source/extension-metapatterns/proxy|*Ambassador*]]. There is also a good chance that several smaller tables will have to be replicated to all the shards or moved to a [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|dedicated *Shared Database*]] (resulting in [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]]). Modern distributed databases support sharding out of the box, but an overgrown table may still impact the performance of the database. Pros: - Unlimited scalability. - You don’t need to change your database vendor. - Failure of a single database instance affects few users. Cons: - You will need to manage many instances of the database. - The application or a custom script may have to synchronize shared tables among the instances. - There is no way to do joins or run aggregate functions (such as *sum* or *count*) over multiple shards – all that logic moves to the services that use the database. Further steps: - [[wiki/concepts/source/basic-metapatterns/shards|*Replicate*]] each shard to improve fault tolerance and, possibly, read throughput. - [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]] or [[wiki/concepts/source/fragmented-metapatterns/layered-services|*CQRS*]] describe pre-calculating aggregates into another analytical database ([*Reporting Database*](https://martinfowler.com/bliki/ReportingDatabase.html)). - [[wiki/concepts/source/implementation-metapatterns/mesh|*Space-Based Architecture*]] may be cheaper as it scales dynamically. However, in its default and highly performant configuration it is prone to write collisions. ## Use Space-Based Architecture ![The shared database is migrated to a Data Grid, resulting in Space-Based Architecture](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Shared%20Database%20to%20Space-Based%20Architecture.png) Patterns: [[wiki/concepts/source/implementation-metapatterns/mesh|Space-Based Architecture]] ([[wiki/concepts/source/implementation-metapatterns/mesh|Mesh]], [[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Repository]]). Goal: dynamically scale throughput of the database. Prerequisite: data collisions are acceptable. *Space-Based Architecture* (SBA) duplicates the contents of a persistent database to a distributed in-memory *cache* co-located with the services managed by the *SBA*’s *Middleware*. That makes most data access operations very fast unless one needs to avoid write collisions. The [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh Middleware*]] autoscales both the services and the associated data cache under load, granting nearly perfect scalability. However, this architecture is costly because of the amount of traffic and CPU time spent on replicating the data between the *Mesh nodes*. Pros: - Nearly unlimited dynamic scalability. - Off-the-shelf solutions are available. - Very high fault tolerance. Cons: - Choose one: data collisions or mediocre performance. - Low latency is guaranteed only when the entire dataset fits in the memory of a node. - High operational cost because the nodes will send each other lots of data. - No support for analytical queries. ## Move the data to private databases of services ![The shared database is split into databases dedicated to subdomains, resulting in Layered Services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Shared%20Database%20to%20Services.png) Patterns: [[wiki/concepts/source/basic-metapatterns/services|Services]], [[wiki/concepts/source/basic-metapatterns/layers|Layers]]. Goal: decouple the services, remove the performance bottleneck (*Shared Database*). Prerequisite: the domain data is weakly coupled. If the data clearly follows subdomains, it may be possible to subdivide it accordingly. The services will become [[wiki/concepts/source/foundations-of-software-architecture/choreography|*choreographed*]] (or [[wiki/concepts/source/foundations-of-software-architecture/orchestration|*orchestrated*]] if they have an [[wiki/concepts/source/extension-metapatterns/orchestrator|*integration layer*]]) instead of communicating through the [[wiki/concepts/source/foundations-of-software-architecture/shared-data|shared data]]. Pros: - The services become independent in their persistence and data processing technologies. - Performance of the [[wiki/concepts/source/basic-metapatterns/layers|*data layer*]], which tends to limit the scalability of the system, will likely improve thanks to the use of smaller specialized databases. Cons: - The communication between the services and the synchronization of their data becomes a major issue. - Joins of the data from different subdomains will not be available. - Costs are likely to increase because of data transfer and duplication between the services. - You will have to administrate multiple databases. Further steps: - [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*CQRS Views*]] or a [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Query Service*]] help a service access and join data that belongs to other services. ## Deploy specialized databases ![The shared database is migrated to specialized databases.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Shared%20Database%20to%20Polyglot%20Persistence.png) Patterns: [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Polyglot Persistence]]. Goal: improve performance and maybe fault tolerance of the data layer. Prerequisite: there are diverse data types or patterns of data access. It is very likely that you can either use [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*specialized databases*]] for various data types or deploy [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*read-only replicas*]] of your data for queries. Pros: - You can choose one of the many specialized databases available on the market. - There is a good chance to significantly improve performance. - Replication improves fault tolerance of your data layer. Cons: - It may take effort to learn the new technologies and use them efficiently. - Someone needs to see to the new database(s). - You’ll likely need to work around *replication lag* \[[wiki/concepts/source/appendices/books-referenced|[MP]]\]. --- title: "Evolutions of an Orchestrator" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Evolutions of architectures/Evolutions of an Orchestrator.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Evolutions%20of%20architectures/Evolutions%20of%20an%20Orchestrator source_license_note: "See namespace README; preserve attribution and source links." --- # Evolutions of an Orchestrator > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Evolutions of architectures/Evolutions of an Orchestrator.md`. Employing an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] has two pitfalls: - The system becomes slower because too much communication is involved. - A single *Orchestrator* may be found to be too large and rigid. There is one way to counter the first point and more ways to solve the second one: - Subdivide the *Orchestrator* by the system’s subdomains, forming [[wiki/concepts/source/fragmented-metapatterns/layered-services|*Layered Services*]] and minimizing network communication. - Subdivide the *Orchestrator* by the type of client, forming [*Backends for Frontends*](). - Add another [[wiki/concepts/source/basic-metapatterns/layers|*layer*]] of orchestration. - Build a [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Top-Down Hierarchy*]]. ## Subdivide to form Layered Services ![An orchestrator is subdivided into subdomain components which become the application layers of respective services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Orchestrator%20to%20Layered%20Services.png) Patterns: [[wiki/concepts/source/fragmented-metapatterns/layered-services|Orchestrated Three-Layered Services]] ([[wiki/concepts/source/fragmented-metapatterns/layered-services|Layered Services]] ([[wiki/concepts/source/basic-metapatterns/services|Services]], [[wiki/concepts/source/basic-metapatterns/layers|Layers]])). Goal: simplify the *Orchestrator*, let the service teams own orchestration, decouple forces for the services, and improve performance. Prerequisite: the high-level ([[wiki/concepts/source/foundations-of-software-architecture/orchestration|orchestration]]) logic is weakly coupled between the subdomains. If the [[wiki/concepts/source/basic-metapatterns/layers|*orchestration* logic]] mostly follows the subdomains, it may be possible to partition it accordingly. Each service gets a part of the *Orchestrator* that mostly deals with its subdomain but may call other services when needed. As a result, [[wiki/concepts/source/foundations-of-software-architecture/orchestration|each service orchestrates every other service]]. Still, a large part of orchestration becomes internal to each service, meaning that fewer calls over the network are involved. Pros: - You subdivide the large *Orchestrator* codebase. - Performance is improved. - The services become more independent in their quality attributes. Cons: - You lose the client-facing *orchestration team* – now each service’s team will need to face its clients. - Service teams become interdependent (while having equal rights), which may result in slow development and [suboptimal decisions](https://en.wikipedia.org/wiki/Design_by_committee). - There is no way to share code between different use cases or even take a look at all of the scenarios at once. Further steps: - [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*CQRS Views*]] or a [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Query Service*]] help a service access and join data that belongs to other services, further reducing the need for interservice communication. ## Subdivide to form Backends for Frontends ![An orchestrator is subdivided into Backends for Frontends.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Orchestrator%20to%20Backends%20for%20Frontends.png) Patterns: [Backends for Frontends](), [[wiki/concepts/source/extension-metapatterns/orchestrator|Orchestrator]]. Goal: simplify the *Orchestrator*, employ a team per client type, and decouple the quality attributes for clients. Prerequisite: clients vary in workflows and forces. When use cases for clients vary, it makes sense for each kind of client to have a dedicated *Orchestrator*. Pros: - The smaller *Orchestrators* are independent in qualities, technologies, and teams. - The smaller *Orchestrators* are … well, smaller. Cons: - There is no good way to [[wiki/concepts/source/analytics/sharing-functionality-or-data-among-services|share code]] between the *Orchestrators*. - There are more system components. Further steps: - You may want to add client-specific [[wiki/concepts/source/extension-metapatterns/proxy|*Proxies*]] and, maybe, co-locate them with the *Orchestrators* to avoid the extra network hop. - Adding another shared [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] below the ones dedicated to clients creates a place for sharing functionality among the *Orchestrators*. - If you are running [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]] over a [[wiki/concepts/source/implementation-metapatterns/mesh|*Service Mesh*]], [[wiki/concepts/source/extension-metapatterns/proxy|*Sidecars*]] may help to share [[wiki/concepts/source/basic-metapatterns/layers|generic code]]. ## Add a layer of orchestration ![An orchestrator is subdivided into a pair of simple and complex orchestrators.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Orchestrator%20add%20Orchestrator.png) Patterns: [[wiki/concepts/source/extension-metapatterns/orchestrator|Orchestrator]], [[wiki/concepts/source/basic-metapatterns/layers|Layers]]. Goal: implement simple use cases quickly, while still supporting complex ones. Prerequisite: use cases vary in their complexity. You may use two or three *orchestration frameworks* (engines) which differ in complexity. A simple declarative tool may be enough for the majority of user requests, and falling back to the custom-tailored code would be needed in rare complex cases. Pros: - Simple scenarios are easy to write. - You retain good flexibility with hand-written code when it is needed. Cons: - Requires learning multiple technologies. - More components mean more failures and more administration. - Performance of complex requests may suffer from more indirection. Further steps: - Subdivide one or more of the resulting *orchestration layers* to form [[wiki/concepts/source/fragmented-metapatterns/layered-services|*Layered Services*]], [*Backends for Frontends*](), [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchy*]], or [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Cell-Based Architecture*]]. ## Form a Hierarchy ![An orchestrator is subdivided into a hierarchy.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Orchestrator%20to%20Hierarchy.png) Patterns: [[wiki/concepts/source/fragmented-metapatterns/hierarchy|Top-Down Hierarchy]] ([[wiki/concepts/source/fragmented-metapatterns/hierarchy|Hierarchy]]). Goal: simplify the *Orchestrator* and, if possible, the services. Prerequisite: the domain is hierarchical. If an *Orchestrator* becomes too complex, some domains (e.g. IIoT or telecom) encourage the use of a tree of *Orchestrators*. The most generic functionality is processed by its root and each additional layer takes care of one aspect of the domain. Pros: - Multiple specialized teams and technologies. - Small codebase per team. - Reasonable testability. - Some decoupling of quality attributes. Cons: - Hard to debug. - Poor latency in global scenarios unless several layers of the *hierarchy* are colocated. --- title: "Evolutions of architectures" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Evolutions of architectures/Evolutions of architectures.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Evolutions%20of%20architectures/Evolutions%20of%20architectures source_license_note: "See namespace README; preserve attribution and source links." --- # Evolutions of architectures > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Evolutions of architectures/Evolutions of architectures.md`. This appendix details dozens of evolutions of [[wiki/concepts/source/introduction/metapatterns|metapatterns]] to show how they connect together. The evolutions probably have practical value through listing prerequisites, benefits, and drawbacks, but I am not sure that many readers will get through them without becoming bored to death. The metapattern chapters in the main parts of the book include abridged versions of the sections below. Duplicate and similar evolutions are omitted, and I did not write any evolutions for [[wiki/concepts/source/fragmented-metapatterns/fragmented-metapatterns|fragmented metapatterns]] as you should be able to infer them on your own after having read the book. Furthermore, for some reason I don’t know of any evolutions for the [[wiki/concepts/source/implementation-metapatterns/implementation-metapatterns|implementation metapatterns]], except for those that re-integrate the custom parts of the system into its core, mostly to improve the system’s performance. However, such evolutions are trivial, thus omitted. ## Contents: - [[wiki/concepts/source/appendices/evolutions-of-a-monolith-that-lead-to-shards|Evolutions of a Monolith that lead to Shards]] - [[wiki/concepts/source/appendices/evolutions-of-a-monolith-that-result-in-layers|Evolutions of a Monolith that result in Layers]] - [[wiki/concepts/source/appendices/evolutions-of-a-monolith-that-make-services|Evolutions of a Monolith that make Services]] - [[wiki/concepts/source/appendices/evolutions-of-a-monolith-that-rely-on-plugins|Evolutions of a Monolith that rely on Plugins]] - [[wiki/concepts/source/appendices/evolutions-of-shards-that-share-data|Evolutions of Shards that share data]] - [[wiki/concepts/source/appendices/evolutions-of-shards-that-share-logic|Evolutions of Shards that share logic]] - [[wiki/concepts/source/appendices/evolutions-of-layers-that-make-more-layers|Evolutions of Layers that make more layers]] - [[wiki/concepts/source/appendices/evolutions-of-layers-that-help-large-projects|Evolutions of Layers that help large projects]] - [[wiki/concepts/source/appendices/evolutions-of-layers-to-improve-performance|Evolutions of Layers to improve performance]] - [[wiki/concepts/source/appendices/evolutions-of-layers-to-gain-flexibility|Evolutions of Layers to gain flexibility]] - [[wiki/concepts/source/appendices/evolutions-of-services-that-restructure-services|Evolutions of Services that restructure services]] - [[wiki/concepts/source/appendices/evolutions-of-services-that-add-layers|Evolutions of Services that add layers]] - [[wiki/concepts/source/appendices/evolutions-of-a-pipeline|Evolutions of a Pipeline]] - [[wiki/concepts/source/appendices/evolutions-of-a-middleware|Evolutions of a Middleware]] - [[wiki/concepts/source/appendices/evolutions-of-a-shared-repository|Evolutions of a Shared Repository]] - [[wiki/concepts/source/appendices/evolutions-of-a-proxy|Evolutions of a Proxy]] - [[wiki/concepts/source/appendices/evolutions-of-an-orchestrator|Evolutions of an Orchestrator]] - [[wiki/concepts/source/appendices/evolutions-of-a-sandwich|Evolutions of a Sandwich]] --- title: "Evolutions of Layers that help large projects" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Evolutions of architectures/Evolutions of Layers that help large projects.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Evolutions%20of%20architectures/Evolutions%20of%20Layers%20that%20help%20large%20projects source_license_note: "See namespace README; preserve attribution and source links." --- # Evolutions of Layers that help large projects > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Evolutions of architectures/Evolutions of Layers that help large projects.md`. The main drawback (and benefit) of [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] is that much or all of the business logic is kept together in one or two components. That allows for easy debugging and fast development in the initial stages of the project but slows down and complicates work as the project [[wiki/concepts/source/analytics/architecture-and-product-life-cycle|grows in size]]. The only way for a growing project to continue evolving at a reasonable speed is to subdivide its business logic into several smaller, thus less [[wiki/concepts/source/foundations-of-software-architecture/modules-and-complexity|complex]], components that match subdomains (*bounded contexts* \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]). There are several options for such a change with their applicability depending on the domain: - In a [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]] the middle layer with the bulk of business logic is divided into [[wiki/concepts/source/basic-metapatterns/services|*Services*]], leaving the upper [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] and lower [[wiki/concepts/source/extension-metapatterns/shared-repository|*database*]] layers intact for possible future evolutions. - Sometimes the business logic can be represented as a set of directed graphs which is known as [[wiki/concepts/source/basic-metapatterns/pipeline|*Event-Driven Architecture*]]. - If you are lucky, your domain is naturally a [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Top-Down Hierarchy*]]. ## Divide the domain layer into Services ![The domain layer is split into subdomain components, making a Sandwich.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Layers/Layers%20Split%20Domain%20to%20Services.png) Patterns: [[wiki/concepts/source/extension-metapatterns/sandwich|Sandwich]] ([[wiki/concepts/source/basic-metapatterns/layers|Layers]], [[wiki/concepts/source/basic-metapatterns/services|Services]], [[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Database]] ([[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Repository]]), [[wiki/concepts/source/extension-metapatterns/orchestrator|Orchestrator]]). Goal: make the code simpler and let several teams work on the project efficiently. Prerequisite: the low-level business logic comprises loosely coupled subdomains. It is very common for a system’s domain to comprise weakly interacting *bounded contexts* \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]. They are integrated through high-level use cases and/or [[wiki/concepts/source/foundations-of-software-architecture/shared-data|relations in data]]. For such a system it is relatively easy to subdivide the [[wiki/concepts/source/basic-metapatterns/layers|domain logic]] into *Services* while leaving the integration and data layers shared, yielding a *Sandwich*. Pros: - You get multiple specialized development teams. - The largest and most complex piece of code is split into several smaller components. - There is more flexibility with deployment and scaling. Cons: - Future changes in the overall structure of the domain will be harder to implement. - System-wide use cases become somewhat harder to debug as they span over many components. - Performance will degrade as soon as the *Services* and their *Orchestrator* become distributed. Further steps: - Continue by subdividing the *Orchestrator* and *database*, turning the system into [[wiki/concepts/source/fragmented-metapatterns/layered-services|*Orchestrated Three-Layered Services*]]. - Divide the *Orchestrator* (by type of client) into [*Backends for Frontends*](). - Use multiple databases ([[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]]). - Scale well with [[wiki/concepts/source/implementation-metapatterns/mesh|*Space-Based Architecture*]]. ## Build an Event-Driven Architecture over a Shared Database ![A backend is subdivided into a pipeline.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Layers/Layers%20Split%20to%20Event-Driven%20Architecture.png) Patterns: [[wiki/concepts/source/basic-metapatterns/pipeline|Event-Driven Architecture]] ([[wiki/concepts/source/basic-metapatterns/pipeline|Pipeline]] ([[wiki/concepts/source/basic-metapatterns/services|Services]])), [[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Database]] ([[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Repository]]). Goal: untangle the code, support multiple teams, and improve scalability. Prerequisite: the use cases are trivial sequences of loosely coupled, coarse-grained steps. If your system features well-defined and simple workflows for processing every kind of input request, then it can be divided into several [[wiki/concepts/source/basic-metapatterns/services|*subdomain services*]], each hosting a few related steps of multiple use cases. Each service subscribes to inputs from other services and/or system’s clients and publishes output events. Pros: - The code is divided into much smaller (and simpler) segments. - It is easy to add new steps or use cases as this structure is quite flexible. - You open a way to having several almost independent teams, one per service. - You can achieve flexible deployment and scaling as the services are stateless, but you need to add a [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] for that. - This architecture naturally supports event replay as the means of reproducing bugs or testing / benchmarking individual components. - There is no need for explicit scheduling or thread synchronization. Cons: - The system as a whole is hard to debug. - You will have to live with high latency. - You may end up with too many components which are interconnected in too many ways. Further steps: - Add a [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] that supports scaling and failure recovery. - Split the *Shared Database* by subdomain, yielding [[wiki/concepts/source/fragmented-metapatterns/layered-services|*Choreographed Two-Layered Services*]]. - Scale with [[wiki/concepts/source/implementation-metapatterns/mesh|*Space-Based Architecture*]]. - Extract the logic of use cases into an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]]. ## Build a Top-Down Hierarchy ![The lower layers of a system are subdivided, resulting in a hierarchy.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Layers/Layers%20to%20Hierarchy.png) Patterns: [[wiki/concepts/source/fragmented-metapatterns/hierarchy|Top-Down Hierarchy]] ([[wiki/concepts/source/fragmented-metapatterns/hierarchy|Hierarchy]]). Goal: untangle the code, support multiple teams, and achieve fine-grained scalability. Prerequisite: the domain is hierarchical. Splitting the lower layers into independent components with identical interfaces simplifies the managing code and allows the managed components to be deployed, developed, and run independently of each other. Ideally, the mid-layer components should participate in decision-making so that the uppermost component is kept relatively simple. Pros: - Hierarchy is easy to develop and support with multiple teams. - Individual components are straightforward to add, modify, or replace. - The components scale, deploy, and run independently. - The system is quite fault tolerant. Cons: - It takes time and skill to figure out good interfaces. - There are many components to administer. - Latency is suboptimal for system-wide use cases. --- title: "Evolutions of Layers that make more layers" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Evolutions of architectures/Evolutions of Layers that make more layers.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Evolutions%20of%20architectures/Evolutions%20of%20Layers%20that%20make%20more%20layers source_license_note: "See namespace README; preserve attribution and source links." --- # Evolutions of Layers that make more layers > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Evolutions of architectures/Evolutions of Layers that make more layers.md`. Not all the layered architectures are equally layered. A [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] with a [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]] or [[wiki/concepts/source/basic-metapatterns/layers|database]] has already stepped into the realm of [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] but is still far from reaping all of its benefits. It may continue its journey in a few ways that [[wiki/concepts/source/appendices/evolutions-of-a-monolith-that-result-in-layers|were earlier discussed]] for *Monolith*: - Employing a [[wiki/concepts/source/basic-metapatterns/layers|*database*]] (if you don’t use one) lets you rely on a thoroughly optimized state-of-the-art subsystem for data processing and storage. - [[wiki/concepts/source/extension-metapatterns/proxy|*Proxies*]] are similarly reusable generic components to be added at will. - Implementing an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] on top of your system may improve the programming experience and runtime performance for your clients. ![A diagram of calls in a layered system. A single request from a client is translated by an Orchestrator into multiple calls to lower layers.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Layers/Layers%20to%20Layers.png) It is also common to: - Segregate the business logic into two [[wiki/concepts/source/basic-metapatterns/layers|*layers*]]. ## Split the business logic into two layers ![A backend is subdivided into application and domain layers.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Layers/Layers%20Split%20in%20Two.png) Patterns: [[wiki/concepts/source/basic-metapatterns/layers|Layers]]. Goal: let parts of the business logic vary in their qualities, improve the structure of the code. Prerequisite: the high-level and low-level parts of the business logic are loosely coupled. It is often possible to split a backend into [[wiki/concepts/source/basic-metapatterns/layers|integration]] ([[wiki/concepts/source/foundations-of-software-architecture/orchestration|orchestration]]) and [[wiki/concepts/source/basic-metapatterns/layers|domain]] layers. That allows for one team to specialize in customer use cases while the other one delves deep into the domain knowledge and infrastructure. Pros: - You get an extra development team. - The high-level use cases may be deployed separately from business rules. - The layers may diverge in technologies and styles. - The code may [[wiki/concepts/source/foundations-of-software-architecture/modules-and-complexity|become less complex]]. Cons: - There is a small performance penalty. - In-depth debugging becomes harder. --- title: "Evolutions of Layers to gain flexibility" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Evolutions of architectures/Evolutions of Layers to gain flexibility.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Evolutions%20of%20architectures/Evolutions%20of%20Layers%20to%20gain%20flexibility source_license_note: "See namespace README; preserve attribution and source links." --- # Evolutions of Layers to gain flexibility > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Evolutions of architectures/Evolutions of Layers to gain flexibility.md`. The last group of evolutions to consider is about making the system more adaptable. We have [[wiki/concepts/source/appendices/evolutions-of-a-monolith-that-rely-on-plugins|already discussed]] the following evolutions for [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]]: - The behavior of the system may be modified through [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]]. - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] protects the business logic from dependencies on libraries and data stores. - [[wiki/concepts/source/implementation-metapatterns/microkernel|*Scripts*]] allow for customization of the system’s logic on a per client basis. ![Diagrams of Layers with plugins, Layers with scripts, and Hexagonal Architecture with a layered core.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Layers%20-%20Further%202.png) There is also a new evolution that modifies the upper (orchestration) layer: - The [[wiki/concepts/source/basic-metapatterns/layers|*orchestration layer*]] may be split into [*Backends for Frontends*]() to match the needs of several kinds of clients. ## Divide the orchestration layer into Backends for Frontends ![The application layer is split into Backends for Frontends.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Layers/Layers%20to%20Backends%20for%20Frontends.png) Patterns: [[wiki/concepts/source/basic-metapatterns/layers|Layers]], [Backends for Frontends aka BFFs](). Goal: let each kind of client get a dedicated development team. Prerequisite: no high-level logic is shared between client types. It is possible that your system has different kinds of users, e.g. buyers, sellers, and admins; or web and mobile applications. It may be easier to support a separate integration module for each kind of client than to keep all the unrelated code together in a single integration layer. Pros: - Each kind of client gets a dedicated team which may choose best fitting technologies. - You get rid of the single large codebase of the integration layer. Cons: - There is no good way to share code between the *BFFs* (in the [[wiki/concepts/source/extension-metapatterns/orchestrator|naive implementation]]). - There are new components to administer. Further steps: - [Evolve the *BFFs*]() through adding a shared *layer* or [[wiki/concepts/source/extension-metapatterns/proxy|*Sidecars*]] for the common functionality. --- title: "Evolutions of Layers to improve performance" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Evolutions of architectures/Evolutions of Layers to improve performance.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Evolutions%20of%20architectures/Evolutions%20of%20Layers%20to%20improve%20performance source_license_note: "See namespace README; preserve attribution and source links." --- # Evolutions of Layers to improve performance > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Evolutions of architectures/Evolutions of Layers to improve performance.md`. There are several ways to improve the performance of a [[wiki/concepts/source/basic-metapatterns/layers|*layered system*]]. One we have [[wiki/concepts/source/appendices/evolutions-of-shards-that-share-data|already discussed]] for [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]]: - [[wiki/concepts/source/implementation-metapatterns/mesh|*Space-Based Architecture*]] co-locates the data store and business logic and scales both dynamically. ![The database is migrated to a Data Grid, resulting in a scalable Space-Based Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Layers/Layers%20to%20Space-Based%20Architecture.png) Others are new and thus deserve more attention: - Merging several layers improves latency by eliminating the communication overhead. - Scaling some of the layers may improve throughput but degrade latency. - [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]] is the name for using multiple specialized data stores. ## Merge several layers ![The application and domain layers are merged.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Layers/Layers%20Merge.png) Patterns: [[wiki/concepts/source/basic-metapatterns/layers|Layers]] or [[wiki/concepts/source/basic-metapatterns/monolith|Monolith]]. Goal: improve performance. Prerequisite: the layers share programming language, hardware setup, and qualities. If your system’s development [[wiki/concepts/source/analytics/architecture-and-product-life-cycle|is finished]] (no changes are expected) and you really need that extra 5% performance improvement, then you can try merging everything back into a *Monolith* or a [[wiki/concepts/source/basic-metapatterns/layers|*3-Tier*]] system (front, back, data). Pros: - Enables aggressive performance optimizations. - The system may become easier to debug. Cons: - The code is frozen – it will be much harder to evolve. - Your teams lose the ability to work independently. Further steps: - [[wiki/concepts/source/basic-metapatterns/shards|*Shard*]] the entire system. ## Scale individual layers ![The application and domain layers are independently sharded.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Layers/Layers_%20Shard.png) Patterns: [[wiki/concepts/source/basic-metapatterns/layers|Layers]], [[wiki/concepts/source/basic-metapatterns/shards|Shards]], often [[wiki/concepts/source/extension-metapatterns/proxy|Load Balancer]] ([[wiki/concepts/source/extension-metapatterns/proxy|Proxy]]). Goal: scale the system. Prerequisite: some layers are [[wiki/concepts/source/basic-metapatterns/shards|stateless]] or limited to the [[wiki/concepts/source/basic-metapatterns/shards|data of a single client]]. Multiple instances or layers can be created, with their number and deployment [[wiki/concepts/source/foundations-of-software-architecture/forces-asynchronicity-and-distribution|varying from layer to layer]]. That may work seamlessly if each instance of the layer which receives an event which can start a use case knows the instance of the next layer to communicate to. Otherwise you will need a *Load Balancer*. Pros: - Flexible scalability. - Better fault tolerance. - Co-deployment with clients is possible. Cons: - More complex operations (more parts to keep an eye on). Further steps: - [[wiki/concepts/source/extension-metapatterns/shared-repository|*Space-Based Architecture*]] scales the data layer. - [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]] improves performance of the data layer. ## Use multiple databases ![The database layer is subdivided into specialized databases, resulting in Polyglot Persistence.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Layers/Layers%20to%20Polyglot%20Persistence.png) Patterns: [[wiki/concepts/source/basic-metapatterns/layers|Layers]], [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Polyglot Persistence]]. Goal: optimize performance of data processing. Prerequisite: there are isolated use cases for or subsets of the data. If you have separated *commands* (write requests) from *queries* (read requests), you can serve the queries with [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|read-only replicas]] of the database while the main database is reserved for the commands. If your types of data or data processing algorithms vary, you may deploy several [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|specialized databases]], each matching some of your needs. That lets you achieve the best performance in a wide range of cases. Pros: - The best performance for all the use cases. - Specialized data processing algorithms out of the box. - Replication may help with error recovery. Cons: - Someone will need to learn and administer all those databases. - Keeping the databases consistent takes effort and the replication delay may negatively affect the UX. Further steps: - Serve the read and write requests with different backends in accordance with [[wiki/concepts/source/fragmented-metapatterns/layered-services|*Command-Query Responsibility Segregation (CQRS)*]]. - Separate the backend into [[wiki/concepts/source/basic-metapatterns/services|*services*]] which match the already separated databases. --- title: "Evolutions of Services that add layers" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Evolutions of architectures/Evolutions of Services that add layers.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Evolutions%20of%20architectures/Evolutions%20of%20Services%20that%20add%20layers source_license_note: "See namespace README; preserve attribution and source links." --- # Evolutions of Services that add layers > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Evolutions of architectures/Evolutions of Services that add layers.md`. The most common modifications to a [[wiki/concepts/source/basic-metapatterns/services|system of *Services*]] involve supplementary system-wide *layers* which compensate for the inability of the *services* to share anything among themselves: - A [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] knows of all the deployed service [[wiki/concepts/source/basic-metapatterns/shards|instances]]. It mediates [[wiki/concepts/source/basic-metapatterns/layers|communication]] between them and may manage their scaling and failure recovery. - The [[wiki/concepts/source/extension-metapatterns/proxy|*Sidecars*]] of a [[wiki/concepts/source/implementation-metapatterns/mesh|*Service Mesh*]] make a virtual layer of [[wiki/concepts/source/analytics/sharing-functionality-or-data-among-services|shared libraries]] for the [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]] it hosts. - A [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Database*]] simplifies the initial phases of development and provides data consistency and [[wiki/concepts/source/foundations-of-software-architecture/shared-data|interservice communication]]. - [[wiki/concepts/source/extension-metapatterns/proxy|*Proxies*]] stand between the system and its clients and take care of shared aspects that otherwise would need to be implemented by every service. - An [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] is the single place for the high-level logic of every [[wiki/concepts/source/basic-metapatterns/layers|use case]]. - Transforming *Services* into a [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]] greatly simplifies their integration. ## Add a Middleware ![The communication aspect of services can be covered by a dedicated middleware.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Services/Services%20add%20Middleware.png) Patterns: [[wiki/concepts/source/extension-metapatterns/middleware|Middleware]], [[wiki/concepts/source/basic-metapatterns/services|Services]]. Goal: take care of scaling, recovery, and interservice communication without programming it. Prerequisite: communication between the services is uniform. Distributed systems may fail in a zillion ways. You want to ruminate neither on that nor on [heisenbugs](https://en.wikipedia.org/wiki/Heisenbug). And you probably want to have a framework for scaling the services and restarting them after failure. Get a third-party *Middleware*! Let your programmers write the business logic, not infrastructure. Pros: - You don't invest your time in infrastructure. - Scaling and error recovery are made easy. Cons: - There may be a performance penalty which becomes worse for uncommon patterns of communication. - The *Middleware* may become a single point of failure. Further steps: - Use a [[wiki/concepts/source/implementation-metapatterns/mesh|*Service Mesh*]] for dynamic scaling and as a way to implement [[wiki/concepts/source/analytics/sharing-functionality-or-data-among-services|shared aspects]]. ## Use a Service Mesh ![Scaled services reside on a shared layer of sidecars which is placed on top of a shared mesh engine. All instances of each service access the service's database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Multifunctional%20-%20Service%20Mesh.png) Patterns: [[wiki/concepts/source/implementation-metapatterns/mesh|Service Mesh]] ([[wiki/concepts/source/implementation-metapatterns/mesh|Mesh]], [[wiki/concepts/source/extension-metapatterns/middleware|Middleware]]), [[wiki/concepts/source/extension-metapatterns/proxy|Sidecar]] ([[wiki/concepts/source/extension-metapatterns/proxy|Proxy]]), [[wiki/concepts/source/basic-metapatterns/services|Services]]. Goal: support dynamic scaling and interservice communication out of the box; share libraries among the services. Prerequisite: service instances are mostly [[wiki/concepts/source/basic-metapatterns/shards|stateless]]. The [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]] architecture boasts dynamic scaling under load thanks to its *Mesh*-based *Middleware*. It also allows for the services to share libraries via their *Sidecars* – additional containers co-located with each service instance – to avoid duplication of [[wiki/concepts/source/basic-metapatterns/layers|generic code]] among the services. Pros: - Dynamic scaling and error recovery. - Available out of the box. - Provides a way to implement shared aspects (cross-cutting concerns) once and use the resulting libraries in every service. Cons: - Performance degrades because of the complex distributed infrastructure. - You may suffer vendor lock-in. ## Use a Shared Repository ![The data of individual services is merged into a shared repository.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Services/Services%20to%20Shared%20Database.png) Patterns: [[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Repository]], [[wiki/concepts/source/basic-metapatterns/services|Services]]. Goal: let the services [[wiki/concepts/source/foundations-of-software-architecture/shared-data|share data]], don’t invest in operating multiple databases. Prerequisite: the services use a uniform approach to persisting their data. You don’t really need every service to have a private database. A shared one is enough in many cases. Pros: - It is easy for the services to share and synchronize data. - Lower operational complexity. - No data duplication. Cons: - All the services depend on the database schema which becomes hard to alter. - The single database will limit performance of the system. - It may also become a single point of failure. Further steps: - [[wiki/concepts/source/implementation-metapatterns/mesh|*Space-Based Architecture*]] scales the [[wiki/concepts/source/basic-metapatterns/layers|data layer]] but it is a simple key-value store. - [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]] is about having multiple specialized data stores. ## Add a Proxy ![Generic aspects of services move to a shared proxy.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Services/Services%20add%20Proxy.png) Patterns: [[wiki/concepts/source/extension-metapatterns/proxy|Proxy]], [[wiki/concepts/source/basic-metapatterns/services|Services]]. Goal: use a standard infrastructure component on behalf of your entire system. Prerequisite: the system serves its clients in a uniform way. Putting a generic component between the system and its clients helps the programmers concentrate on business logic rather than protocols, infrastructure, or even security. Pros: - You get a choice of generic functionality without investing development time. - It is an additional layer that isolates your system from both its clients and attackers. Cons: - There is a latency penalty caused by the extra network hop. - Each *Proxy* may be a single point of failure, or at least needs some admin oversight. Further steps: - You can always add another kind of [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]]. - If there are multiple clients that differ in their protocols, you can employ a stack of *Proxies* per client, resulting in [*Backends for Frontends*](). ## Use an Orchestrator ![The application logic is extracted from individual services into a shared orchestrator.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Services/Services%20use%20Orchestrator.png) Patterns: [[wiki/concepts/source/extension-metapatterns/orchestrator|Orchestrator]], [[wiki/concepts/source/basic-metapatterns/services|Services]]. Goal: have the high-level logic of use cases distilled as intelligible code. Prerequisite: the use cases comprise sequences of high-level steps (which is very likely to be true for a system of [[wiki/concepts/source/basic-metapatterns/services|*subdomain services*]]). When a use case jumps over several services in a dance of [[wiki/concepts/source/foundations-of-software-architecture/choreography|*choreography*]], there is no easy way to understand it as there is no single place to see it in the code. It may be even worse with [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipelined*]] systems where the use cases are embodied in the structure of event channels between the components. Extract the [[wiki/concepts/source/basic-metapatterns/layers|high-level business logic]] from the choreographed services or their interconnections and put it into a dedicated component. Pros: - You are not limited in the number and complexity of use cases anymore. - The global use cases become much easier to debug. - You have a new team dedicated to the interaction with customers, freeing the other teams to study their parts of the domain or work on code improvements. - Many changes in the high-level logic can be implemented and deployed without touching the main services. - The extra layer decouples the main services from the system’s clients and from each other. Cons: - There is a performance penalty because the number of messages per use case doubles. - The *Orchestrator* may become a single point of failure. - Some flexibility is lost as the *Orchestrator* couples the services’ qualities. Further steps: - If there are several clients that strongly vary in workflows, you can apply [*Backends for Frontends*]() with an *Orchestrator* per client. - If the *Orchestrator* grows too large, it [[wiki/concepts/source/extension-metapatterns/orchestrator|can be divided]] into layers, services, or both; the latter option resulting in a [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Top-Down Hierarchy*]]. - The *Orchestrator* can be [[wiki/concepts/source/extension-metapatterns/orchestrator|scaled]] and can have its own database. ## Make a Sandwich ![The application and data parts of services are separated from the domain logic and merged into system-wide layers, resulting in a Sandwich.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Services/Services%20to%20Sandwich.png) Patterns: [[wiki/concepts/source/extension-metapatterns/sandwich|Sandwich]] ([[wiki/concepts/source/basic-metapatterns/layers|Layers]], [[wiki/concepts/source/basic-metapatterns/services|Services]], [[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Database]] ([[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Repository]]), [[wiki/concepts/source/extension-metapatterns/orchestrator|Orchestrator]]). Goal: simplify the integration of tightly coupled services. Prerequisite: the services use compatible technologies in their application and data layers. Tightly coupled services waste a lot of programming effort and performance on boilerplate communication and data transfer. At the same time, their [[wiki/concepts/source/basic-metapatterns/layers|*domain* logic]] remains fairly independent, making full merge into a [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] impractical. Try merging only the [[wiki/concepts/source/basic-metapatterns/layers|*use cases*]] and [[wiki/concepts/source/basic-metapatterns/layers|*persistence*]] while leaving the *domain* layer subdivided. Pros: - *Use cases* become much easier to debug. - No boilerplate code for communication between the services. - No data duplication or data transfer among the services. - You have a new team dedicated to use cases and interaction with the customers. - Many changes in *use cases* can be implemented and deployed without touching the *domain* logic. Cons: - The services are coupled in their properties. - There are now single points of failure that leave the system totally inoperational. - The *Shared Database* limits the system’s performance. Further steps: - [[wiki/concepts/source/implementation-metapatterns/mesh|*Space-Based Architecture*]] scales the *data* layer. --- title: "Evolutions of Services that restructure services" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Evolutions of architectures/Evolutions of Services that restructure services.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Evolutions%20of%20architectures/Evolutions%20of%20Services%20that%20restructure%20services source_license_note: "See namespace README; preserve attribution and source links." --- # Evolutions of Services that restructure services > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Evolutions of architectures/Evolutions of Services that restructure services.md`. [[wiki/concepts/source/basic-metapatterns/services|*Services*]] work well when each service matches a subdomain and is developed by a dedicated team. If those premises change, you’ll need to restructure the system: - A new feature request may emerge outside of any of the existing subdomains, creating a new service. - A service may grow too large to be developed by a single team, calling for division. - Two services may become so strongly coupled that they fare better when merged together. - The entire system may need to be glued back into a [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] if the domain knowledge changes or interservice communication strongly degrades performance. - Alternatively, coupled services may be clustered into co-deployed [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cells*]] to reduce operational complexity. ## Add or split a service ![A service is split in half.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Services/Services_%20Split.png) Patterns: [[wiki/concepts/source/basic-metapatterns/services|Services]]. Goal: get one more team to work on the project, decrease the size of an existing service. Prerequisite: there is a loosely coupled (new or existing) subdomain that does not have a dedicated service (yet). If you need to add a new functionality that does not naturally fit into one of the existing services, you may create a new service and, maybe, get a new team for it. If one of your services has grown too large, you should look for a way to subdivide it (likely through a [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]] stage with a shared [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] and [[wiki/concepts/source/extension-metapatterns/shared-repository|*database*]]) to decrease the size and, correspondingly, complexity of its code and get multiple teams to work on the resulting (sub)services. However, that makes sense only if the old service is not highly cohesive – otherwise [[wiki/concepts/source/foundations-of-software-architecture/modules-and-complexity|the resulting subsystem may be more complex]] than the original service. Pros: - You get an extra development team. - The complexity of the code decreases (splitting an existing service) or does not increase (adding a new one). - The new service is independently scalable. Cons: - You add to the operations complexity by creating a new system component and several inter-component dependencies. - There is a new point of failure, which means that bugs and outages become more likely. - Performance (or at least the latency and cost efficiency) of the system will deteriorate because interservice communication is slow. - You may have a hard time debugging use cases that involve both the old and new service. ## Merge services ![Two services are merged.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Services/Services_%20Merge.png) Patterns: [[wiki/concepts/source/basic-metapatterns/services|Services]], [[wiki/concepts/source/basic-metapatterns/monolith|Monolith]] or [[wiki/concepts/source/basic-metapatterns/layers|Layers]]. Goal: accept the coupling of subdomains and improve performance. Prerequisite: the services use compatible technologies. If you see that several services communicate with each other almost as intensely as they call their internal methods, then they probably belong together. If your use cases have too high a latency or you pay too much for CPU and traffic, the issue may originate with the interservice communication, and merging the services should help. No services, no headache. Alternatively, as the domain knowledge changes \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\], you may have to merge much of the code together only to subdivide it later along the updated subdomain boundaries. Which means that you face [lots of work for no reason](https://martinfowler.com/bliki/MonolithFirst.html). Pros: - Improved performance. - It becomes easy for parts of the merged code to access each other and share data. - The new merged *service* or *Monolith* is easier to debug than the original *Services*. Cons: - The development teams become even more interdependent. - There is no good way to vary qualities by subdomain. - You lose granular scalability by subdomain. - The merged codebase may be too large for comfortable development. - If anything fails, everything fails. ## Cluster services ![Services are grouped into Cells, reducing their interdependencies.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Services/Services_%20Cluster.png) Patterns: [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Cell]] ([[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Hexagonal Architecture]], [[wiki/concepts/source/basic-metapatterns/services|Services]]). Goal: reduce operational complexity, decouple subdomains, and improve performance. Prerequisite: there are distinct subdomains. When there are too many services, none sees the big picture: which components are involved in a use case and why the system works the way it does. Moreover, managing tens to hundreds of different services with their databases is hard and error-prone. Therefore, cluster the services which share a subdomain into a co-deployed cohesive *Cell*. Pros: - Managing ten *Cells* is much easier than managing a hundred services. - More clear and independent subdomains as their interdependencies become explicit. - Lower traffic because the closely communicating services are now co-located. - Lower data storage requirements as the contents of the *Cell* may [[wiki/concepts/source/extension-metapatterns/shared-repository|share a database]]. - No boilerplate code for versioning or [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|data views]] inside the *Cell*. Cons: - The development teams that work on services that belong to a single *Cell* need to synchronize their actions. - A *Cell* is usually scaled as a whole. Further steps: - Complete the *Cell* encapsulation through the use of [[wiki/concepts/source/extension-metapatterns/proxy|*Adapters*]] and [[wiki/concepts/source/implementation-metapatterns/plugins|*Ambassador Plugins*]]. - Transform any strongly coupled *Cells* into [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwiches*]]. --- title: "Evolutions of Shards that share data" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Evolutions of architectures/Evolutions of Shards that share data.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Evolutions%20of%20architectures/Evolutions%20of%20Shards%20that%20share%20data source_license_note: "See namespace README; preserve attribution and source links." --- # Evolutions of Shards that share data > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Evolutions of architectures/Evolutions of Shards that share data.md`. One issue peculiar to [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]] is that of coordinating the instances deployed, especially if their data become coupled. The most direct solution is to let the instances operate a component that wraps the shared data: - If the whole dataset needs to be shared, it can be extracted into a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]] layer. - If data collisions are tolerated, [[wiki/concepts/source/implementation-metapatterns/mesh|*Space-Based Architecture*]] promises low latency and dynamic scalability. - If a part of the system’s data becomes coupled, only that part can be moved to a *Shared Repository*, causing each instance to manage two data stores: [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|private and shared]]. - Another option is to split out a [[wiki/concepts/source/basic-metapatterns/services|*service*]] to own the coupled data and always deploy it as a single instance. The remaining parts of the system become coupled to that service, not to each other. ## Move all the data to a Shared Repository ![The data of shards moves to a shared database. The shards become stateless and are deployed behind a load balancer.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Shards/Shards%20to%20Shared%20DB.png) Patterns: [[wiki/concepts/source/basic-metapatterns/shards|Pool]] ([[wiki/concepts/source/basic-metapatterns/shards|Shards]]), [[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Database]] ([[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Repository]]), [[wiki/concepts/source/extension-metapatterns/proxy|Load Balancer]] ([[wiki/concepts/source/extension-metapatterns/proxy|Proxy]]), [[wiki/concepts/source/basic-metapatterns/layers|Layers]]. Goal: don’t struggle against the coupling of the shards, keep it simple and stupid. Prerequisite: the system is not under pressure for data size or latency (which can be addressed by the further evolutions). In case a shard needs to access data owned by any other shard, the prerequisite of the independence of shards starts to fall apart. Grab all the data of all the shards and push it into a *Shared Database*, if you can (there may be too much data or the database access may be too slow). As all the shards become identical, you’ll likely add a *Load Balancer*. Pros: - You can choose one of the many specialized databases available. - The stateless instances of the main application become dynamically scalable. - Failure of a single instance affects a few users for a short time. - [*Canary Release*](https://martinfowler.com/bliki/CanaryRelease.html) is supported. Cons: - The database limits the system’s scalability and performance. - The *Load Balancer* and *Shared Database* increase latency and are single points of failure. Further steps: - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] will let you change your database in the future. - [[wiki/concepts/source/implementation-metapatterns/mesh|*Space-Based Architecture*]] decreases latency by co-locating subsets of the data and the instances of your application. - [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]] uses multiple specialized databases, often discerning between commands and queries. That may greatly relieve the primary (write) database. - [[wiki/concepts/source/fragmented-metapatterns/layered-services|*CQRS*]] goes even further by processing read and write requests with dedicated *services*. ## Use Space-Based Architecture ![The data of the shards moves to a Data Grid, resulting in a Space-Based Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Shards/Shards%20to%20Space-Based%20Architecture.png) Patterns: [[wiki/concepts/source/implementation-metapatterns/mesh|Space-Based Architecture]] ([[wiki/concepts/source/implementation-metapatterns/mesh|Mesh]], [[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Repository]]), [[wiki/concepts/source/basic-metapatterns/shards|Shards]], [[wiki/concepts/source/basic-metapatterns/layers|Layers]]. Goal: don’t struggle against the coupling between the shards, maintain high performance. Prerequisite: data collisions are acceptable. *Space-Based Architecture* is a *Mesh* of nodes which comprise the application and a cached subset of the system’s data. A node broadcasts any changes to its data to other nodes, and it may request any data that it needs from the other nodes. Collectively, the nodes of the *Mesh* keep the entire system’s data cached in memory. Though *Space-Based Architecture* may provide several modes of action, including [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|single write / multiple read]] replicas, it is most efficient when there is no write synchronization between its nodes, in which case data consistency is sacrificed for performance and scalability. Pros: - Unlimited dynamic scalability. - Off-the-shelf solutions are available. - Failure of a single instance affects few users. Cons: - Choose one: data collisions or mediocre performance. - Low latency is supported only for datasets that fit in memory of a single node. - High operational costs because the nodes exchange huge amounts of data. - No support for analytical queries. ## Use a Shared Repository for the coupled subset of data ![A coupled subset of the system's data is stored in a shared repository, while the bulk of the data is sharded.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Shards/Shards%20add%20Shared%20DB.png) Patterns: [[wiki/concepts/source/basic-metapatterns/shards|Shards]], [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Private and Shared Databases]] ([[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Polyglot Persistence]]), [[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Database]] ([[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Repository]]), [[wiki/concepts/source/basic-metapatterns/layers|Layers]]. Goal: solve the coupling between shards without losing performance. Prerequisite: the shards are coupled through a small subset of data. If a subset of the data is accessed by all the shards, that subset can be moved into a dedicated database, which is likely to be fast if only because it is small. Using a distributed database that keeps its data synchronized among all the shards may be even faster. This approach resembles [*Shared Kernel*](https://ddd-practitioners.com/home/glossary/bounded-context/bounded-context-relationship/shared-kernel/) \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]. Pros: - You can choose one of the many specialized databases available. Cons: - The *Shared Database* increases latency and is the single point of failure. ## Split a service with the coupled data ![Coupled business logic and data is separated from shards into a shared singletone service.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Shards/Shards%20split%20Shared%20Service.png) Patterns: [[wiki/concepts/source/basic-metapatterns/services|Services]], [[wiki/concepts/source/basic-metapatterns/shards|Shards]]. Goal: solve the coupling between the shards in an honorable way. Prerequisite: the part of the domain which causes coupling between the shards is weakly coupled to the remaining domain. If a part of the domain is too cohesive to be sharded, we can often extract it from the main application into a dedicated service. That way the main application remains sharded while the new service exists as a single instance. In rare cases there is a chance to re-shard the new service with a sharding key which is different from the one used for sharding the main application. This approach resembles [*Shared Kernel*](https://ddd-practitioners.com/home/glossary/bounded-context/bounded-context-relationship/shared-kernel/) \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]. Pros: - The main code should become a little bit simpler. - The new service can be given to a new team. - The new service may choose a database which best fits its needs. Cons: - Now it’s hard to share data between the new service and the main application. - Scenarios that use the new service are harder to debug. - There is a moderate performance penalty for using the extra service. --- title: "Evolutions of Shards that share logic" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Evolutions of architectures/Evolutions of Shards that share logic.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Evolutions%20of%20architectures/Evolutions%20of%20Shards%20that%20share%20logic source_license_note: "See namespace README; preserve attribution and source links." --- # Evolutions of Shards that share logic > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Evolutions of architectures/Evolutions of Shards that share logic.md`. Other cases are better solved by extracting the logic that manipulates multiple [[wiki/concepts/source/basic-metapatterns/shards|*shards*]]: - Splitting a [[wiki/concepts/source/basic-metapatterns/services|*service*]] (as discussed [[wiki/concepts/source/appendices/evolutions-of-shards-that-share-data|above]]) yields a component that represents both shared data and shared logic. - Adding a [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] lets the shards communicate with each other without keeping direct connections. It also may do housekeeping: error recovery, replication, and scaling. - A [[wiki/concepts/source/extension-metapatterns/proxy|*Sharding Proxy*]] hides the existence of the shards from clients. - An [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] calls (or messages) multiple shards to serve a user request. That relieves the shards of the need to coordinate their states and actions by themselves. ## Add a Middleware ![A middleware manages shards and lets them communicate to each other.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Shards/Shards%20add%20Middleware.png) Patterns: [[wiki/concepts/source/basic-metapatterns/shards|Shards]], [[wiki/concepts/source/extension-metapatterns/middleware|Middleware]], [[wiki/concepts/source/basic-metapatterns/layers|Layers]]. Goal: simplify the communication between shards, their deployment, and recovery. Prerequisite: many shards need to exchange information, some may fail. A *Middleware* transports messages between shards, checks their health, and recovers ones which have crashed. It may manage data replication and deployment of software updates as well. Pros: - The shards become simpler because they don’t need to track each other. - Many good third-party implementations are readily available. Cons: - Performance may degrade. - The components of the *Middleware* are new points of failure. ## Add a Sharding Proxy ![A sharding proxy relieves clients from the need to find the appropriate shard.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Shards/Shards%20add%20Load%20Balancer.png) Patterns: [[wiki/concepts/source/basic-metapatterns/shards|Shards]], [[wiki/concepts/source/extension-metapatterns/proxy|Sharding Proxy]] ([[wiki/concepts/source/extension-metapatterns/proxy|Proxy]]), [[wiki/concepts/source/basic-metapatterns/layers|Layers]]. Goal: simplify the code on the client side, hide your implementation from clients. Prerequisite: each client connects directly to the shard which owns their data. The client application may know the address of the shard which serves it and connect to it without intermediaries. That is the fastest means of communication, but it prevents you from changing the number of shards or other details of your implementation without updating all the clients, which may be unachievable. An intermediary may help. Pros: - Your system becomes isolated from its clients. - You can put generic aspects into the *Proxy* instead of implementing them in the shards. - *Proxies* are readily available. Cons: - The extra network hop increases latency unless you deploy the *Sharding Proxy* as an [[wiki/concepts/source/extension-metapatterns/proxy|*Ambassador*]] co-located with every client which, however, brings back the issue of client software updates. - The *Sharding Proxy* is a single point of failure unless [[wiki/concepts/source/basic-metapatterns/shards|*replicated*]]. ## Move the integration logic into an Orchestrator ![The high-level logic of shards moves to a shared orchestrator which integrates the data stored within and processed by individual shards.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Shards/Shards%20use%20Orchestrator.png) Patterns: [[wiki/concepts/source/basic-metapatterns/shards|Shards]], [[wiki/concepts/source/extension-metapatterns/orchestrator|Orchestrator]], [[wiki/concepts/source/basic-metapatterns/layers|Layers]]. Goal: isolate the shards and eliminate their awareness of each other. Prerequisite: the shards are coupled through their high-level logic. When a high-level scenario uses multiple shards ([[wiki/concepts/source/extension-metapatterns/orchestrator|*Scatter-Gather* and *MapReduce*]] are the simplest examples), the way to follow is to extract all such scenarios into a dedicated stateless component. That makes the shards independent of each other. Pros: - The shards don’t need to be aware of each other. - The high-level logic can be written in a high-level language by a dedicated team. - The high-level logic can be deployed independently. - The main bulk of the code should become much simpler. Cons: - Latency will increase. - The *Orchestrator* becomes a single point of failure which has a good chance to corrupt your data. Further steps: - [[wiki/concepts/source/extension-metapatterns/orchestrator|*Shard* or *replicate* the *Orchestrator*]] to support higher load and to remain online if it fails. - *Persist* the *Orchestrator* (give it a dedicated database) to make sure that it does not leave half-committed transactions upon failure. - *Divide* the *Orchestrator* [[wiki/concepts/source/appendices/evolutions-of-layers-to-gain-flexibility|into *Backends for Frontends*]] or a [[wiki/concepts/source/extension-metapatterns/orchestrator|*SOA*-style layer]] if you have multiple kinds of clients or workflows, respectively. --- title: "Format of a metapattern" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Format of a metapattern.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Format%20of%20a%20metapattern source_license_note: "See namespace README; preserve attribution and source links." --- # Format of a metapattern > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Format of a metapattern.md`. The descriptions of most [[wiki/concepts/source/introduction/metapatterns|metapatterns]] follow the same format: ### Diagram The structural diagram (in *abstractness-subdomain-sharding* [[wiki/concepts/source/introduction/metapatterns|coordinates]]) of a typical application of the metapattern. Please note that in practice the number and types of components and their interactions may vary: - Even though most diagrams show 3 *layers* or *services*, there are many 2-layered or 4-layered systems, while the number of services may often be greater than 10. - [[wiki/concepts/source/extension-metapatterns/extension-metapatterns|*Extension metapatterns*]] add a *layer* (or a *layer of services*) to an existing system, which is shown as [[wiki/concepts/source/basic-metapatterns/services|*Services*]], but may instead comprise [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]], [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]], or even a [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]]. - Subtypes of [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchy*]] or [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]] differ in their [[wiki/concepts/source/introduction/system-topologies|topologies]]. Only one is shown. - Components of metapatterns may communicate in various ways that include in-process calls, RPC, asynchronous messaging, or streams. Only one of them is shown. Optional communication pathways may appear as dashed arrows. Most diagrams feature the following colors: - [[wiki/concepts/source/basic-metapatterns/layers|*Use cases*]] (aka integration, orchestration, workflow or application logic) are shown in green. Those are high-level scenarios executed by user actions or signals from hardware which keep the system acting as a whole. Use cases are *what* your software does. - [[wiki/concepts/source/basic-metapatterns/layers|*Domain logic*]] (business rules), shown in blue, is the set of algorithms that models the real-world system which your software describes. It is *how* your system solves its tasks. - [[wiki/concepts/source/basic-metapatterns/layers|*Generic code*]] is white. It stands for tools and libraries unrelated to your business. Examples include communication protocols, data compression, and common maths. - [[wiki/concepts/source/basic-metapatterns/layers|*Data*]] is gray. It includes business-critical in-memory state (e.g. user’s session) and persistent storage (in a database or files). *Use cases* and *domain logic* comprise *business logic* \[[wiki/concepts/source/appendices/books-referenced|[PEAA]]\] – the code that makes your software different from whatever else is available on the market. It is this part of the system which your customers pay for, and it usually is much larger than the other parts, which makes business logic the primary focus of development. In [[wiki/concepts/source/foundations-of-software-architecture/choreography|*choreographed*]] systems use cases are defined by the web of communication channels instead of the code inside the system’s components. That is represented by green arrows and the overall lack of green areas on the diagrams of system components. ### Abstract *Motto* and the design goal. Known as: the list of aliases for the general metapattern. Structure: a short description of the structure of the metapattern. Type: system topology, extension component, or implementation. - A *system topology* ([[wiki/concepts/source/basic-metapatterns/layers|*Layers*]], [[wiki/concepts/source/basic-metapatterns/services|*Services*]] and few others derived from them) makes the backbone of any system. - An *extension component* is an addition to a *system topology* which modifies its properties. - An *implementation* shows the internal structure of a component, usually hidden from its clients. A short *table of benefits and drawbacks*. References: select articles and books that describe the topic. After that, there follow two or three paragraphs of facts and ideas about the metapattern. ### Performance This section discusses the performance of the subject metapattern in scenarios of various scope: simple requests or events that relate to a single subsystem are usually processed much faster than those that touch multiple components. There are two kinds of performance: latency and throughput. Low latency is possible only if few components are involved because inter-component communication, especially in distributed systems, increases latency. Contrariwise, throughput depends on the number of components that work in parallel, thus it scales together with the system. This section may also discuss optimization techniques that apply to the metapattern. ### Dependencies Some components of the metapattern depend on other components. If a component changes, everything that depends on it may need to be re-tested with the updated version. If a component’s interface changes, all the components that depend on it must be updated. Therefore, components that evolve quickly should depend on others, not the other way around. Some patterns, such as [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]], use [[wiki/concepts/source/extension-metapatterns/proxy|*Adapters*]] to [[wiki/concepts/source/analytics/indirection-in-commands-and-queries|break dependencies]]. An *Adapter* depends on components on both sides of itself, making those components independent of each other. The *Adapters* are small enough to update quickly, and may easily be replaced with stubs for testing or running a component in isolation. ### Applicability Here follows a list of types of projects which may benefit from applying the architecture under review, and another list of those which it is more likely to hurt. ### Relations These are illustrated through an optional sequence of diagrams, showing the (*extension* or *implementation*) metapattern applied to various kinds of architectures, followed by a list of relations between the current and other metapatterns. ### Variants and examples A metapattern usually unites many variations of several patterns. Here we may have a section per dimension of variability, and often another section with well-known patterns and architectures that match the metapattern. When a pattern is listed under several metapatterns (as often seen with *extension components*), the headers of the multiple pattern descriptions cross-link to each other. In other cases I had to include several variants that do not properly belong to the metapattern under review, just to avoid confusion with terminology and point the reader to the right chapter. For example, [[wiki/concepts/source/basic-metapatterns/monolith|*Modular Monolith*]] has a module per subdomain, thus it belongs to [[wiki/concepts/source/basic-metapatterns/services|*Services*]] rather than [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]]. Still, when the chapter on *Monolith* was not mentioning it, I was blamed for misunderstanding the *Monolithic Architecture*. Such patterns are marked as (misapplied) or (inexact). I tried to show the difference between synonymous names for every variant or example whenever I could identify one. ### Evolutions This section covers a brief summary of possible changes to the architecture under review. Each change leads to a new architecture which usually belongs to another metapattern. [[wiki/concepts/source/appendices/evolutions-of-architectures|Appendix E]] discusses many evolutions in greater detail: - A diagram that shows the original and resulting structures. - The list of patterns, present in the resulting architecture. More general forms of each pattern are given in parentheses, i.e. Pattern (Metapattern (Parent Metapattern)). - The goal(s) of the transition. - The prerequisites that enable the change. - A short description of the change and the resulting system. - Lists of pros and cons of the evolution. - An optional list of metapatterns that the resulting system may evolve into and their benefits in the context of the current evolution. --- title: "Glossary" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Glossary.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Glossary source_license_note: "See namespace README; preserve attribution and source links." --- # Glossary > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Glossary.md`. *Abstractness* – the scope of information that a *concept* operates. Highly abstract *concepts* describe the *system’s* behavior in less words. *Action* – an act of a *system* that changes its environment. [[wiki/concepts/source/basic-metapatterns/layers|*API*]] (*application programming interface*) – a set of *methods* or *messages* that a *component* exposes to its *clients*. [[wiki/concepts/source/basic-metapatterns/layers|*Application*]] – the most *abstract* layer that usually *integrates components* of a less *abstract* layer. *Architectural pattern* – a way to structure a *system* or a part of a *system* to achieve desirable properties (address a set of *forces*). *Architectural style* – see *architecture*. *Architecture* – the structure (*components* and their *interactions*) of a system which follows a certain framework or guidelines. *ASS diagram* – a *structural diagram* with *abstraction*, *subdomain* and *sharding* for coordinates. *Asynchronous communication* – the mode of *communication* when the sender of the *request message* does not stop the execution of its *scenario* to wait for the confirmation message. *Attack surface* – the amount of *components* and functionality that faces an external network (potentially exposed to hackers). *Availability* – the percentage of time that the *system* is operational (satisfies its *users*). *Bounded context* – a subset of *requirements* and code that shares a set of *concepts*. Usually consists of internals of a *component* and *APIs* of all the *components* it uses. *Business logic* – the thing that *users* pay for. It is the heart of the business and is usually the largest part of the *project*. You cannot buy *business logic*, only *implement* it. *Business logic* comprises *use cases* and *business rules*. *Business rules* – *domain concepts* and their relations. They make the low-level half of *business logic*. *Choreography* – a kind of *workflow* in which *components* that belong to the same level of *abstractness* cooperate to implement a *use case*. *Client* – an external *component* or *system* that makes use of a *component* or *system* in question. *Cohesion* – the density of logical connections between entities inside a *component*. *Colocated* – running in the same address space (process) on the same hardware. *Communication* – transfer of data or signals in a *system*. *Complexity* – the cognitive load caused by the quantity of entities (*concepts* or *modules*) and their relations that a programmer needs to operate. *Component* – an encapsulated part of a *system*. It exposes an *API* to the system's *clients* and/or other *components* of the *system*. *Concept* – a notion of an element of a *system's* behavior, usually present in *requirements*. *Contract* – the informal rules for the behavior of a *component* expected by its *clients*. *Control* – a kind of system that supervises physical entities or external programs. *Coupling* – the density of logical connections between *components*. *Cross-cutting concern* – a functionality that should be present in multiple *components*. *Data store* – anything where data can be placed or retrieved. Includes *databases*, file systems, and cloud storage. *Database* – a service for storing, retrieving, and analyzing data. *Debugging* – trying to force the *system* to behave correctly from the user’s point of view. *Deployment* – uploading a *component* to the hardware that will execute it. *Design* – planning for the best way to write code. *Design* – see *architecture*. *Design space* – the multitude of possible ways to *design* a given project. *Development* – building a *project* for its *users*. Usually involves intermixed *design*, *implementation*, *debugging* and *testing* phases. *Distributed* – spread over multiple computers that *communicate* via a network. *Domain* – the whole of knowledge (including *requirements*) that is needed to build a *system*. [[wiki/concepts/source/basic-metapatterns/layers|*Domain*]] – the middle layer of a *system* that contains its *business rules*. *Event* – a signal that carries some meaning for a *system* or *component*. *Events* may carry data. *Fault tolerance* – the ability of a *system* to remain (at least partially) operational if one or more of its *components* fail (become inaccessible due to a hang, crash or a hardware failure). *Forces* – restrictions on the design and development of a system based on the *qualities* it should meet (see *non-functional requirements*) and organizational limitations (such as time, budget and skills). *Functional requirements* – the *requirements* that describe *inputs* and *outputs* of a *system*, but not its *performance* or stability. *Global use case* – a *use case* that involves most of the *components* of a *system*. Such *scenarios* are strongly affected by the *system’s* structure. *Implementation* – the process of writing code. *Implementation* – internals of a *component*. *Infrastructure* – the lowest layer of a *system* that provides general-purpose functionality (tools) to its upper layers. *Input* – *events* or data that a *system* reacts to. *Integration* – see *orchestration*. *Integration complexity* – the *complexity* of understanding how individual *components interact* to make a *system*. *Interactions* – the kinds and routes of *communication* between *components* of a *system*. *Interface* – see *API*. *Latency* – the delay between a *system’s* receiving *input* and producing a corresponding *output*. *Layers* – *components* of a *system* partitioned by the level of *abstraction*. *Messaging* – communication by sending short pieces of data. *Method call* – invocation of an *interface* method (or procedure) of a *component* by another *component*. *Metapattern* – a cluster of *patterns* that have similar *topologies* and address related issues. *Module* – a *colocated* (in-process) *component*. *Non-functional requirements* (*NFR*s) – expected properties of a *system* (such as its stability or response time) which are crucial for the *system* to be built, *deployed*, and used successfully. Closely related to *qualities* and *forces*. *Notification* – an *event* that one *component* sends to another *component*(s) to inform them of some change. *Operational complexity* – see *integration complexity*. *Orchestration* – a kind of *workflow* where a single dedicated *component* (*Orchestrator*) makes use of (usually multiple) less *abstract components*. *Facade* \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] is a good example. *Output* – *actions* or data that a *system* produces. *Pattern* – a documented approach (blueprint) for solving a recurrent programming issue. *Pattern Language* – a set of interrelated *patterns* intended to cover most aspects of *designing systems* in a target *domain*. *Performance* – a measure of a *system*’s *throughput*, *latency* and *resource* consumption. *Persistent data* – data which survives rebooting the software. *Pipeline* – a set of *components* for stepwise *processing* of data. *Processing* – transformation of *input* data into *output* data. *Project* – the process of making a *system*. *Pub/sub (publish/subscribe)* – a mode of *communication* when one *component* (*subscriber*) receives a subset of *notifications* from another *component* (*publisher*). It is the *subscriber* that chooses which *notifications* it is interested in. *Qualities* – the properties which a *component* or (sub)*system* manifests to satisfy *forces*. *Real-time* – a *force* that requires the *system* to respond to incoming *events* immediately. *Request/confirmation* – a pair of *messages* between two *components* (Requestor and Executor). The *request* describes an *action* that the requestor wants the executor to run (R =\> E). The *confirmation* describes the results of the execution (R \<= E). *Requirements* – a set of rules that describe the correct (expected) behavior of the *system*. *Resources* – CPU, memory, network bandwidth and other stuff that costs money. *Scaling* – ability to increase *throughput* of a system by providing it with more *resources*. *Scenario* – see *use case*. *Service* – a *distributed component*. *Services* – *components* of a *system* partitioned by *subdomain*. *Sharding* – deploying multiple instances of a *component*. *Single point of failure* – a software or hardware *component* which if fails makes the entire *system* non-operational. High-*availability systems* should avoid *single points of failure*. [[wiki/concepts/source/basic-metapatterns/layers|*SPI*]] (*service provider interface*) – a set of *methods* or *messages* that a *component* expects to be supported by the *components* it uses. *State* – data that a *component* keeps between processing its *inputs*. *Structural diagram* – a graphical representation of the structure of a (sub-)*system* that shows *components* and their *interactions*. *Stub* – a very simple *implementation* of a *module* that allows other *components* that use it to run without starting the original *module*. *Stubs* are used to *implement modules* concurrently or *test* them in isolation. *Subdomain* – a distinct *cohesive* part of *domain* knowledge. *Synchronous communication* – the mode of communication when the *requesting component* waits for results of its *request* to another *component* before continuing to run its *task*. *System* – a self-sufficient set of *communicating components* that were brought together or *implemented* to satisfy its *users* (by running *use cases*). *Task* – a high-level sequence of execution steps. Similar to *use case* or *scenario*. *Team* – a few programmers and testers that work on a *component*. Teams of more than 5 members lose productivity to communication overhead. *Testing* – checking how satisfactorily the *system* behaves. *Throughput* – the amount of data a *system* can *process* per unit of time. *Topology* – A map of system components and their relations, similar to a *structural diagram*. *Use case* – a behavior expected by *system*’s users. A *system* is *implemented* to run *use cases*. *Use cases* are the high-level half of *business logic*. *User* – a human that uses a *system* and usually pays well if satisfied with its behavior. *Vendor lock-in* – a pitfall when a *system* relies on an external provider so much that it is impossible to change the provider. It is similar to falling prey to a monopoly. *Workflow* – a sequence of actions (*messages* or *method calls*) required to *implement* a *use case*. --- title: "History of changes" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/History of changes.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/History%20of%20changes source_license_note: "See namespace README; preserve attribution and source links." --- # History of changes > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/History of changes.md`. 0.1 (2020) – Description of my semisynchronous *Proactor* architecture for a VoIP gateway, published by dou.ua. It received very positive feedback and lots of comments from the community. 0.2 (2020) – [The same in a more official style](http://www.hillside.net/plop/2020/papers/poltorak.pdf) for the (Corona-)PLoP’20 conference. 0.3 (2021) – Comparison of choreography and orchestration for dou.ua. No impact. 0.4 (2022) – A series of 5 articles that looked into local and distributed architectures by applying the actor model. Positive feedback from dou.ua, but the series was interrupted by the war. 0.5 (2023) – [The same series in English](https://medium.com/itnext/introduction-to-software-architecture-with-actors-part-1-89de6000e0d3), published by ITNEXT and upvoted by r/softwarearchitecture. 0.6 (2023) – I attempted to rebuild the series for InfoQ but the first article was rejected as impractical (technology-agnostic). 0.7 (09-2024) – [Chapters from this book](https://medium.com/itnext/the-list-of-architectural-metapatterns-ed64d8ba125d), published by ITNEXT. Some of them were boosted by Medium. 0.8 (11-2024) – The complete book as a pdf. Clients were changed to mid-brown. Detailed evolutions were moved to the appendix. Rejected by Manning (the free license and color diagrams make the book unprofitable) and O’Reilly (it would get in the way of their bestsellers). Ignored by Addisson-Wesley. 0.9 (12-2024) – Integrated patterns from \[[wiki/concepts/source/appendices/books-referenced|[DDS]], [[wiki/concepts/source/appendices/books-referenced|LDDD]], [[wiki/concepts/source/appendices/books-referenced|SAHP]]\] and Internet sources, mostly affecting [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]], [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]], [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]], [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] and [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]]. Added diagrams for [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence* with derived storage]] and detailed evolutions for [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]]. Downgraded [[wiki/concepts/source/analytics/comparison-of-architectural-patterns|analytical chapters]] to sections and added a couple of new ones. Extended the [[wiki/concepts/source/analytics/ambiguous-patterns|ambiguous patterns chapter]]. Improved the structure of the variants sections of metapatterns: now each synonym has a short description. Fixed alignment of text and figures. Liked by [r/softwarearchitecture](https://www.reddit.com/r/softwarearchitecture/comments/1hi377v/free_book_architectural_metapatterns_the_pattern/). Rejected by The Pragmatic Programmer (they want “hands-on, actionable content”). Ignored by No Starch Press and Packt. 1.0 (04-2025) – Integrated \[[wiki/concepts/source/appendices/books-referenced|[DEDS]]\]. Integration logic (use cases) is now in green. Added [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*MVC*]]- and [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*MVP*]]-related patterns and a section on [[wiki/concepts/source/foundations-of-software-architecture/programming-and-architectural-paradigms|Programming and architectural paradigms]]. Replaced the chapter on [control and processing](https://medium.com/itnext/control-and-processing-software-9011fee8bc66) with a new one about [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|Four kinds of software]] and added another one called [[wiki/concepts/source/analytics/the-heart-of-software-architecture|The heart of software architecture]]. Made minor changes all over the book. Now I know how to [generate a table of contents for both EPUB and PDF versions](https://medium.com/@denyspoltorak/guide-on-converting-a-google-docs-text-into-an-ebook-5b1abc65f69d). Ignored by Wikibooks. 1.1 (07-2025) – Lars Noodén edited the book, fixing my poor English. Patterns are now in *Title Case Italics*. [*Domain-Oriented Microservice Architecture*]() was added. There are now short explanation sections (in gray) throughout the book. Rejected by [EuroPLoP](https://www.europlop.net/) and [AsianPLoP](https://plopcon.org/asianplop2026/) because I was unable to attend the conferences in person. Ignored by the [main PLoP](https://plopcon.org/). 1.2 (05-2026) – New chapters: [[wiki/concepts/source/introduction/system-topologies|System topologies]] and [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]] (replaced the boring *Combined Component*). Added [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cell*]], [[wiki/concepts/source/basic-metapatterns/layers|*ECB*]], [[wiki/concepts/source/extension-metapatterns/proxy|*User Interface*]], [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Pedestals*]], [[wiki/concepts/source/implementation-metapatterns/plugins|examples of *Plugins*]], and [[wiki/concepts/source/basic-metapatterns/layers|descriptions of layer roles]]. Changed the diagram for [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]]. Made many minor alterations. The book passed another cycle of editing. --- title: "Index of patterns" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Appendices/Index of patterns.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Appendices/Index%20of%20patterns source_license_note: "See namespace README; preserve attribution and source links." --- # Index of patterns > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Appendices/Index of patterns.md`. [[wiki/concepts/source/extension-metapatterns/proxy|Abstraction Layer]] (as a Proxy) [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Abstraction Layer]] (as a part of Hexagonal Architecture) [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Action-Domain-Responder]] (ADR) [[wiki/concepts/source/basic-metapatterns/services|Actors]] (architecture) [[wiki/concepts/source/implementation-metapatterns/mesh|Actors]] (as Mesh) [[wiki/concepts/source/basic-metapatterns/services|Actors]] (backend) [[wiki/concepts/source/basic-metapatterns/shards|Actors]] (create on demand) [[wiki/concepts/source/basic-metapatterns/services|Actors]] (embedded systems) [[wiki/concepts/source/basic-metapatterns/services|Actors]] (scope) [[wiki/concepts/source/extension-metapatterns/proxy|Adapter]] [[wiki/concepts/source/implementation-metapatterns/plugins|Add-in]] (component) [[wiki/concepts/source/implementation-metapatterns/plugins|Addon]] (component) [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Aggregate Data Product Quantum]] (Data Mesh) [[wiki/concepts/source/extension-metapatterns/proxy|Ambassador]] [[wiki/concepts/source/implementation-metapatterns/plugins|Ambassador Plugin]] [[wiki/concepts/source/extension-metapatterns/proxy|Anticorruption Layer]] (as a Proxy) [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Anticorruption Layer]] (as a part of Hexagonal Architecture) [[wiki/concepts/source/extension-metapatterns/orchestrator|API Composer]] [[wiki/concepts/source/extension-metapatterns/orchestrator|API Gateway]] (as an Orchestrator) [[wiki/concepts/source/extension-metapatterns/proxy|API Gateway]] (as a Proxy) [[wiki/concepts/source/extension-metapatterns/proxy|API Rate Limiter]] [[wiki/concepts/source/extension-metapatterns/proxy|API Service]] (adapter) [[wiki/concepts/source/extension-metapatterns/proxy|API Throttling]] [[wiki/concepts/source/extension-metapatterns/orchestrator|Application Layer]] (Orchestrator) [[wiki/concepts/source/extension-metapatterns/orchestrator|Application Service]] [[wiki/concepts/source/implementation-metapatterns/plugins|Aspects]] (Plugins) [[wiki/concepts/source/extension-metapatterns/orchestrator|Atomically Consistent Saga]] [Automotive SOA]() (as Service-Oriented Architecture) [[wiki/concepts/source/implementation-metapatterns/microkernel|AUTOSAR Classic Platform]] (as Microkernel) [[wiki/concepts/source/extension-metapatterns/proxy|Backend for Frontend]] (adapter) [Backends for Frontends]() (BFF) [[wiki/concepts/source/basic-metapatterns/pipeline|Batch Processing]] [[wiki/concepts/source/basic-metapatterns/monolith|Big Ball of Mud]] [[wiki/concepts/source/extension-metapatterns/shared-repository|Blackboard]] [[wiki/concepts/source/extension-metapatterns/sandwich|Blackboard System]] [[wiki/concepts/source/fragmented-metapatterns/hierarchy|Bottom-Up Hierarchy]] [[wiki/concepts/source/basic-metapatterns/layers|Boundary-Control-Entity]] (BCE) [[wiki/concepts/source/extension-metapatterns/middleware|Broker]] (Middleware) [[wiki/concepts/source/basic-metapatterns/pipeline|Broker Topology Event-Driven Architecture]] [[wiki/concepts/source/fragmented-metapatterns/hierarchy|Bus of Buses]] [[wiki/concepts/source/extension-metapatterns/proxy|Cache]] (read-through) [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Cache-Aside]] [[wiki/concepts/source/extension-metapatterns/proxy|Caching Layer]] [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Cell]] (WSO2 definition) [[wiki/concepts/source/extension-metapatterns/proxy|Cell Gateway]] (WSO2 Cell-Based Architecture) [[wiki/concepts/source/extension-metapatterns/proxy|Cell Router]] (Amazon Cell-Based Architecture) [[wiki/concepts/source/fragmented-metapatterns/hierarchy|Cell-Based Architecture]] (WSO2 version) [[wiki/concepts/source/fragmented-metapatterns/hierarchy|Cell-Based Microservice Architecture]] (WSO2 version) [[wiki/concepts/source/basic-metapatterns/shards|Cells]] (Amazon definition) [[wiki/concepts/source/basic-metapatterns/pipeline|Choreographed Event-Driven Architecture]] [[wiki/concepts/source/fragmented-metapatterns/layered-services|Choreographed Two-Layered Services]] [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Clean Architecture]] [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Cluster]] (group of services) [[wiki/concepts/source/extension-metapatterns/proxy|Command Line Interface]] (CLI) [[wiki/concepts/source/fragmented-metapatterns/layered-services|Command Query Responsibility Segregation]] (CQRS) [[wiki/concepts/source/extension-metapatterns/orchestrator|Composed Message Processor]] [[wiki/concepts/source/implementation-metapatterns/microkernel|Configuration File]] [[wiki/concepts/source/implementation-metapatterns/microkernel|Configurator]] [[wiki/concepts/source/implementation-metapatterns/microkernel|Container Orchestrator]] [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Content Delivery Network]] (CDN) [[wiki/concepts/source/extension-metapatterns/orchestrator|Control]] (Orchestrator) [[wiki/concepts/source/extension-metapatterns/orchestrator|Coordinator]] (Saga) [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|CQRS View Database]] [[wiki/concepts/source/basic-metapatterns/shards|Create on Demand]] (temporary instances) [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Data Archiving]] [[wiki/concepts/source/extension-metapatterns/shared-repository|Data Domain]] [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Data File]] [[wiki/concepts/source/extension-metapatterns/shared-repository|Data Grid]] (Space-Based Architecture) [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Data Lake]] [[wiki/concepts/source/basic-metapatterns/pipeline|Data Mesh]] [[wiki/concepts/source/basic-metapatterns/pipeline|Data Product Quantum]] (DPQ) [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Data Warehouse]] [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Database Cache]] [[wiki/concepts/source/extension-metapatterns/proxy|Database Abstraction Layer]] (DBAL or DAL) [[wiki/concepts/source/analytics/dependency-inversion-in-architectural-patterns|Dependency Inversion]] [[wiki/concepts/source/extension-metapatterns/middleware|Deployment Manager]] [[wiki/concepts/source/basic-metapatterns/services|Device Drivers]] [[wiki/concepts/source/extension-metapatterns/proxy|Direct Server Return]] [[wiki/concepts/source/extension-metapatterns/proxy|Dispatcher]] (Proxy) [[wiki/concepts/source/extension-metapatterns/proxy|Distributed Cache]] [[wiki/concepts/source/extension-metapatterns/middleware|Distributed Middleware]] [Distributed Monolith]() [[wiki/concepts/source/basic-metapatterns/services|Distributed Runtime]] (client point of view) [[wiki/concepts/source/implementation-metapatterns/microkernel|Distributed Runtime]] (internals) [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Document-View]] [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Domain]] (Uber definition for WSO2-style Cell) [[wiki/concepts/source/basic-metapatterns/layers|Domain-Driven Design]] (layers) [Domain-Oriented Microservice Architecture]() (DOMA) [[wiki/concepts/source/basic-metapatterns/services|Domain Services]] (scope) [[wiki/concepts/source/implementation-metapatterns/microkernel|Domain-Specific Language]] (DSL) [[wiki/concepts/source/extension-metapatterns/proxy|Driver]] [[wiki/concepts/source/extension-metapatterns/proxy|Edge Service]] [[wiki/concepts/source/basic-metapatterns/layers|Embedded systems]] (layers) [[wiki/concepts/source/extension-metapatterns/middleware|Enterprise Service Bus]] (as Middleware) [[wiki/concepts/source/extension-metapatterns/orchestrator|Enterprise Service Bus]] (as Orchestrator) [Enterprise Service-Oriented Architecture]() [[wiki/concepts/source/basic-metapatterns/layers|Entity-Boundary-Control]] (EBC) [[wiki/concepts/source/basic-metapatterns/layers|Entity-Control-Boundary]] (ECB) [[wiki/concepts/source/basic-metapatterns/pipeline|Event Collaboration]] [[wiki/concepts/source/basic-metapatterns/pipeline|Event-Driven Architecture]] (EDA) [[wiki/concepts/source/extension-metapatterns/middleware|Event Mediator]] (as Middleware) [[wiki/concepts/source/extension-metapatterns/orchestrator|Event Mediator]] (as Orchestrator) [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Event-Sourced View]] [[wiki/concepts/source/extension-metapatterns/orchestrator|Eventually Consistent Saga]] [[wiki/concepts/source/implementation-metapatterns/plugins|Extension]] (component) [[wiki/concepts/source/implementation-metapatterns/plugins|Extension Architecture]] [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|External Search Index]] [[wiki/concepts/source/basic-metapatterns/services|FaaS]] (nanoservices) [[wiki/concepts/source/basic-metapatterns/pipeline|FaaS]] (pipelined) [[wiki/concepts/source/extension-metapatterns/orchestrator|Facade]] [[wiki/concepts/source/extension-metapatterns/proxy|Firewall]] [[wiki/concepts/source/implementation-metapatterns/plugins|Flavors]] (Plugins) [[wiki/concepts/source/extension-metapatterns/orchestrator|Front Controller]] (query service of a pipeline) [[wiki/concepts/source/extension-metapatterns/proxy|Frontend]] [[wiki/concepts/source/extension-metapatterns/proxy|Full Proxy]] [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Game Development Engine]] [[wiki/concepts/source/extension-metapatterns/proxy|Gateway]] (adapter) [[wiki/concepts/source/extension-metapatterns/orchestrator|Gateway Aggregation]] [[wiki/concepts/source/extension-metapatterns/proxy|Graphical User Interface]] (GUI) [[wiki/concepts/source/implementation-metapatterns/mesh|Grid]] [[wiki/concepts/source/extension-metapatterns/proxy|Half-Proxy]] [[wiki/concepts/source/basic-metapatterns/monolith|Half-Sync/Half-Async]] [[wiki/concepts/source/extension-metapatterns/proxy|Hardware Abstraction Layer]] (HAL) [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Hexagonal Architecture]] [[wiki/concepts/source/basic-metapatterns/services|Hexagonal Service]] [[wiki/concepts/source/fragmented-metapatterns/hierarchy|Hierarchical Model-View-Controller]] (HMVC) [[wiki/concepts/source/fragmented-metapatterns/hierarchy|Hierarchy]] [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Historical Data]] [[wiki/concepts/source/implementation-metapatterns/plugins|Hooks]] (Plugins) [[wiki/concepts/source/extension-metapatterns/proxy|Human-Machine Interface]] (HMI) [[wiki/concepts/source/implementation-metapatterns/microkernel|Hypervisor]] [[wiki/concepts/source/fragmented-metapatterns/hierarchy|In-Depth Hierarchy]] [[wiki/concepts/source/extension-metapatterns/proxy|Ingress Controller]] [[wiki/concepts/source/basic-metapatterns/shards|Instances]] [[wiki/concepts/source/extension-metapatterns/shared-repository|Integration Database]] [[wiki/concepts/source/extension-metapatterns/orchestrator|Integration Service]] [[wiki/concepts/source/extension-metapatterns/orchestrator|Integration Microservice]] [[wiki/concepts/source/implementation-metapatterns/microkernel|Interpreter]] [[wiki/concepts/source/basic-metapatterns/monolith|Lambda Monolith]] [[wiki/concepts/source/basic-metapatterns/monolith|Lambdalith]] [[wiki/concepts/source/basic-metapatterns/shards|Lambdas]] [[wiki/concepts/source/basic-metapatterns/layers|Layered Architecture]] [Layered Microservice Architecture]() (Backends for Frontends) [[wiki/concepts/source/basic-metapatterns/layers|Layered Monolith]] [[wiki/concepts/source/basic-metapatterns/services|Layered Service]] [[wiki/concepts/source/fragmented-metapatterns/layered-services|Layered Services]] (architecture) [[wiki/concepts/source/basic-metapatterns/layers|Layers]] [[wiki/concepts/source/implementation-metapatterns/mesh|Leaf-Spine Architecture]] [[wiki/concepts/source/extension-metapatterns/proxy|Load Balancer]] [[wiki/concepts/source/implementation-metapatterns/plugins|Logic Extension]] [[wiki/concepts/source/basic-metapatterns/services|Macroservices]] [[wiki/concepts/source/extension-metapatterns/proxy|Man-Machine Interface]] (MMI) [[wiki/concepts/source/extension-metapatterns/orchestrator|MapReduce]] [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Materialized View]] [[wiki/concepts/source/extension-metapatterns/orchestrator|Mediator]] [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Memory Image]] [[wiki/concepts/source/implementation-metapatterns/mesh|Mesh]] [[wiki/concepts/source/extension-metapatterns/middleware|Message Broker]] [[wiki/concepts/source/extension-metapatterns/middleware|Message Bus]] [[wiki/concepts/source/extension-metapatterns/proxy|Message Translator]] (adapter) [[wiki/concepts/source/extension-metapatterns/proxy|Messaging Grid]] (Space-Based Architecture) [[wiki/concepts/source/extension-metapatterns/proxy|Microgateway]] [[wiki/concepts/source/implementation-metapatterns/microkernel|Microkernel]] [[wiki/concepts/source/implementation-metapatterns/plugins|Microkernel]] (Plugins) [[wiki/concepts/source/implementation-metapatterns/plugins|Microkernel Architecture]] (Plugins) [[wiki/concepts/source/basic-metapatterns/services|Microservices]] (architecture) [[wiki/concepts/source/basic-metapatterns/services|Microservices]] (scope) [[wiki/concepts/source/extension-metapatterns/middleware|Middleware]] [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Model 1]] (MVC1) [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Model 2]] (MVC2) [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Model-View-Adapter]] (MVA) [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Model-View-Controller]] (MVC) [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Model-View-Presenter]] (MVP) [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Model-View-ViewModel]] (MVVM) [[wiki/concepts/source/basic-metapatterns/services|Modular Monolith]] [[wiki/concepts/source/basic-metapatterns/services|Modules]] [[wiki/concepts/source/basic-metapatterns/services|Modulith]] [[wiki/concepts/source/basic-metapatterns/monolith|Monolambda]] (lambda Monolith) [[wiki/concepts/source/basic-metapatterns/monolith|Monolith]] [[wiki/concepts/source/basic-metapatterns/monolith|Monolithic Architecture]] [[wiki/concepts/source/basic-metapatterns/services|Monolithic Service]] [[wiki/concepts/source/basic-metapatterns/shards|Multitenancy]] [[wiki/concepts/source/basic-metapatterns/layers|Multitier Architecture]] [[wiki/concepts/source/extension-metapatterns/orchestrator|Multi-Worker]] [[wiki/concepts/source/basic-metapatterns/services|Nanoservices]] (API layer) [[wiki/concepts/source/basic-metapatterns/services|Nanoservices]] (as runtime) [[wiki/concepts/source/extension-metapatterns/sandwich|Nanoservices]] (as a Sandwich) [[wiki/concepts/source/basic-metapatterns/pipeline|Nanoservices]] (pipelined) [[wiki/concepts/source/basic-metapatterns/services|Nanoservices]] (scope) [Nanoservices]() (SOA) [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Native Data Product Quantum]] (sDPQ) [[wiki/concepts/source/basic-metapatterns/pipeline|Nearline System]] [[wiki/concepts/source/fragmented-metapatterns/hierarchy|Network of Networks]] [[wiki/concepts/source/basic-metapatterns/layers|N-Tier Architecture]] [[wiki/concepts/source/basic-metapatterns/pipeline|Offline System]] [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Onion Architecture]] [[wiki/concepts/source/extension-metapatterns/proxy|Open Host Service]] (as a Proxy) [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Open Host Service]] (as a part of Hexagonal Architecture) [[wiki/concepts/source/implementation-metapatterns/microkernel|Operating System]] [[wiki/concepts/source/extension-metapatterns/proxy|Operating System Abstraction Layer]] (OSAL or OAL) [[wiki/concepts/source/extension-metapatterns/proxy|Operator Interface]] [[wiki/concepts/source/extension-metapatterns/orchestrator|Orchestrated Saga]] [[wiki/concepts/source/extension-metapatterns/orchestrator|Orchestrated Services]] [[wiki/concepts/source/fragmented-metapatterns/layered-services|Orchestrated Three-Layered Services]] [[wiki/concepts/source/extension-metapatterns/orchestrator|Orchestrator]] [[wiki/concepts/source/fragmented-metapatterns/hierarchy|Orchestrator of Orchestrators]] [[wiki/concepts/source/basic-metapatterns/shards|Partition]] [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Pedestal]] [[wiki/concepts/source/implementation-metapatterns/mesh|Peer-to-Peer Networks]] [[wiki/concepts/source/extension-metapatterns/middleware|Persistent Event Log]] (as Middleware) [[wiki/concepts/source/extension-metapatterns/shared-repository|Persistent Event Log]] (as Shared Repository) [[wiki/concepts/source/basic-metapatterns/pipeline|Pipeline]] [[wiki/concepts/source/basic-metapatterns/pipeline|Pipes and Filters]] [[wiki/concepts/source/extension-metapatterns/proxy|Platform Abstraction Layer]] (PAL) [[wiki/concepts/source/implementation-metapatterns/microkernel|Pluggable Component Framework]] [[wiki/concepts/source/implementation-metapatterns/plugins|Plug-In Architecture]] [[wiki/concepts/source/implementation-metapatterns/plugins|Plugin]] (component) [[wiki/concepts/source/implementation-metapatterns/plugins|Plugins]] [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Polyglot Persistence]] [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Ports and Adapters]] [[wiki/concepts/source/basic-metapatterns/shards|Pool]] (stateless instances) [[wiki/concepts/source/fragmented-metapatterns/hierarchy|Presentation-Abstraction-Control]] (PAC) [[wiki/concepts/source/extension-metapatterns/proxy|Presentation Layer]] [[wiki/concepts/source/basic-metapatterns/monolith|Proactor]] [[wiki/concepts/source/extension-metapatterns/orchestrator|Process Manager]] [[wiki/concepts/source/extension-metapatterns/orchestrator|Processing Grid]] (Space-Based Architecture) [[wiki/concepts/source/extension-metapatterns/proxy|Proxy]] [[wiki/concepts/source/extension-metapatterns/proxy|Published Language]] [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Query Service]] [[wiki/concepts/source/extension-metapatterns/proxy|Rate Limiter]] [[wiki/concepts/source/basic-metapatterns/monolith|Reactor]] (multi-threaded) [[wiki/concepts/source/basic-metapatterns/monolith|Reactor]] (single-threaded) [[wiki/concepts/source/basic-metapatterns/monolith|(Re)Actor-with-Extractors]] [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Read-Only Replicas]] [[wiki/concepts/source/extension-metapatterns/proxy|Read-Through Cache]] [[wiki/concepts/source/implementation-metapatterns/plugins|Reflection]] (Plugins) [[wiki/concepts/source/extension-metapatterns/orchestrator|Remote Facade]] [[wiki/concepts/source/basic-metapatterns/shards|Replica]] [[wiki/concepts/source/extension-metapatterns/proxy|Replicated Cache]] [[wiki/concepts/source/basic-metapatterns/shards|Replicated Load-Balanced Services]] (instances) [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Reporting Database]] [[wiki/concepts/source/extension-metapatterns/proxy|Repository]] [[wiki/concepts/source/basic-metapatterns/shards|Request Hedging]] [[wiki/concepts/source/extension-metapatterns/proxy|Response Cache]] [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Resource-Method-Representation]] (RMR) [[wiki/concepts/source/extension-metapatterns/proxy|Reverse Proxy]] [[wiki/concepts/source/implementation-metapatterns/microkernel|Saga Engine]] (Microkernel) [[wiki/concepts/source/extension-metapatterns/orchestrator|Saga Execution Component]] [[wiki/concepts/source/extension-metapatterns/orchestrator|Saga Orchestrator]] [[wiki/concepts/source/extension-metapatterns/sandwich|Sandwich]] [[wiki/concepts/source/basic-metapatterns/services|Scaled Service]] [[wiki/concepts/source/extension-metapatterns/orchestrator|Scatter-Gather]] [[wiki/concepts/source/extension-metapatterns/proxy|Scheduler]] [[wiki/concepts/source/implementation-metapatterns/microkernel|Script]] [[wiki/concepts/source/fragmented-metapatterns/hierarchy|Segmented Microservice Architecture]] [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Separated Presentation]] [[wiki/concepts/source/basic-metapatterns/services|Service-Based Architecture]] (architecture) [[wiki/concepts/source/extension-metapatterns/shared-repository|Service-Based Architecture]] (shared database) [[wiki/concepts/source/extension-metapatterns/orchestrator|Service Layer]] (Orchestrator) [[wiki/concepts/source/implementation-metapatterns/mesh|Service Mesh]] (as Mesh) [[wiki/concepts/source/extension-metapatterns/middleware|Service Mesh]] (as Middleware) [[wiki/concepts/source/basic-metapatterns/services|Service of Services]] [Service-Oriented Architecture]() (SOA) [[wiki/concepts/source/basic-metapatterns/services|Services]] [[wiki/concepts/source/fragmented-metapatterns/hierarchy|Services of Services]] [[wiki/concepts/source/basic-metapatterns/shards|Sharding]] (persistent slices of data) [[wiki/concepts/source/extension-metapatterns/proxy|Sharding Proxy]] [[wiki/concepts/source/basic-metapatterns/shards|Shards]] [[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Database]] [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Shared Databases]] (Polyglot Persistence) [[wiki/concepts/source/extension-metapatterns/middleware|Shared Event Store]] (as Middleware) [[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Event Store]] (as Shared Repository) [[wiki/concepts/source/extension-metapatterns/shared-repository|Shared File System]] [[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Memory]] [[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Repository]] [[wiki/concepts/source/extension-metapatterns/proxy|Sidecar]] [[wiki/concepts/source/implementation-metapatterns/microkernel|Software Framework]] (Microkernel) [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Source-Aligned Data Product Quantum]] (Data Mesh) [[wiki/concepts/source/implementation-metapatterns/mesh|Space-Based Architecture]] (as Mesh) [[wiki/concepts/source/extension-metapatterns/sandwich|Space-Based Architecture]] (as Sandwich) [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Specialized Databases]] [[wiki/concepts/source/implementation-metapatterns/mesh|Spine-Leaf Architecture]] [[wiki/concepts/source/extension-metapatterns/shared-repository|Stamp Coupling]] [[wiki/concepts/source/implementation-metapatterns/plugins|Strategy]] (Plugins) [[wiki/concepts/source/basic-metapatterns/pipeline|Stream Processing]] [[wiki/concepts/source/basic-metapatterns/layers|Three-Tier Architecture]] [[wiki/concepts/source/basic-metapatterns/layers|Tiers]] [[wiki/concepts/source/fragmented-metapatterns/hierarchy|Top-Down Hierarchy]] [[wiki/concepts/source/extension-metapatterns/orchestrator|Transaction Script]] [[wiki/concepts/source/extension-metapatterns/proxy|User Interface]] [[wiki/concepts/source/implementation-metapatterns/microkernel|Virtualizer]] [[wiki/concepts/source/basic-metapatterns/shards|Work Queue]] [[wiki/concepts/source/basic-metapatterns/pipeline|Workflow System]] [[wiki/concepts/source/extension-metapatterns/orchestrator|Workflow Owner]] (Orchestrator) [[wiki/concepts/source/extension-metapatterns/orchestrator|Wrapper Facade]] (Orchestrator) [[wiki/concepts/source/extension-metapatterns/proxy|Write-Behind Cache]] [[wiki/concepts/source/extension-metapatterns/proxy|Write-Through Cache]] --- title: "Basic metapatterns" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Basic metapatterns/Basic metapatterns.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Basic%20metapatterns/Basic%20metapatterns source_license_note: "See namespace README; preserve attribution and source links." --- # Basic metapatterns > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Basic metapatterns/Basic metapatterns.md`. Basic [[wiki/concepts/source/introduction/metapatterns|metapatterns]] are both common stand-alone architectures and building blocks for more complex systems. They include the single-component *Monolithic Architecture* and the results of its division along each of the [[wiki/concepts/source/introduction/metapatterns|coordinate axes]], namely *abstractness*, *subdomain*, and *sharding*: ### [[wiki/concepts/source/basic-metapatterns/monolith|Monolith]] ![A diagram of Monolith, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Monolith.png) A [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] is a single-component system, the simplest possible architecture. It is easy to write but hard to evolve and maintain. *Includes*: Reactor, Proactor, and Half-Sync/Half-Async. ### [[wiki/concepts/source/basic-metapatterns/shards|Shards]] ![A diagram of Shards, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Shards.png) [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]] are multiple instances of a [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]]. They are scalable but usually require an external component for coordination. *Includes*: Shards and Amazon Cells, Replicas, Pool of Stateless Instances, and Create on Demand. ### [[wiki/concepts/source/basic-metapatterns/layers|Layers]] ![A diagram of Layered Architecture, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Layers.png) [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] contain one component per level of abstraction. The layers may vary in technologies and qualities and scale individually. *Includes*: Layers and Tiers. ### [[wiki/concepts/source/basic-metapatterns/services|Services]] ![A diagram of Services, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Services.png) [[wiki/concepts/source/basic-metapatterns/services|*Services*]] organize a system into subdomains, often resulting in parts of comparable size which can be assigned to dedicated teams. However, a system of *Services* is hard to synchronize or debug. *Includes*: Service-Based Architecture, Modular Monolith (Modulith), Microservices, Device Drivers, and Actors. ### [[wiki/concepts/source/basic-metapatterns/pipeline|Pipeline]] ![A diagram of Pipeline, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Pipeline.png) A [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]] is a kind of [[wiki/concepts/source/basic-metapatterns/services|*Services*]] with unidirectional flow. Each service implements a single step of data processing. The system is flexible but may grow out of control. *Includes*: Pipes and Filters, Choreographed Event-Driven Architecture, and Data Mesh. --- title: "Layers" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Basic metapatterns/Layers.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Basic%20metapatterns/Layers source_license_note: "See namespace README; preserve attribution and source links." --- # Layers > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Basic metapatterns/Layers.md`. ![A diagram for Layered Architecture, in abstractness-subdomain-sharding coordinates.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Main/Layers.png) *Yet another layer of indirection.* Separate business logic from implementation details. Known as: Layers \[[wiki/concepts/source/appendices/books-referenced|[POSA1]], [[wiki/concepts/source/appendices/books-referenced|POSA4]]\], Layered Architecture \[[wiki/concepts/source/appendices/books-referenced|[SAP]], [[wiki/concepts/source/appendices/books-referenced|FSA]], [[wiki/concepts/source/appendices/books-referenced|LDDD]]\], Multitier Architecture, and N-tier Architecture \[[wiki/concepts/source/appendices/books-referenced|[LDDD]]\]. Structure: A component per level of abstractness. Type: System topology, implementation. | *Benefits* | *Drawbacks* | | --- | --- | | Rapid start for development | Quickly deteriorates as the project grows | | Easy debugging | Hard to develop with more than a few teams | | Good performance | Does not solve force conflicts between subdomains | | Development teams may specialize | Does not support aggressive optimizations | | Business logic is encapsulated | | | Allows for the resolution of conflicting forces, including the use of specialized technologies | | | Deployment to dedicated hardware | | | Layers with no business logic are reusable | | References: \[[wiki/concepts/source/appendices/books-referenced|[POSA1]]\] and \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] discuss layered software in depth; \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\] promotes the layered style; most of the architectures in Herberto Graça’s [Software Architecture Chronicles](https://herbertograca.com/2017/07/03/the-software-architecture-chronicles/) are layered. The Wiki has a reasonably [good article](https://en.wikipedia.org/wiki/Multitier_architecture). *Layering* a system creates interfaces between its levels of abstractness (high-level [use cases](#application-use-cases-or-integration), lower-level [domain logic](#domain-business-rules-or-model), and infrastructure) while also retaining monolithic cohesiveness within each of the levels. That allows both for easy debugging inside each individual layer (no need to jump into another programming language or re-attach the debugger to a remote server) and enough flexibility to have a dedicated development team, tools, deployment, and scaling policies for each layer. Though layered code is slightly better than that of [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]], thanks to the separation of concerns, one of the upper (business logic) layers may nonetheless grow too large for efficient development. Splitting a system into layers tends to resolve conflicts of forces between its abstract and highly optimized parts: the top-level business logic changes rapidly and does not require much optimization (as its methods are called infrequently), thus it can be written in a high-level programming language. In contrast, infrastructure, which is called thousands of times per second, has stable workflows and interfaces but must be thoroughly optimized and extremely well tested. Many patterns have one or more of their layers split into subdomains, resulting in a layer of *services*. That causes no penalties as long as the services are completely independent (when the original layer had zero coupling between its subdomains), which happens if each of the services deals with a separate subset of requests (as in [*Backends for Frontends*]()) or is choreographed by an upper layer (as in [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]], [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]], or [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchy*]]) which boils down to the same “separate subset of subrequests” under the hood. However, if the services which form a layer need to intercommunicate, you immediately get a whole set of troubles with debugging, sharing data, and performance characteristic of the [[wiki/concepts/source/basic-metapatterns/services|*Services*]] architecture. ![Diagrams of Backends for Frontends and Services with Polyglot Persistence.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Misc/Layers%20of%20Services.png) > Thanks to its substantial benefits and minor drawbacks, and the many evolutions it supports, *Layers* became the default architecture for starting new projects. ### Performance The performance of a layered system is shaped by two factors: - Communication between layers is slower than within a layer. Components of a layer may access each other’s data directly, while accessing another layer involves data transformation (as interfaces tend to operate generic data structures), serialization, and often [IPC](https://en.wikipedia.org/wiki/Inter-process_communication) or networking. - The frequency and granularity of events or actions increases as we move from the upper more abstract layers to lower-level components that interface an OS or hardware. > An ideal component should be replaceable and reusable. As soon as a component exposes details of its implementation, such as workflows or data types, in its interface, it becomes incompatible with other possible implementations, and its interface may even see major changes as the related internals of the component evolve. Therefore, well-behaving components tend to have their interfaces written in most generic terms, which requires inputs to be transformed to their internal formats and thus penalizes performance. There is a number of optimizations to reduce interlayer calls: ![Caching the latest known state of the system in its highest layer.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Performance/Layers-caching.png) *Caching*: an upper layer tends to *model* (cache last known state of) the layers below it. This way it can behave as if it knew the state of the whole system without querying the actual state from the hardware present at the bottom of the system’s stack of layers. Such an approach is universal for [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|*control software*]]. For example, a network monitoring suite shows you the last known state of all the components it observes without actually querying them – it is subscribed to various notifications and remembers what and when each device has previously reported. ![Aggregation of events from hardware by the lowest layer of a layered system.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Performance/Layers-aggregation.png) *Aggregation*: a lower layer collects multiple events before notifying the layer above it to avoid being overly chatty. An example is an [IIoT](https://en.wikipedia.org/wiki/Industrial_internet_of_things) field gateway that collects data from all the sensors in the building and sends it in a single report to the server. Or consider a data transfer over a network where a low-level driver collects multiple data packets that come from the hardware and sends an acknowledgement for each of them while waiting for a datagram or file transfer to complete. It notifies its client software only once when all the data has been collected and its integrity confirmed. ![Sending a batch of commands all the way down to the lowest layer of a system.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Performance/Layers-batching.png) *Batching*: an upper layer forms a queue of commands and sends it as a single job to the layer below it. This takes place in drivers for complex low-level hardware, like printers, or in database access as *stored procedures*. \[[wiki/concepts/source/appendices/books-referenced|[POSA4]]\] describes variants of this approach as the *Combined Method*, *Enumeration Method*, and *Batch Method* patterns. Programming languages and frameworks may implement *foreach* and *MapReduce* which allow for a single command to operate on multiple pieces of data. ![Moving a part of the business logic from the highest layer to the lowest layer of the system.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Performance/Layers-injection.png) *Strategy injection*: an upper layer installs an event handler (hook or [[wiki/concepts/source/implementation-metapatterns/plugins|*Ambassador Plugin*]]) into the lower layer. The goal is for the hook to do basic pre-processing, filtering, aggregation, and decision making to process the majority of events autonomously while escalating to the upper layer in exceptional or important cases. That may help in such time-critical domains as [high-frequency trading](https://en.wikipedia.org/wiki/High-frequency_trading). Layers can be scaled independently, as [[wiki/concepts/source/foundations-of-software-architecture/forces-asynchronicity-and-distribution|exemplified]] by common web applications that comprise a highly scalable and resource-consuming frontend, somewhat scalable backend, and unscalable data layer. Another example is an OS (lower layer) that runs multiple user applications (upper layer). ### Dependencies Usually an upper layer depends on the *API* (*application programming interface*) of the layer directly below it. That makes sense as the lower the layer is, the more stable it tends to be: a user-facing software gets updated on a daily or weekly basis while hardware drivers may not change for years. As every update of a component may destabilize other components that depend on it, it is much more preferable for a quickly evolving component to depend on others instead of the other way round. Some domains, including embedded systems and telecom, require their lower layers to be polymorphic as they deal with varied hardware or communication protocols. In that case an upper layer (e.g. OS kernel) defines a *service provider interface* (*SPI*) which is implemented by every variant of the lower layer (e.g. a device driver). That allows for a single implementation of the upper layer to be interoperable with any subclass of the lower layer. Such an approach enables [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]], [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]], and [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]]. There may also be an [[wiki/concepts/source/extension-metapatterns/proxy|*Adapter*]] layer between your system’s SPI and an external API. It is called *Anticorruption Layer* \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\], [*Database Abstraction Layer*](https://en.wikipedia.org/wiki/Database_abstraction_layer) / *Database Access Layer* \[[wiki/concepts/source/appendices/books-referenced|[POSA4]]\] / *Data Mapper* \[[wiki/concepts/source/appendices/books-referenced|[PEAA]]\], *OS Abstraction Layer*, or *Platform Abstraction Layer / Hardware Abstraction Layer*, depending on what kind of component it adapts. ![Individual layers may depend on other layers' APIs, SPIs, or both. In the last case the layer between the SPI and API is an adapter.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Dependencies/Layers-1.png) A layer can be *closed* (*strict*) or *open* (*relaxed*). A layer above a closed layer depends only on the closed layer right below it – it does not see through it. Conversely, a layer above an open layer may depend on both the open layer and the layer below it – the open layer is transparent. That helps keep a layer which encapsulates only one or two subdomains small: if such a layer were closed, it would have to copy much of the interface of the layer below it just to pass the incoming requests which it does not know how to handle through to the layer below. The size optimization of open layers has a cost: the team that works on the layer above an open layer needs to learn APIs of both layers below it, which may even differ in their terminologies. ![Dependencies for open and closed layers.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Dependencies/Layers-2.png) If you ever need to *scale* (run multiple instances of) a layer, you may notice that a layer which sends requests naturally supports multiple instances, either through the use of communication channels or with the instance address being appended to each request so that its destination layer knows where to send the response. On the other hand, if there are multiple instances of a layer you call into, you need a kind of [[wiki/concepts/source/extension-metapatterns/proxy|*Load Balancer*]] to dispatch requests among the instances. ![A load balancer helps access multiple instances of a layer directly below it.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Dependencies/Layers-3.png) ### Applicability *Layers* are good for: - *Small and medium-sized projects.* Separating the business logic from the low-level code should be enough to work comfortably on codebases below 100 000 lines in size. - *Specialized teams*. You can have a team per layer: some people, who are proficient in optimization, work on the highly loaded infrastructure, while others talk to the customers and write the business logic. - *Deployment to a specific hardware.* Frontend instances run on client devices, a backend needs much RAM, the data layer demands a large HDD and security. There is no way to unite them into a single generic component. - *Flexible scaling.* It is common to have hundreds or thousands of frontend instances being served by several backend processes that use a single database. - *Real-time systems*. Hardware components and network events often need the software to respond within a set time limit. This is achievable by separating the time-critical code from normal priority calculations. See [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]], [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]], and [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchy*]] for improved solutions. *Layers* are bad for: - *Large projects.* You are still going to enter *monolithic hell* \[[wiki/concepts/source/appendices/books-referenced|[MP]]\] much sooner than your project will reach 1 000 000 lines of code. - *Low-latency decision making*. If your business logic needs to be applied in real time, you cannot tolerate the extra latency caused by the interlayer communication. ### Relations ![Splitting a layer into services and splitting a service into layers.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Relations/Layers.png) *Layers*: - Can be applied to the internals of any component, for example, layering [[wiki/concepts/source/basic-metapatterns/services|*Services*]] results in [[wiki/concepts/source/fragmented-metapatterns/layered-services|*Layered Services*]]. - Can be altered by [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]] or extended with a [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]], [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]], and/or [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]] which would become an extra layer. - Can be implemented by [[wiki/concepts/source/basic-metapatterns/services|*Services*]] yielding layers of services present in [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]], [*Service-Oriented Architecture*](), [*Backends for Frontends*](), and [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]]. - May be closely related to [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]]. - A layer often serves as a [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]], [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]], and/or [[wiki/concepts/source/extension-metapatterns/shared-repository|*(Shared) Repository*]], see [below](#variants-of-layer-roles). ## Variants by isolation There are [[wiki/concepts/source/foundations-of-software-architecture/forces-asynchronicity-and-distribution|several grades]] of layer isolation between unstructured [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] and distributed [*Tiers*](#three-tier-architecture). All of them are widely used in practice: each step adds its specific benefits and drawbacks to those of the previous stages until at some point it makes more sense to reject the next deal because its cons are too inconvenient for you. ### Synchronous layers, Layered Monolith First you separate the high-level logic from low-level implementation details. Then draw interfaces between them. The layers will still call each other directly, but at least the code has been sorted out into some kind of structure, and you can now have two or three dedicated teams, one per layer. The cost is quite low – it is that the newly created interfaces will stand in the way of aggressive performance optimization. | *Benefits* | *Drawbacks* | | --- | --- | | Structured code | Lost opportunities for interlayer optimization | | Two or three teams | | ### Asynchronous layers The next step you may decide to take could be to isolate the layers’ execution threads and data. The layers will communicate only through in-process messages, which are slower than direct calls and harder to debug, but now each layer can run at its own pace – a must for [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|interactive systems]]. | *Benefits* | *Drawbacks* | | --- | --- | | Structured code | No opportunities for interlayer optimization | | Two or three teams | Some troubles with debugging | | The layers may differ in latency | | ### A process per layer Next, you may run each layer in a separate process. You have to devise an efficient means of communication between them, but now the layers may differ in technologies, security, frequency of deployment, and even stability – the crash of one layer does not directly impact any of the others. Moreover, you may scale each layer to make good use of the available CPU cores. However, you will pay through even harder debugging, lower performance, and you will have to take care of error recovery, because if one of the components crashes, the others are likely to remain in an inconsistent state. | *Benefits* | *Drawbacks* | | --- | --- | | Structured code | No opportunities for interlayer optimization | | Two or three teams | Troublesome debugging | | The layers may differ in latency | Some performance penalty | | The layers may differ in technologies | Error recovery must be addressed | | The layers are deployed independently | | | Software security isolation | | | Software fault isolation | | | Limited scalability | | ### Distributed Tiers Finally, you may separate the hardware which the processes run on – going all out for distribution. This allows you to fine-tune the resources available for each layer, run parts of the system close to its clients, and physically isolate the most secure components, with your scalability limited only by your budget. The price is paid in latency and debugging experience. | *Benefits* | *Drawbacks* | | --- | --- | | Structured code | No opportunities for interlayer optimization | | Two or three teams | Even worse debugging | | The layers may differ in latency | Definite performance penalty | | The layers may differ in technologies | Error recovery must be addressed | | The layers are deployed independently | | | Full security isolation | | | Full fault isolation | | | Full scalability | | | Layers vary in hardware setup | | | Deployment close to clients | | ## Variants of layer roles Though the structure of every software system is unique, there is a common set of roles or functions that need to be covered by its code. It is generally accepted that a piece of code should [*do one thing, and do it well*](https://en.wikipedia.org/wiki/Unix_philosophy#Do_One_Thing_and_Do_It_Well), which often makes the code written to support the same kind of functionality stick together and end up in a dedicated layer of a system. That also helps your teams specialize and keeps their cognitive load low by limiting the amount of code they deal with, which [allows for high productivity](https://realmensch.org/2018/05/04/we-are-all-10x-developers/). That clarity of design, which separates technically different pieces, is opposed by a host of more pragmatic [[wiki/concepts/source/foundations-of-software-architecture/forces-asynchronicity-and-distribution|*forces*]] that compel you to keep your code together: - Performance can be easily optimized inside a component, while any communication between components will likely be much slower. - If many distinct workflows traverse a set of components, those components require large interfaces which take much effort to design, implement, and support. You may end up spending more time maintaining your perfect architecture than writing the business logic which earns money for the company. - The more components you have, the harder it is to deploy them and keep them consistent, not to mention error recovery. Moreover, as the number of system components increases, the big picture becomes elusive, and soon there is nobody who knows how to change the system if need arises. Balancing the [[wiki/concepts/source/analytics/cohesers-and-decouplers|cohesers and decouplers]] listed above usually results in coarse-grained system components each of which covers several concerns, with some real-world system compositions shown in the [Examples section](#examples) later in this chapter. However, first we need to see which kinds of roles a system layer may incorporate: ![A stack of layers: client or user, interface, application, domain, generic code, communication, data, and operating system and hardware.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Layer%20Roles.png) ### Interface (API or UI) If a system serves a human user or remote client software, there is a part of it, called an *interface*, that deals with communication and translation between the system’s internal data model and one convenient for its clients. As an interface represents the system to its clients, it is a kind of [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]] by definition \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\]. If the client is another software system, the interface is called an *Application Programming Interface* (*API*) and is likely to be implemented by a [[wiki/concepts/source/extension-metapatterns/proxy|*Gateway*]] which will receive a message through a well-known protocol, check its correctness and authenticity of the sender, and forward the message’s payload to a layer below it. In most cases it will also send response and notification messages by executing the converse tasks: translation from the system’s internal data format to something more convenient for its clients and sending the resulting message over a network protocol. When a system interacts with a human, it exposes another kind of interface – [[wiki/concepts/source/extension-metapatterns/proxy|*Human-Machine Interface* (*HMI*) or *User Interface* (*UI*)]]. The basics of its action are similar to the case of software-to-software interaction described above save that humans prefer visual or textual information instead of a highly structured Internet protocol. Another, less common kind of interface is called *Service Provider Interface* (*SPI*). It is declared by a system that relies on an external component and is implemented by that component’s authors to make it pluggable into the system. *SPI*s are in use by [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]] and [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]] architectures, with *device drivers* being the best known example of pluggable components. There are also other kinds of *Proxies* which adapt a system to foreign interfaces: - An [[wiki/concepts/source/extension-metapatterns/proxy|*Anticorruption Layer* or *Open Host Service*]] translates between two software subsystems to loosen dependencies between them. - A [[wiki/concepts/source/extension-metapatterns/proxy|*Hardware Abstraction Layer* or *Operating System Abstraction Layer*]] stands between a system and an underlying hardware or OS, respectively, to make the system portable. ![A service wrapped with: a gateway with its API, a user interface, an Anticorruption Layer, a plugin and an Open Host Service with a Published Language.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Interface%20-%20Kinds.png) A *Proxy* that implements an interface may reside in a dedicated layer (and there may be multiple *Proxies* stacked together, for example, a *Gateway* behind a [[wiki/concepts/source/extension-metapatterns/proxy|*Firewall*]]) or be merged with a neighboring layer: for example, an [[wiki/concepts/source/extension-metapatterns/proxy|*API Gateway*]] fills the roles of both interface and [*application*](#application-use-cases-or-integration). An interface layer can contain multiple components (services, modules, or high-level classes) when the system below it supports several kinds of clients: a bank is likely to provide a web interface, a mobile application, and a SWIFT endpoint. See [*Backends for Frontends*]() for a detailed description. ![Diagrams of an API Gateway and Backends for Frontends.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Interface%20-%20Derived.png) ### Application (use cases or integration) The *application* determines *what* a system does. If it faces clients, the application runs client commands, called *use cases*, by executing chains of calls to its [*model*](#domain-business-rules-or-model) which knows *how* to do simple actions – the building blocks from which a use case is composed. For example, to transfer money between accounts, the application asks the model to: - Verify that the client is the owner of the account to be charged. - Calculate the bank’s fee for the transfer. - Subtract the amount to be transferred and the fee from the client’s account. - Add the fee to the bank’s account. - Tell the recipient’s bank to add the transferred amount to the target account. It is also responsible for dealing with any error that may occur in the process. For example, if the target account does not exist or is blocked, the application will need to both refund the client’s account and return a meaningful error message in the client’s preferred language. Another kind of software, called [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|*control systems*]], is written to supervise hardware or software entities. In that case there are no client requests or use cases – instead, the system reacts to signals from the components controlled. It is the application layer which is responsible for the system’s behavior: if a smoke sensor detects fire, the application tells an alarm to sound. This role is called *integration* – the system acts as a living organism, all its parts orderly moving in response to a stimulus perceived by its senses. When an application resides in a dedicated layer, it is called an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]]. Like the [*interface*](#interface-api-or-ui) layer, the application layer may also contain multiple components: the bank will likely have distinct applications for its clients and for its managers. The corresponding pattern is also called [*Backends for Frontends*]() (there is little distinction between [[wiki/concepts/source/extension-metapatterns/proxy|*Proxies*]] and *Orchestrators* in that topology). Some systems lack the application role – they are structured as [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipelines*]], so that whatever enters one end of the system passes through all its components and pops out, digested, at the other end. In that case it is the very structure of the system – the connections between its components – that drive its workflow. ![Backends for Frontends between a gateway and a monolithic service; a pipeline with use case logic hardwired into the graph of connections.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Application%20-%20Derived.png) ### Domain (business rules or model) The *domain* layer (aka *model*) models the real-world system which your software operates or emulates. It contains rules that describe *how* to do anything that your clients may want or *how* the hardware which you control needs to operate. Back to the banking example, the domain layer can: - Find if an account is valid and who owns it. - Calculate a fee. - Add money to or subtract it from an account. - Transfer money to another bank. And it has some tricks up its sleeves to assure that nothing it does about money is ever lost, even if the network is disconnected or the hardware it runs on fails. In most cases the domain layer is the largest one and it is the one which makes your software valuable for business because it actually *does* whatever your software is about. It is also [the only layer in which OOP classes may correspond to real-world entities](http://tedfelix.com/software/jacobson1992.html). As the largest layer, the domain is often the first among them to be subdivided: - The most common way is to partition it into *subdomains* – loosely coupled subsets of your system’s functionality – yielding [[wiki/concepts/source/basic-metapatterns/services|*Services*]] (when other layers are fragmented along the same lines or replicated among the subdomain components) or a [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]]. - Rare cases allow for [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*hierarchical* decomposition]] where most components blend [*application*](#application-use-cases-or-integration) and domain roles. - Last but not least, we can use separate models for making changes (executing *commands*) and for analytics (running *queries*), giving rise to [[wiki/concepts/source/fragmented-metapatterns/layered-services|*Command Query Responsibility Segregation* (*CQRS*)]]. This makes sense because a command usually involves many fields of a single record (database row) while a query runs over select rows of all the records – they vary in how they access and treat the data, which is why it is common to have a record wrapped into an OOP class in the command model, while the query model, if it is not omitted completely, provides for direct access to the database. ![Diagrams of Services, Sandwich, Hierarchy, and Command-Query Responsibility Segregation.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Domain%20-%20Derived.png) ### Generic code (libraries and utilities) *Generic code* is something not directly related to your business but still used by your logic: a graph traversal algorithm, an e-mail server, or even a computer vision framework to identify duplicate user avatars. In most cases generic code stays together with the [*domain*-level code](#domain-business-rules-or-model) which calls it. However, when the domain layer gets subdivided into services, there emerge [[wiki/concepts/source/analytics/sharing-functionality-or-data-among-services|multiple options]]: - If the generic code is not shared, it moves into the service that uses it. See [[wiki/concepts/source/basic-metapatterns/services|*Services*]]. - If it needs to be shared, it can be: - Extracted into a dedicated service, as in [*Service-Oriented Architecture*](). - Replicated as a [[wiki/concepts/source/extension-metapatterns/proxy|*Sidecar*]] attached to every instance of each service that uses it. - Copied into the codebases of the services to allow each team to change it independently from other teams – see *Separate Ways* in \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]. ![Diagrams of Services, Service-Oriented Architecture, and Microservices with sidecars, with components that carry generic code highlighted.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Generic%20Code%20-%20Derived.png) ### Communication (middleware) If your system is made of multiple components, they need a way to communicate, which may be as simple as in-process method calls or as complex as a [distributed consensus protocol](https://en.wikipedia.org/wiki/Paxos_(computer_science)). The *communication infrastructure*, when used consistently throughout a system, makes a distinct virtual (conjoining separately deployed nodes) system layer, called [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] and often implemented with a [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]]. ![Multiple instances of a communication library represented as a virtual middleware layer.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Communication%20-%20Derived.png) ### Data (persistence) Most systems but the simplest [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipelines*]] are stateful – they remember something about their users or their environment: - A backend would usually *persist* useful facts to a *database* which makes a dedicated *persistence layer*. - A [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|real-time *control system*]] does not have the leisure to access anything remote, therefore its state is embedded in its [*domain* layer](#domain-business-rules-or-model)’s memory. - Complex distributed frameworks that implement [[wiki/concepts/source/basic-metapatterns/services|*Actors*]] or [[wiki/concepts/source/implementation-metapatterns/mesh|*Space-Based Architecture*]] both keep each service’s data inside the service’s memory for fast access and back all the changes to a persistent data store to support failure recovery. ![Diagrams of a three-tier system, hierarchical control system, and Space-Based Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Data%20-%20Derived.png) If the persistence layer becomes a system’s performance bottleneck, as it often does, one of [[wiki/concepts/source/extension-metapatterns/shared-repository|the cures]] is using several specialized data stores, leading to [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]]. ![A load-balanced service over a database evolves into a monolith with two specialized databases or into a load-balanced stateless service over database replicas with a single leader.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Data%20-%20Evolutions.png) ### Operating system and hardware All software always runs on *hardware* and usually relies on an *operating system* (*OS*) for file and network access and memory management. Even though many modern backends don’t care about such low-level details and omit them on their diagrams, embedded or systems software communicates with its OS or hardware directly, making the corresponding components indispensable parts of its topology. Integrating multiple pieces of hardware into an intelligently behaving system is usually what [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|*control software*]] is written for. Such systems [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|often follow]] the [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Pedestal*]], [[wiki/concepts/source/basic-metapatterns/services|*Actors*]], or [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchy*]] architectures. ![Diagrams of control systems with the following architectures: monolithic, actors, Pedestal, hierarchical.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/4Kinds/Control%20-%20variants.png) ## Examples The notion of layering seems to be so natural to our minds that most known architectures are layered to an extent. Not surprisingly, there are several approaches to assigning functionality to and naming the layers: - [*ECB*](#entity-control-boundary-ecb-entity-boundary-control-ebc-boundary-control-entity-bce) distinguishes a client-facing layer, use cases and domain logic. - [*DDD*](#domain-driven-design-ddd-layers) adds the infrastructure layer. - [*Tiers*](#three-tier-architecture) are distributed *Layers* that usually include frontend, backend, and database. - [Layering of an embedded system](#embedded-systems) often matches its supply chain. ### Entity-Control-Boundary (ECB), Entity-Boundary-Control (EBC), Boundary-Control-Entity (BCE) ![The boundary, control and entity layers.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/ECB.png) [*Entity-Control-Boundary*](https://en.wikipedia.org/wiki/Entity%E2%80%93control%E2%80%93boundary) (*ECB*) or other combinations of these words (*EBC* and *BCE*) designate a system composed of the following layers: - *Boundary* – the layer which [interacts with the system’s *clients* or *users*](#interface-api-or-ui). See [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]]. - *Control* – the layer which [contains *use cases*](#application-use-cases-or-integration) – sequences of actions on the system’s internals that should be made to process a client’s request or respond to a user’s action. See [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]]. - *Entity* – the bulk of the system’s business logic and data. A closer look at an *ECB* system may reveal a finer-grained structure that resembles [*Backends for Frontends*]() or [*Service-Oriented Architecture*]() as each layer is composed of modules or objects: ![The boundary, control and entity layers, each subdivided into several services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/ECB%20as%20SOA.png) ### Domain-Driven Design (DDD) Layers ![The four layers of Domain-Driven Design: presentation, application, domain, and infrastructure.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/DDD.png) *Domain-Driven Design* (*DDD*), as given in \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\], is a methodology for enterprise-scale backend development which extends the more generic [*Entity-Control-Boundary*](#entity-control-boundary-ecb-entity-boundary-control-ebc-boundary-control-entity-bce) with a new *Infrastructure* layer responsible for [*communication*](#communication-middleware) and [*persistence*](#data-persistence) roles which don’t exist in most desktop applications. Its layers are called: - *Presentation* ([[wiki/concepts/source/extension-metapatterns/proxy|*User Interface*]]) – the user-facing component (frontend, UI). It should be highly responsive to the user's input. See [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Separated Presentation*]]. - *Application* ([*Integration*](#application-use-cases-or-integration), *Service*) – the high-level scenarios which build upon the API of the *domain* layer. It should be easy to change and to deploy. See [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]]. - *Domain* ([*Model*](#domain-business-rules-or-model), *Business Rules*) – the bulk of the mid- and low-level business logic. It should usually be well-tested and performant. - *Infrastructure* (*Utility*, *Data Access*) – the utility components devoid of business logic. Their stability and performance is business-critical but updates to their code are rare. For example, an online banking system comprises: - the presentation layer which is its frontend; - the application layer which implements sequences of steps for payment, card to card transfer, and viewing a client’s history of transactions; - the domain layer with its classes for various kinds of cards and accounts; - the infrastructure layer with a database and libraries for encryption and interbank communication. However, in practice you are much more likely to encounter the derived [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*DDD-style Hexagonal Architecture*]] than the original *DDD Layers*. ### Three-Tier Architecture ![Four instances of the presentation layer accessing two instances of the logic layer accessing a single database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Three-Tier.png) Here the focus lies with the distribution of the components over heterogeneous hardware (*Tiers*): - *Presentation* ([[wiki/concepts/source/extension-metapatterns/proxy|*Frontend*]]) tier – a user-facing application which runs on a user’s hardware. It is very scalable and responsive, but insecure. - *Logic* (*Backend*) tier – the business logic which is deployed on the service provider’s side. Its scalability is limited mostly by the funding committed, security is good, but latency is high. - *Data* (*Database*) tier – a service provider’s database which runs on a dedicated server. It is not scalable yet is very secure. In this case the division into layers resolves the conflict between scalability, latency, security, and cost as discussed in detail in the [[wiki/concepts/source/foundations-of-software-architecture/forces-asynchronicity-and-distribution|chapter on distribution]]. > *Tiers* don’t map directly to *Layers*. For example, a protocol support library which is used for communication between services belongs to the lowest (infrastructure) layer but to the middle (backend) tier. The discrepancy is rooted in different natures ([views of the *4\+1 model*](https://en.wikipedia.org/wiki/4%2B1_architectural_view_model)) of the patterns in question: *Layers* show the logical composition (codebase) of the system while *Tiers* deal with its physical structure (deployment). ### Embedded systems ![An embedded system with the following pairs of layers: user interface and human-machine interface, software development kit and hardware abstraction layer, firmware and hardware.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Embedded.png) Bare metal and micro-OS systems which run on low-end chips use a different terminology, which is not unified across domains. A generic example involves: - *Presentation* – a UI engine used by the *HMI*. It may be a third-party library or come as a part of the *SDK*. - [[wiki/concepts/source/extension-metapatterns/proxy|*Human-Machine Interface*]] (*HMI* aka *MMI*) – the UI and high-level business logic for user scenarios, written by a [value-added reseller](https://en.wikipedia.org/wiki/Value-added_reseller). - *Software Development Kit* (*SDK*) – the mid-level business logic and device drivers, written by the [original equipment manufacturer](https://en.wikipedia.org/wiki/Original_equipment_manufacturer). - [[wiki/concepts/source/extension-metapatterns/proxy|*Hardware Abstraction Layer*]] (*HAL*) – the low-level code that hides hardware registers to enable code reuse between hardware platforms. - *Firmware of Hardware Components* – usually closed-source binary pre-programmed into chips by chipmakers. - *Hardware* itself. It is of note that in this approach the layers form strongly coupled pairs. Each pair is implemented by a separate party of the supply chain, which is an extra force that shapes the system into layers. An example of such a system can be found in an old mobile phone or a digital camera. ## Evolutions Layers are not without drawbacks which may force a layered system to evolve. A summary of such evolutions is given below while more details can be found in [[wiki/concepts/source/appendices/evolutions-of-architectures|Appendix E]]. ### [[wiki/concepts/source/appendices/evolutions-of-layers-that-make-more-layers|Evolutions that make more layers]] Not all the layered architectures are equally layered. A [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] with a [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]] or database has already stepped into the realm of *Layers* but is far from reaping all its benefits. Such a system may continue its course in a few ways that were previously [[wiki/concepts/source/basic-metapatterns/monolith|discussed for *Monolith*]]: - Employing a *database* (if you don’t have one yet) lets you rely on a thoroughly optimized state-of-the-art subsystem for data processing and storage. - [[wiki/concepts/source/extension-metapatterns/proxy|*Proxies*]] are similarly reusable generic modules to be added at will. - Implementing an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] on top of your system may improve programming experience and runtime performance for your clients. ![A diagram of calls in a layered system. A single request from a client is translated by an Orchestrator into multiple calls to lower layers.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Layers/Layers%20to%20Layers.png) It is also common to: - Have the business logic divided into two layers. ![A backend is subdivided into application and domain layers.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Layers/Layers%20Split%20in%20Two.png) ### [[wiki/concepts/source/appendices/evolutions-of-layers-that-help-large-projects|Evolutions that help large projects]] The main drawback (and benefit as well) of *Layers* is that much or all of the business logic is kept together in one or two components. That allows for easy debugging and fast development in the initial stages of the project but slows down and complicates work as the project grows in size \[[wiki/concepts/source/appendices/books-referenced|[MP]]\]. The only way for a growing project to survive and continue evolving at a reasonable speed is to subdivide its business logic into several smaller, [[wiki/concepts/source/foundations-of-software-architecture/modules-and-complexity|thus less complex]], components that match subdomains (*bounded contexts* \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]). There are several options for such a change, with their applicability depending on the domain: - In a [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]] the middle layer with the main business logic is divided into [[wiki/concepts/source/basic-metapatterns/services|*Services*]], leaving the upper [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] and lower [[wiki/concepts/source/extension-metapatterns/shared-repository|*database*]] layers intact for future evolutions. ![The domain layer is split into subdomain components, making a Sandwich.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Layers/Layers%20Split%20Domain%20to%20Services.png) - Sometimes the business logic can be represented as a set of directed graphs which is known as [[wiki/concepts/source/basic-metapatterns/pipeline|*Event-Driven Architecture*]]. ![A backend is subdivided into a pipeline.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Layers/Layers%20Split%20to%20Event-Driven%20Architecture.png) - If you are lucky, your domain makes a [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Top-Down Hierarchy*]]. ![The lower layers of a system are subdivided, resulting in a hierarchy.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Layers/Layers%20to%20Hierarchy.png) ### [[wiki/concepts/source/appendices/evolutions-of-layers-to-improve-performance|Evolutions that improve performance]] There are several ways to improve the performance of a layered system. One we have [[wiki/concepts/source/basic-metapatterns/shards|already discussed for *Shards*]]: - [[wiki/concepts/source/implementation-metapatterns/mesh|*Space-Based Architecture*]] co-locates the data store and business logic and scales both dynamically. ![The database is migrated to a Data Grid, resulting in a scalable Space-Based Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Layers/Layers%20to%20Space-Based%20Architecture.png) Others are new: - Merging several layers improves latency by eliminating the communication overhead. ![The application and domain layers are merged.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Layers/Layers%20Merge.png) - [[wiki/concepts/source/basic-metapatterns/shards|Scaling]] some of the layers may improve throughput. ![The application and domain layers are independently sharded.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Layers/Layers_%20Shard.png) - [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]] is the name for using multiple specialized data stores. ![The database layer is subdivided into specialized databases, resulting in Polyglot Persistence.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Layers/Layers%20to%20Polyglot%20Persistence.png) ### [[wiki/concepts/source/appendices/evolutions-of-layers-to-gain-flexibility|Evolutions to gain flexibility]] The last group of evolutions to consider is about making the system more adaptable. We have already discussed the following [[wiki/concepts/source/basic-metapatterns/monolith|evolutions for *Monolith*]]: - The behavior of the system may be modified with [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]]. - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] allows for abstracting the business logic from the technologies used in the project. - [[wiki/concepts/source/implementation-metapatterns/microkernel|*Scripts*]] allow for customization of the system’s logic on a per client basis. ![Diagrams of Layers with plugins, Layers with scripts, and Hexagonal Architecture with a layered core.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Layers%20-%20Further%202.png) There is also one new evolution which modifies the upper (*orchestration*) layer: - The [[wiki/concepts/source/extension-metapatterns/orchestrator|orchestration layer]] may be split into [*Backends for Frontends*]() to match the individual needs of several kinds of clients. ![The application layer is split into Backends for Frontends.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Layers/Layers%20to%20Backends%20for%20Frontends.png) ## Summary *Layered architecture* separates the high-level logic from the low-level details. It is superior for medium-sized projects as it supports rapid development by two or three teams, is flexible enough to resolve conflicting forces, and provides many options for further evolution, which will come in handy when the project grows in size and complexity. --- title: "Monolith" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Basic metapatterns/Monolith.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Basic%20metapatterns/Monolith source_license_note: "See namespace README; preserve attribution and source links." --- # Monolith > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Basic metapatterns/Monolith.md`. Let’s take a look at the simplest possible [[wiki/concepts/source/introduction/metapatterns|metapattern]] – *Monolith* – and see what it can teach us. ![A diagram for Monolith, in abstractness-subdomain-sharding coordinates.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Main/Monolith.png) *Keep it simple, stupid!* If you don’t need a modular design, why bother? Known as: Monolith, Monolithic Architecture. Structure: A monoblock with no strong internal modularity. Type: System topology, the root of the hierarchy of metapatterns. | *Benefits* | *Drawbacks* | | --- | --- | | Rapid start of development | Quickly deteriorates with project growth | | Easy debugging | Hard to develop with multiple teams | | Best latency | Does not scale | | Low resource consumption | Lacks support for conflicting forces | | The system’s state is self-consistent | Any failure crashes the entire system | References: [Big Ball of Mud](http://www.laputan.org/mud/) for a philosophical discussion, [my article](https://itnext.io/introduction-to-software-architecture-with-actors-part-2-on-handling-messages-940c62cb06dc) and \[[wiki/concepts/source/appendices/books-referenced|[POSA2]]\] for subtypes of *Monolith*, Martin Fowler’s discussion on [starting development with *Monolith*](https://martinfowler.com/bliki/MonolithFirst.html), \[[wiki/concepts/source/appendices/books-referenced|[MP]]\] for the [definition of *monolithic hell*](https://livebook.manning.com/book/microservices-patterns/chapter-1/25) and a post describing the [first-hand experience of it](https://news.ycombinator.com/item?id=18442941). We distance ourselves from the [[wiki/concepts/source/analytics/ambiguous-patterns|systems architecture’s definition]] of *Monolith* as a single unit of deployment because our main focus lies with the internal structure of systems. Instead, we will use the old definition of a *monolithic* application as a cohesive lump of code which does not contain any discernible components \[[wiki/concepts/source/appendices/books-referenced|[GoF]], [[wiki/concepts/source/appendices/books-referenced|POSA1]]\]. A *Monolith* is non-modular (not divided by interfaces) along all the [[wiki/concepts/source/introduction/metapatterns|structural dimensions]]. Its thorough cohesiveness is both its blessing (single-step debugging, system-wide optimizations, and self-consistent data) and its curse (messy code, no scalability of development and deployment, zero flexibility). ### Performance On one hand, monolithic applications provide perfect opportunities for performance optimizations as every piece of code is readily accessible from any other. On the other hand, if the application is stateful, access to the state may [limit the performance benefit](https://stackoverflow.com/questions/16571381/degrading-performance-when-increasing-number-of-cores) of using multiple CPU cores. Furthermore, large *Monoliths* may become too messy, too complicated, and too fragile for programmers to identify and implement any non-local optimizations that could drastically improve performance. > There are many kinds of bottlenecks which limit an application’s performance. As soon as you change your code to use multiple CPU cores you may find that the program’s throughput [is constrained](https://en.wikipedia.org/wiki/Resource_contention) by the speed of your hard drive or network interface. And when you upgrade those two, you may well hit something more subtle, like OS interrupts or [CPU cache coherence](https://www.youtube.com/watch?v=wGSSUSeaLgA). Overall, tiny *Monoliths* provide the best latency and throughput per CPU core. Larger performance-critical projects may need to partition the code into [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] or [[wiki/concepts/source/basic-metapatterns/services|*Services*]] so that any manually optimized part remains small enough to be manageable. Higher throughput is attainable through distributing the software over multiple computers: [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]] employ several copies of the whole system while a [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]] may run each step of data processing on a separate server. ### Dependencies Even though a *Monolith* is a single module, meaning that there are no dependencies among its parts (in fact, everything depends on everything), it still may depend on some external components or services which it uses. Those dependencies tend to cause [*vendor lock-in*](https://en.wikipedia.org/wiki/Vendor_lock-in) or make the software OS- or hardware-dependent. [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] (including [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*MVP*]] and [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*MVC*]]) decouples a monolithic system from its dependencies by isolating the latter behind [[wiki/concepts/source/extension-metapatterns/proxy|*Adapters*]]. ### Applicability *Monolith* is good for cases which are harmed by the introduction of modularity: - *Tiny projects.* The project is relatively small (below 10 000 lines) and the requirements will never change (like when you need to implement an application for running a specific mathematical calculation or a library supporting a well-established communication protocol). - *Ultra optimization.* You already have a working and thoroughly optimized system, but you still need that extra 5% performance improvement achievable through merging all the components together. - *Low latency.* If you need ultra low latency for the entire application, any asynchronous communication between its modules is not a viable option. Example: [high-frequency trading](https://en.wikipedia.org/wiki/High-frequency_trading). - *Prototyping.* You are writing a prototype in a domain which you are not familiar with, and gathering requirements in the process. Chances for a correct initial identification of weakly coupled subdomains (to become [[wiki/concepts/source/basic-metapatterns/services|modules or services]]) are [quite low](https://martinfowler.com/bliki/MonolithFirst.html) and it is worse to have wrong module boundaries than to use no modules at all. [[wiki/concepts/source/analytics/architecture-and-product-life-cycle|At the later stages]] of the project, when you will know the domain much better and your users will have approved the initial implementation, you will be able to split the system into components in a much better way, if and when that will be needed. Nevertheless, you may already know enough to apply [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] or [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] which keep the business logic monolithic while isolating it from the periphery and third-party libraries. - *Quick and dirty.* You are out of time and money and need to show your customers something right now. There is no time to think, no money to perfect the code, and no day after tomorrow. *Monolith* should be avoided when we need modules: - *Incompatible forces.* There are [[wiki/concepts/source/foundations-of-software-architecture/forces-asynchronicity-and-distribution|conflicting *forces*]] for different subsets of functionality. They require splitting the system into (usually asynchronous) components each of which is specifically designed to satisfy its own subset of forces. Your main tool is the careful selection of technologies and architectures on a per component basis which may allow the project to satisfy all the non-functional requirements even if the task looks impossible during the initial analysis. - *Long-running projects.* The project is going to evolve over time and you believe you can predict the general direction of the future changes. Modularity brings flexibility which you will need for sure. - *Larger codebases.* The project grows above average size (100 000 lines of code). If you don’t split it into smaller components it will descend into a [monolithic hell](https://livebook.manning.com/book/microservices-patterns/chapter-1/25) with development and debugging slowing down year after year till it reaches [terminal stage](https://news.ycombinator.com/item?id=18442941). Slow development is a waste of money, both in salary and in time to market. - *Multiple teams.* You have multiple teams to work on the project. Inter-team communication is hard and error-prone whereas merging several teams together is known to greatly reduce the programmers’ productivity (which peaks with teams of 5 or less members). Explicit interfaces between components will formalize interdependencies between the teams, lowering communication overhead. - *Fault tolerance.* Your domain requires fault tolerance which is next to impossible for large monolithic applications. - *Resource-limited.* Your project is too resource-hungry for commodity hardware. Even if you buy the best server for its needs right now, it is going to crave more tomorrow (or on the next Black Friday). - *Distributed setup.* Your project needs to run on multiple hardware devices. One of common examples is a [[wiki/concepts/source/foundations-of-software-architecture/forces-asynchronicity-and-distribution|web service]] containing frontend and backend. ### Relations ![Intermediary architectures between Monolith and distributed Shards, Layers, and Services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Relations/Monolith.png) *Monolith*: - Can be extended with a [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]], or turned into a [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] or [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]]. - Yields [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]], [[wiki/concepts/source/basic-metapatterns/services|*Services*]], or [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]] if divided along the [[wiki/concepts/source/introduction/metapatterns|*abstractness*, *subdomain*, or *sharding*]] dimensions, respectively. All the known architectures are combinations of those three metapatterns. - Is the bird’s-eye view of any architecture. ## Variants by the internal structure *Monoliths* are the atoms to create more complex architectures from, the opaque building blocks, each of which satisfies a consistent set of [[wiki/concepts/source/foundations-of-software-architecture/forces-asynchronicity-and-distribution|forces]]. Any individual component of a more complex architecture either is monolithic or encapsulates another architectural pattern, decomposable into *Monoliths*, and any architecture looks monolithic to its clients. ![A Sandwich Architecture looks like a monolith when the details of its internal structure are omitted.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/MonolithAsUnzoomed.png) There is a misunderstanding because *software architecture* inspects the internals of *applications* at the level of *modules* or even classes while *systems architecture* deals with *distributed systems* and operates *deployment units* which tend to incorporate multiple modules or even applications. Each branch of the architecture [[wiki/concepts/source/analytics/ambiguous-patterns|calls]] its atomic unit a *Monolith*, leading to the term sticking both to a *module that cannot be subdivided*, as in \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] and \[[wiki/concepts/source/extension-metapatterns/shared-repository|[POSA1]]\], and to a *(sub)system which must be deployed as a whole*, as per present-day literature. As we aspire to build a unified classification for both distributed and local systems, we must treat both kinds of components in the same way, whether they are [[wiki/concepts/source/basic-metapatterns/services|distributed services]], [[wiki/concepts/source/basic-metapatterns/services|co-located *Actors*]], or [[wiki/concepts/source/basic-metapatterns/services|in-process modules]]. Thus, for the scope of the current book, we will follow the definition of *Monolith* from \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\]: “Tight coupling leads to *monolithic* systems, where you can't change or remove a class without understanding and changing many other classes”. Still, we need to account for a couple of misnomers from systems architecture. ### True Monolith, Big Ball of Mud ![A square that represents a non-modular monolith.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/True%20Monolith.png) A true *Monolith* features [no clear internal structure](http://laputan.org/mud/). If it has any components, they are so tightly coupled that the entire thing behaves as a single cohesive module. This is the subject of the current chapter. ### (inexact) Lambda Monolith, Monolambda, [[wiki/concepts/source/basic-metapatterns/shards|Lambdalith]] ![Instances of a stateless component between a load balancer and a database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Lambdalith.png) A [*Monolambda*](https://jesseduffield.com/Notes-On-Lambda/) or [*Lambdalith*](https://theburningmonk.com/2025/03/the-pros-and-cons-of-lambdalith/) is a dynamic [[wiki/concepts/source/basic-metapatterns/shards|*Pool* of stateless instances]] of a system. Though each instance may contain [[wiki/concepts/source/basic-metapatterns/layers|*layers*]] or [[wiki/concepts/source/basic-metapatterns/services|*subdomain modules*]], the whole is often called a *Monolith* [[wiki/concepts/source/analytics/ambiguous-patterns|because it is deployed as a single unit]]. ### (misapplied) [[wiki/concepts/source/basic-metapatterns/layers|Layered Monolith]] ![Application, domain, and infrastructure layers.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Layered%20Monolith.png) When they say [[wiki/concepts/source/basic-metapatterns/layers|*Layered Monolith*]] \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\], that refers to a non-distributed application with a layered structure, which is a proper [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] architecture and will be discussed in the corresponding chapter. It is called a *Monolith* for the [[wiki/concepts/source/analytics/ambiguous-patterns|sole reason that it is not distributed]]. Nevertheless, *Layers* resemble *Monolith* in many aspects, including easy debugging and the risk of outgrowing the comfort zone of developers. ### (misapplied) [[wiki/concepts/source/basic-metapatterns/services|Modular Monolith]] (Modulith) ![A diagram of subdomain services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Modular%20Monolith.png) A [[wiki/concepts/source/basic-metapatterns/services|*Modular Monolith*]] (*Modulith*) \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] is a single-process application subdivided into modules that correspond to subdomains. If the modules communicate via in-process messaging, the architecture is nearly identical to coarse-grained [[wiki/concepts/source/basic-metapatterns/services|*Actors*]], thus it is a *Monolith* only in name. *Modulith* [is a kind of](https://en.wikipedia.org/wiki/Duck_typing) [[wiki/concepts/source/basic-metapatterns/services|*Services*]] – it supports development by multiple teams and its asynchronous variant is hard to debug. The relation to *Monolith* is mostly limited to the inability to scale individual parts of the system. ### (misapplied) [Distributed Monolith]() ![A distributed monolith as three layers of services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Distributed%20Monolith.png) A [*Distributed Monolith*]() \[[wiki/concepts/source/appendices/books-referenced|[MP]]\] is a highly distributed system (usually [*Service-Oriented Architecture*]() or [[wiki/concepts/source/basic-metapatterns/services|*Services*]]) where all the components still need to be deployed together because of their interdependencies. It is said to have the drawbacks of both *Monolith* (low fault tolerance and coupled release cycles) and *Services* (poor debuggability, high latency, and operational complexity). ### (inexact) [[wiki/concepts/source/implementation-metapatterns/plugins|Plugins]] and [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Hexagonal Architecture]] ![Hexagonal Architecture with adapters between its core and each component the core interacts with.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Hexagonal%20Monolith.png) [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]] and [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] extend a (sub)system with external components. These architectures can be applied to a *Monolith* without drastically changing its properties – it still remains relatively easy to write and debug but hard to support when it has grown large. Therefore, we will not currently discuss these modifications, mainly because each of them has a dedicated chapter. ## Examples Let’s take a look inside a *Monolith*. Any software module reacts to incoming events or data and produces outgoing events or data. But there are a few basic ways to implement that cycle: ![Control flow diagrams for Reactor, Proactor, and Half-Sync/Half-Async.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Subtypes%20of%20Monolith.png) - *Reactor* runs each request in a separate thread: - A [single-threaded version](#single-threaded-reactor-one-thread-one-task) is used to serialize access to a hardware device. - A [multi-threaded *Reactor*](#multi-threaded-reactor-a-thread-per-task) is the simplest backend implementation. - [*Proactor*](#proactor-one-thread-many-tasks) relies on short event handlers to run multiple requests in a single thread. - [*Half-Sync/Half-Async*](#inexact-half-synchalf-async-coroutines-or-fibers) implements coroutines by changing call stacks of a thread. - [*(Re)Actor-with-Extractors*](#inexact-reactor-with-extractors-phased-processing) passes the whole system through alternating planning and execution phases to run lock-free. ### Single-threaded Reactor (one thread, one task) ![A single thread that blocks on calls to an operating system executes a request and then another request which has to wait in a queue.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Reactor%20-%20Single%20Thread.png) In a [*Reactor*](https://www.dre.vanderbilt.edu/~schmidt/PDF/reactor-siemens.pdf) \[[wiki/concepts/source/appendices/books-referenced|[POSA2]]\] a single thread waits for an incoming event, request, or data packet, processes it with blocking calls to the underlying OS, hardware, and external dependencies, and returns the result, rinse and repeat. That makes sense when the module wraps a hardware component which cannot do several actions at once, for example, a communication bus or a HDD firmware capable of a single read or write at any given moment. ### Multi-threaded Reactor (a thread per task) ![Two threads, each runs a single request and blocks on accessing an operating system.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Reactor%20-%20Multiple%20Threads.png) A [*Reactor*](https://www.dre.vanderbilt.edu/~schmidt/PDF/reactor-siemens.pdf) \[[wiki/concepts/source/appendices/books-referenced|[POSA2]]\] may employ multiple threads by having a [[wiki/concepts/source/basic-metapatterns/shards|*pool*]] of them waiting for a request or data to arrive. The incoming event activates one of the waiting threads, which thereby becomes dedicated to processing it, makes several blocking calls and, finally, sends back a response. When the request processing is complete, the thread returns to the pool of idle threads to wait for the next event to process. This is the default [simple & stupid](https://en.wikipedia.org/wiki/KISS_principle) implementation of backend services. Its pitfalls include contention for shared resources, deadlocks, and high memory consumption by OS-level threads. ### Proactor (one thread, many tasks) ![A single thread handles messages that belong to several use cases in an interleaved manner.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Proactor.png) In [*Proactor*](https://hillside.net/plop/plop97/Proceedings/pyarali.proactor.pdf) \[[wiki/concepts/source/appendices/books-referenced|[POSA2]]\] a single thread processes all of the incoming events, both from the module’s clients and from the hardware or dependencies which it manages. When an event is received, the thread goes through a short piece of corresponding business logic (*event handler*) which usually does one or more non-blocking actions, such as sending messages to other components, writing to registers of the managed hardware, or initiating an async I/O. As soon as the event handler returns, the thread becomes ready to process further events. As the thread never blocks, it is resource-efficient (does not hold anything for a noticeable amount of time) and can serve many interleaved tasks. This approach is good for real-time systems where thread synchronization is largely forbidden because of the associated delays and for reactive [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|control]] applications which mostly adapt to the environment instead of running pre-programmed scenarios. The drawback is very poor structure of the code and nightmarish debuggability as any complex behavior is broken into a swarm of separate event handlers. ### (inexact) Half-Sync/Half-Async (coroutines or fibers) ![A system subdivided into two layers: the upper one with a coroutine per request and the lower one with a generic event handling thread.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Half-Sync%20Half-Async.png) [*Half-Sync/Half-Async*](https://www.dre.vanderbilt.edu/~schmidt/PDF/PLoP-95.pdf) \[[wiki/concepts/source/appendices/books-referenced|[POSA2]]\] originally described the interaction between user space and kernel threads in operating systems which is not much different from what happens under the hood in coroutines or fibers. A single thread (or a thread pool with one thread per CPU core) handles all the incoming events and switches its call stack in the process. Every incoming request is allocated a call stack which stores the processing state (local variables and methods called) of the request. When it needs to access an external component, the [runtime system](https://en.wikipedia.org/wiki/Runtime_system) saves the request’s stack, makes a non-blocking call, and the executing thread returns to its original stack to wait for any new event to handle while the request processing stack remains frozen until the action it has initiated completes asynchronously, raising an event. Then the runtime, upon receiving the event, switches the execution thread back to the stored request’s stack and continues processing the request until it completes and its stack is deleted. This makes programming and debugging feel as easy as they are with [*Reactor*](#single-threaded-reactor-one-thread-one-task) (imperative style) while partially retaining the low resource consumption and high performance of [*Proactor*](#proactor-one-thread-many-tasks) (reactive paradigm). Coroutines and fibers are used in highly efficient [game engines](https://www.gdcvault.com/play/1022186/Parallelizing-the-Naughty-Dog-Engine) and [databases](https://docs.seastar.io/master/tutorial.html#coroutines). Though *Half-Sync/Half-Async* has two layers (is not truly monolithic), I believe it belongs next to *Reactor* and *Proactor* which make up its upper and lower halves, respectively. ### The state of the art These patterns are not widely recognized and programmers tend to mix them together, for better or for worse. One is likely to encounter a heavily multithreaded [big ball of mud](https://www.laputan.org/mud/) where some threads serve user requests while others are dedicated to periodic service routines. Moreover, people [[wiki/concepts/source/analytics/ambiguous-patterns|often call]] any event-driven service a *Reactor*, causing confusion among those who distinguish between the three patterns. ### (inexact) (Re)Actor-with-Extractors (phased processing) ![In the extraction phase components call each other and add actions to their queues. In the reaction phase they execute the actions from their queues but don't interact. The phases alternate.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Reactor%20with%20Extractors.png) As a bonus, let’s review an [unconventional execution model](http://ithare.com/multi-coring-and-non-blocking-instead-of-multi-threading-with-a-script/3/) that fits game development and other kinds of simulations with many interacting objects. We have a long-running system where each simulated object with a complex behavior depends on the objects around it. Common wisdom proposes two ways to implement it: - [*Actors*](https://doc.akka.io/libraries/akka-core/current/typed/guide/actors-intro.html) (asynchronous messaging, reactive programming) – each [[wiki/concepts/source/basic-metapatterns/services|*actor*]] (simulated object) runs single-threaded and wakes up only to process incoming messages. While processing a message, an actor may change its state and/or send messages to other actors. The entire actor’s data is private and there are no synchronous calls between the actors. The good thing is that actors are very efficient in highly parallel tasks as there are no locks in their code. The bad thing is that actors have no way to synchronize their states: you can only request another actor to tell you about its state, and its response may become outdated even before you receive it. Also, any complex logic that involves multiple actors is fragmented into many event handlers. - The opposite approach is to have the simulated objects access each other synchronously. This allows for complex logic that depends on states of several objects yet gets in trouble with changing the objects’ states from multiple threads: you need to protect them with those inefficient locks and you get those dreadful deadlocks as the outcome. Here we see two bad options to choose from. However, it is the simulated nature of the system that saves the day: we can *stop the world to get off*. The objects’ querying each other and their changing their states neither needs to happen at the same time nor obey the same rules! The simulation runs in steps. Each step consists of two phases: - *Query phase* (*extraction*) is when the object states are immutable, thus the objects can communicate synchronously with no need for locks. In this phase each object collects information from its surroundings (other objects), plans its actions and posts them as commands to its own message queue. I suppose that objects may also post events to each other’s queues in this phase. - *Command phase* (*reaction*) is when each object executes its planned (queued) actions that change its state, but it cannot access other objects. Each phase lasts until every object in the system completes its tasks scheduled for that particular phase. The phase toggle is supervised by a [[wiki/concepts/source/extension-metapatterns/proxy|*Scheduler*]] which runs the objects on all the available CPU cores. The entire process resembles the [game of Mafia](https://en.wikipedia.org/wiki/Mafia_(party_game)) with public daily conversations and covert nightly actions. *(Re)Actor-with-Extractors* is the perfect example of earning the benefits of two architectural styles without paying their penalties. It utilizes both the lockless parallelism of *Actors*-style [*shared-nothing*](https://en.wikipedia.org/wiki/Shared-nothing_architecture) and the simplicity of synchronous access in [*shared-memory*](https://en.wikipedia.org/wiki/Shared-memory_architecture) by alternating between those two modes through applying the [*CQRS principle*](https://en.wikipedia.org/wiki/Command_Query_Responsibility_Segregation) to the time dimension. ## Evolutions Every architecture has drawbacks and tends to evolve in a variety of ways to address them as soon as they start causing trouble. Below is a brief summary of common evolutions of *Monolith* with more information available in [[wiki/concepts/source/appendices/evolutions-of-architectures|Appendix E]]. ### [[wiki/concepts/source/appendices/evolutions-of-a-monolith-that-lead-to-shards|Evolutions to Shards]] One of the main drawbacks of the *Monolithic Architecture* is its lack of scalability – a single running instance of your system may not be enough to serve all the clients no matter how many resources you add in. If that is the case, you should consider [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]] – *multiple instances* of a *Monolith*. There are following options: - Self-managed [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]] – each instance owns a part of the system’s data and may communicate with all the other instances (forming a [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]]). ![Several instances of a monolith are run as intercommunicating shards, each of which holds a subset of the system's data.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Mesh%20of%20Shards.png) - *Shards* with a [[wiki/concepts/source/extension-metapatterns/proxy|*Sharding Proxy*]] – each instance owns a part of the system’s data and relies on an external component to choose a shard for a client. ![Multiple instances of a monolith, each a subset of the system's data, are run behind a sharding proxy.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Isolated%20Shards%20with%20Load%20Balancer.png) - A [[wiki/concepts/source/basic-metapatterns/shards|*Pool*]] of stateless instances with a [[wiki/concepts/source/extension-metapatterns/proxy|*Load Balancer*]] and a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]] – any instance can process any request, but the shared database or file system limits the throughput. ![A monolith is transformed into stateless instances which run behind a load balancer and access a shared database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Stateless%20Shards%20with%20Shared%20DB.png) - A [[wiki/concepts/source/basic-metapatterns/shards|*Stateful Instance*]] per client with an external persistent storage – each instance owns the data related to its client and runs in a virtual environment (i.e. web browser or an [[wiki/concepts/source/implementation-metapatterns/microkernel|*Actor Framework*]]). ![Each user is allocated a temporary instance of a subsystem which loads their data at the start of the session and persists any changes to the database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Instance%20per%20Client.png) ### [[wiki/concepts/source/appendices/evolutions-of-a-monolith-that-result-in-layers|Evolutions to Layers]] Another drawback of *Monolith* is its… er… monolithism. The entire application exposes a single set of qualities and all its parts (if they ever emerge) are deployed together. However, life awards flexibility: parts of a system may benefit from being written in varying languages and styles and deployed with different frequency and amount of testing, sometimes to specific hardware or end users’ devices. They may need to [[wiki/concepts/source/foundations-of-software-architecture/forces-asynchronicity-and-distribution|vary in security and scalability]] as well. Enter [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] – a subdivision by the *level of abstractness*: - Most *Monoliths* can be divided into 3 or 4 layers of different abstractness. ![A monolith is split into application, domain and database layers.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Layers.png) - It is common to see the database separated from the main application. ![The data of a monolithic system is moved to a database, leaving the business logic stateless.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20add%20Database.png) - [[wiki/concepts/source/extension-metapatterns/proxy|*Proxies*]] (e.g. [[wiki/concepts/source/extension-metapatterns/proxy|*Firewall*]], [[wiki/concepts/source/extension-metapatterns/proxy|*Cache*]], [[wiki/concepts/source/extension-metapatterns/proxy|*Reverse Proxy*]]) are common additions to the system. ![A part of generic functionality of a monolith is moved to a proxy.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20add%20Proxy.png) - An [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] adds a layer of indirection to make the system’s external API more user-friendly. ![An orchestrator is added to a monolithic system, allowing for higher-level client requests.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20add%20Orchestrator.png) ### [[wiki/concepts/source/appendices/evolutions-of-a-monolith-that-make-services|Evolutions to Services]] The final major drawback of *Monolith* is the cohesiveness of its code. The rapid start of development with *Monolith* begets a major obstacle as the project grows: every developer needs to know the entire codebase to be productive while changes made by individual developers overlap and may break each other. Such distress is usually solved by dividing the project into modules along *subdomain boundaries* (which usually match [*bounded contexts*](https://martinfowler.com/bliki/BoundedContext.html)). However, that requires much work, and good boundaries and APIs are hard to design, wherefore many organizations prefer a slower iterative transition. - A *Monolith* can be split into [[wiki/concepts/source/basic-metapatterns/services|*Services*]] right away. ![A monolith is subdivided into services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Services.png) - A feature may be added or a weakly coupled part of the Monolith separated into a new service. ![A service is split from a monolith.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20Split%20Service.png) - Some domains allow for sequential data processing best described by [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipelines*]]. ![A Monolith is transformed into a pipeline.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Pipeline.png) ### [[wiki/concepts/source/appendices/evolutions-of-a-monolith-that-rely-on-plugins|Evolutions with Plugins]] The last group of evolutions does not really change the monolithic nature of the application. Instead, its goal is to improve the customizability of the *Monolith*: - Vanilla [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]] is the most direct approach which relies on replaceable bits of logic. ![Plugins customize the monolith's behavior.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Plugins.png) - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] is a subtype of *Plugins* which is all about isolating the main code from any third-party components which it uses. ![The database, external libraries, and a protocol support component are separated from the business logic and isolated with adapters.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Hexagonal.png) - [[wiki/concepts/source/implementation-metapatterns/microkernel|*Scripts*]] is a kind of [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]] – yet another subtype of *Plugins* – which gives users of the system full control over its behavior. ![The high-level logic is rewritten as scripts which are run by an interpreter.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Monolith/Monolith%20to%20Interpreter.png) ## Summary A *Monolith* is an unstructured application. It is the best architecture for rapid prototyping by a small team and it usually grants the best performance to costs ratio. However, it does not scale, lacks any flexibility and becomes unmanageable as the amount of code grows. --- title: "Pipeline" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Basic metapatterns/Pipeline.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Basic%20metapatterns/Pipeline source_license_note: "See namespace README; preserve attribution and source links." --- # Pipeline > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Basic metapatterns/Pipeline.md`. ![A diagram for Pipeline, in abstractness-subdomain-sharding coordinates.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Main/Pipeline.png) *Never return.* Push your data through a chain of processors. Known as: Pipeline \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\]. Structure: A component per step of data processing. Type: System topology, implementation. | *Benefits* | *Drawbacks* | | --- | --- | | It is very easy to add or replace components | Becomes too complex when the number of scenarios grows | | Multiple development teams and technologies | Poor latency | | Good scalability | Significant communication overhead | | The components can be reused | Error handling may be non-trivial | | The components can be tested in isolation | | References: *Pipes and Filters* are defined in \[[wiki/concepts/source/appendices/books-referenced|[POSA1]]\] and are the foundation for part 3 (Derived Data) of \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\]. \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\] has an overview of all kinds of *Pipelines* in general while \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] has a chapter on *Event-Driven Architecture*. \[[wiki/concepts/source/appendices/books-referenced|[DEDS]]\] is dedicated to *Event-Driven Architecture*. The \[[wiki/concepts/source/appendices/books-referenced|[SAHP]]\] chapter on *Data Mesh* was written by the pattern’s author. \[[wiki/concepts/source/appendices/books-referenced|[EIP]]\] is a whole book about distributed *Pipelines*. *Pipeline* is a variation of [[wiki/concepts/source/basic-metapatterns/services|*Services*]] with no user sessions \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\], a unidirectional data flow, and often a single message type per communication channel (which thus becomes a *data stream*). As processed data does not return to the module that requested processing, there is no common concept of request ownership or high-level ([[wiki/concepts/source/basic-metapatterns/layers|*integration*, *application*]]) business logic, which is instead defined by the graph of connections between the system’s components. On the one hand, as all the services involved are equal and know nothing about each other (their interfaces are often limited to a single entry point), it is very easy to reshape the overall algorithm. On the other hand, the system lacks the abstractness dimension, thus any new use case builds a separate pipeline, threatening to turn the architecture into a mess of thousands of intricately interrelated pieces when the number of scenarios grows. Moreover, error handling requires dedicated pipelines that roll back changes to the system’s state which had been committed by the earlier steps of a failed use case. ### Performance Because any task for a pipeline is likely to involve all (or most of, if branched) its steps, there is no way to optimize away communication. Therefore, latency tends to be high. However, as pipeline components are often stateless, multiple instances of individual services or entire pipelines can run in parallel, making *Pipeline* a highly scalable architecture. Another point to observe is that a local pipeline naturally spreads the load among the available CPU cores (by using one thread per component) without any explicit locks or thread synchronization. ### Dependencies There are three ways to build communication in a pipeline, each with different dependencies: - *Commands* make each service depend on the services it sends messages to. It is easy to add a new input to such a pipeline. - With *publish/subscribe* each service depends on the services it subscribes to. That case favors downstream branching with multiple consumers. - Services may share a *message schema*, in which case all of them depend on it, not on each other. That allows for reshuffling the services. ![Commands cause downstream dependencies. Notifications cause upstream dependencies. If a shared message schema is used, every component depends on the shared message.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Dependencies/Pipeline.png) See the [[wiki/concepts/source/foundations-of-software-architecture/choreography|*Choreography* chapter]] for a more detailed discussion. ### Applicability *Pipeline* is good for: - *Experimental algorithms.* This architecture allows for the data processing steps both to be tested in isolation and connected into complex systems without changing their code. - *Easy scaling.* Pipelines tend to evenly saturate all the available CPU cores without any need for custom schedulers. Stateless services can run distributed, thus the *Pipeline*’s scalability is limited only by its data channels. - *Tailoring projects*. Many pipeline components are abstract enough to be easily reused, greatly reducing the cost of serial development of customized projects once the company builds a collection of common reusable services. *Pipeline* does not work for: - *High number of use cases.* The number of components and their interactions is going to be roughly proportional to the number of supported use cases and will easily overwhelm any developer or architect if new scenarios continue to be added over time. - *Complex use cases*. Any conditional logic written as two or three lines of code with [[wiki/concepts/source/foundations-of-software-architecture/orchestration|*orchestration*]] is likely to need a separate pipeline and dedicated services with [[wiki/concepts/source/foundations-of-software-architecture/choreography|*choreography*]]. Errors and corner cases are remarkably difficult to handle \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\]. - *Low latency*. Every step of a data packet along its journey between services takes time, not in the least because of data serialization. Moreover, the next service in the chain may still be busy processing previous data packets or its activation involves the OS scheduler. ### Relations *Pipeline*: - Is a kind of [[wiki/concepts/source/basic-metapatterns/services|*Services*]] with unidirectional communication which often processes a single kind of data records. - Is [[wiki/concepts/source/analytics/pipelines-in-architectural-patterns|involved]] in [[wiki/concepts/source/fragmented-metapatterns/layered-services|*CQRS*]], [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence* with derived databases]], and [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*MVC*]]. - Can be extended with a [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]], [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]], or [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]]. ## Variants by scheduling A pipeline may be either always active or run once in a while: ### Stream processing, Nearline system *Stream processing* \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\] is when the pipeline components (*jobs, processors, filters, services* – whatever you prefer to call them) are actively waiting for input and process each incoming item immediately. This results in *near-real-time* (*nearline*) latency \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\] but the pipeline will then always use resources, even when there is nothing for it to process. ### Batch processing, Offline system *Batch processing* \[[wiki/concepts/source/appendices/books-referenced|[DDIA]], [[wiki/concepts/source/appendices/books-referenced|DDS]]\] happens when a batch of input items is first collected into storage, and then the pipeline is run (often on a schedule) to process it. Such a mode of action is called *offline* \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\] as the processing results are delayed. However, the company saves on resources because the pipeline is only active for brief periods of time. ## Examples *Pipelines* can be local or distributed, linear or branched (usually trees, but cycles may happen in practice), they may utilize a feedback engine to keep the throughput of all their components uniform by slowing down faster steps or scaling out slower ones. In some systems *pipes* (channels) or *filters* (services) persist data. *Pipes* may store the processed data in files or databases to enable error recovery and [*event sourcing*](https://microservices.io/patterns/data/event-sourcing.html). Filters may need to read or write to a database, which is often [[wiki/concepts/source/extension-metapatterns/shared-repository|*shared*]], if the data processing relies on the system’s state. Moreover, transferring data through a pipe may be implemented as anything ranging from a method call on the next filter to a pub/sub framework to polling a database or file system. Such a variety of options enables the use of pipelines in a wide range of domains. Notwithstanding, there are a few mainstream types of *Pipeline* architectures: - [*Pipes and Filters*](#pipes-and-filters-workflow-system) for local processing of data streams. - Distributed and usually branched [*Choreographed Event-Driven Architecture*](#choreographed-broker-topology-event-driven-architecture-eda-event-collaboration). - [*Data Mesh*](#data-mesh) which builds a reporting pipeline for analytical data. - Extremely elastic [*Pipelined Nanoservices*](#function-as-a-service-faas-nanoservices-pipelined). ### Pipes and Filters, Workflow System ![A pipeline chaining: source, three filters, and sink.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Pipes%20and%20Filters.png) *Pipes and Filters* \[[wiki/concepts/source/appendices/books-referenced|[POSA1]], [[wiki/concepts/source/appendices/books-referenced|POSA4]], [[wiki/concepts/source/appendices/books-referenced|EIP]]\] usually name a linear local system which obtains data with its *source*, passes the data through a chain of *filters*, connected by *pipes*, and outputs it via a *sink*. The entire *pipeline* may run as a single process to avoid the overhead of data serialization. It may range from a Unix shell script which passes file contents through a series of utilities to a hardware pipeline for image processing in a video [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|camera]]. The filters tend to be single-purpose (handling only one type of payload) and nearly stateless. In some cases a filter may use dedicated hardware (e.g. for encryption or audio/video processing). The entire pipeline often operates a single data format ([[wiki/concepts/source/extension-metapatterns/shared-repository|*Stamp Coupling*]]). Though most commonly a filter waits for data to appear in its input pipe, processes it, and pushes the result to its output pipe, which allows for multiple filters to run in parallel, some implementations may let the source push the data through the entire pipeline all the way through to the sink. In that case either each filter calls the next filter in the line directly or the sink pulls the data by making direct upstream calls itself \[[wiki/concepts/source/appendices/books-referenced|[POSA1]]\]. The last two approaches remove the need for pipes yet are limited to using a single CPU core. *Workflow* \[[wiki/concepts/source/appendices/books-referenced|[DDIA]], [[wiki/concepts/source/appendices/books-referenced|DDS]]\] is a more modern name for similar stepwise processing which often stores intermediate results in a file or database and may run distributed. However, the same word generally describes the sequence of high-level steps in a use case \[[wiki/concepts/source/appendices/books-referenced|[PEAA]]\], and is another name for [[wiki/concepts/source/basic-metapatterns/layers|*application* or *integration* logic]], therefore we will avoid using “workflow” as a synonym for “pipeline”. Examples: Unix shell pipes, processing of video streams, many types of hardware. ### Choreographed (Broker Topology) Event-Driven Architecture (EDA), Event Collaboration ![Event-Driven Architecture as a branched pipeline built from a group of services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Event-Driven%20Architecture.png) *Event-Driven Architecture* (*EDA*) means that the system is built of services which use events to communicate in a non-blocking way. The idea is similar to the [actor model](https://en.wikipedia.org/wiki/Actor_model) of telecom and embedded programming. Thus, *EDA* itself does not define anything about the structure of the system (except that it is not [[wiki/concepts/source/basic-metapatterns/monolith|*monolithic*]]). In practice, there are [two kinds](https://theburningmonk.com/2020/08/choreography-vs-orchestration-in-the-land-of-serverless/) of *Event-Driven Architectures*: - [[wiki/concepts/source/foundations-of-software-architecture/choreography|*Choreographed*]] */ Broker Topology* / [*Event Collaboration*](https://martinfowler.com/eaaDev/EventCollaboration.html) \[[wiki/concepts/source/appendices/books-referenced|[DEDS]]\] – the events are notifications (usually via publish/subscribe) and the services form tree-like structures, matching our definition of *Pipeline*. - [[wiki/concepts/source/foundations-of-software-architecture/orchestration|*Orchestrated*]] */ Mediator Topology* / [*Request-Response Collaboration*](https://martinfowler.com/eaaDev/RequestResponseCollaboration.html) – the events are request/confirmation pairs and usually there is a single entity that drives a use case by sending requests and receiving confirmations. Such a system corresponds to our [[wiki/concepts/source/basic-metapatterns/services|*Services*]] metapattern with the supervisor being an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]], discussed in a dedicated chapter. An ordinary *Choreographed Event-Driven Architecture* \[[wiki/concepts/source/appendices/books-referenced|[SAP]], [[wiki/concepts/source/appendices/books-referenced|FSA]], [[wiki/concepts/source/appendices/books-referenced|DDS]]\] is built as a set of subdomain services (similar to those of the parent [[wiki/concepts/source/basic-metapatterns/services|*Services*]] metapattern). Each of the services subscribes to notifications from other services which it uses as action / data inputs and produces notifications which other services may rely on. For example, an email service may subscribe to error notifications from other services in the system to let the users know about issues that occur while processing their orders. It will also subscribe to the user data service’s add / edit / delete notifications to keep its user contact database updated. This example shows several differences from a typical *Pipes and Filters* implementation: - The system supports multiple use cases (e.g. user registration and order processing). - A service has several entry points (the email service involves an order error handler and user created handler). - A notification which a service produces may have many subscribers or no subscribers (nobody needs to act on our sending an email to a user). Those points translate to difference in structure: while *Pipes and Filters* is usually a linear chain of components, *EDA* entails multiple branched (and sometimes looped) event flow graphs over a single set of subdomain services. Pipelined *Event-Driven Architecture* (often boosted with [event sourcing](https://learn.microsoft.com/en-us/azure/architecture/patterns/event-sourcing)) works well for highly loaded systems of moderate size, but larger projects are likely to grow prohibitively complex graphs of event flows and service dependencies. This architecture’s scalability is limited by the services’ databases and the pub/sub framework employed. *Event-Driven Architecture* may involve a [[wiki/concepts/source/extension-metapatterns/proxy|*Gateway*]] as a user-facing event source and sink and a [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] for an application-wise pub/sub engine. [[wiki/concepts/source/extension-metapatterns/orchestrator|*Front Controller*]] or [[wiki/concepts/source/extension-metapatterns/shared-repository|*Stamp Coupling*]] are used if it is important to know the state of requests that are being processed by the pipeline. Examples: high performance web services. ### Data Mesh ![Data Mesh builds an extra graph of services that stream and process analytical data.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Data%20Mesh.png) First and foremost, [*Data Mesh*](https://martinfowler.com/articles/data-mesh-principles.html) \[[wiki/concepts/source/appendices/books-referenced|[LDDD]], [[wiki/concepts/source/appendices/books-referenced|SAHP]]\] is not a [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]], but rather a *Pipeline*. This architecture applies [*CQRS*](https://learn.microsoft.com/en-us/azure/architecture/patterns/cqrs) on the system level: it separates the interfaces and channels through which the services change their state (matching *commands* of *CQRS* or [*OLTP*](https://en.wikipedia.org/wiki/Online_transaction_processing)) and the ones used to retrieve their data (similar to *queries* or [*OLAP*](https://en.wikipedia.org/wiki/Online_analytical_processing)). That results in two overlapping subsystems, *operational* and *analytical*, that share most of their nodes. The *operational system* is an ordinary [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]] or [*Event-Driven Architecture*](#choreographed-broker-topology-event-driven-architecture-eda-event-collaboration). The *analytical system* contains *Data Product Quanta* (*DPQ*) – services that provide convenient access (streaming, replaying, and possibly querying) to parts of the system’s data. The *DPQs* are assembled into a graph akin to *Event-Driven Architecture*. There are three kinds of *DPQs* \[[wiki/concepts/source/appendices/books-referenced|[SAHP]]\]: - A *source-aligned (native) DPQ* (*sDPQ*) is coupled to an operational service and streams (or provides queries into) its data. It is likely to be implemented as a [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Reporting Database*]]. - An *aggregate DPQ* (*aDPQ*) merges and transforms inputs from several sources (*sDPQs* or other *aDPQs*). It is similar to a [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Query Service*]]. - A *fit-for-purpose (custom-made) DPQ* (*fDPQ*) is an end-user (leaf application) of the *Data Mesh*’s data. It may collect a dataset for machine learning or let a business analyst do their research. *fDPQs* tend to be short-lived one-off components. There is a pragmatic option to allow an operational service to resort to the analytical system’s *DPQs* to query other services’ data instead of messaging them directly or implementing a [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*CQRS View*]] and subscribing it to events that flow in the operational system. ### Function as a Service (FaaS), [[wiki/concepts/source/extension-metapatterns/sandwich|Nanoservices]] (pipelined) ![Many Nanoservices access a shared database to implement CRUD functionality.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Nanoservices.png) A [[wiki/concepts/source/basic-metapatterns/services|*nanoservice*]] is literally a [function as a service](https://en.wikipedia.org/wiki/Function_as_a_service) (*FaaS*) \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\] – a stateless (thus perfectly scalable) component whose API comprises a single input method. *Nanoservices* run in proprietary cloud [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] over a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Database*]] and are [chained into pipelines](https://increment.com/software-architecture/the-rise-of-nanoservices/) dedicated to specific use cases. The code complexity stays low, but as the project grows, the integration will quickly turn into a nightmare of hundreds or thousands of interconnected nanoservices. *Nanoservices* are good for rapid development of small elastic (dynamically scalable) applications. The supported load is limited by the *Shared Database*, and the project evolvability is limited by the complexity of scenarios. As any use case is going to involve many asynchronous steps, latency is not a strong side of *Nanoservices*. ## [[wiki/concepts/source/appendices/evolutions-of-a-pipeline|Evolutions]] *Pipeline* [[wiki/concepts/source/basic-metapatterns/services|inherits its set of evolutions from *Services*]]. Filters can be added, split in two, merged, or replaced. Many systems employ a [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] (a pub/sub or pipeline framework), a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]] (which may be a database or a file system), or [[wiki/concepts/source/extension-metapatterns/proxy|*Proxies*]]. There are a couple of [[wiki/concepts/source/appendices/evolutions-of-a-pipeline|pipeline-specific evolutions]], with more details provided in [[wiki/concepts/source/appendices/evolutions-of-architectures|Appendix E]]: - The first service of the *Pipeline* can be promoted to a [[wiki/concepts/source/extension-metapatterns/orchestrator|*Front Controller*]] which tracks the status updates for every request it handles. ![The first service of a pipeline subscribes to notifications from other services and thus becomes a Front Controller.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Services/Pipeline%20promote%20Front%20Controller.png) - Adding an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] turns a *Pipeline* into normal [[wiki/concepts/source/basic-metapatterns/services|*Services*]]. As the high-level business logic moves into the orchestration layer, the domain-level filters don’t need to interact directly, therefore the inter-filter communication channels disappear and the system becomes identical to *Orchestrated Services*. ![Adding an orchestrator transforms a pipeline into Orchestrated Services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Services/Pipeline%20use%20Orchestrator.png) ## Summary A *Pipeline* represents a data processing algorithm as a sequence of steps. It not only subdivides the system’s code into smaller components but is also very flexible: its parts are easy to add, remove, or replace. Several use cases can be built over the same set of services. Scalability is good. Event replay helps with debugging. However, this architecture lacks support for complex scenarios and error handling which limits it to smaller domains with a few use cases. --- title: "Services" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Basic metapatterns/Services.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Basic%20metapatterns/Services source_license_note: "See namespace README; preserve attribution and source links." --- # Services > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Basic metapatterns/Services.md`. ![A diagram for Services, in abstractness-subdomain-sharding coordinates.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Main/Services.png) *Divide and conquer.* Scale development through decoupling subdomains. Known as: Services, Domain Services \[[wiki/concepts/source/appendices/books-referenced|[FSA]] and [[wiki/concepts/source/appendices/books-referenced|SAHP]], [[wiki/concepts/source/analytics/ambiguous-patterns|but not]] [[wiki/concepts/source/appendices/books-referenced|DDD]]\], Modules \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]. Structure: A component per subdomain. Type: System topology. | *Benefits* | *Drawbacks* | | --- | --- | | Supports large codebases | Global use cases are hard to debug | | Multiple development teams and technologies | Poor latency in global use cases | | Forces may vary by subdomain | No good way to share state between services | | | The domain structure should never change | | | Operational complexity | References: \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] has a chapter on *Service-Based Architecture*; \[[wiki/concepts/source/appendices/books-referenced|[MP]]\] is dedicated to *Microservices*. Splitting a [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] by *subdomain* allows for mostly independent properties, development, and deployment of the resulting components (distributed *services* or colocated *modules*). However, for the system to benefit from the division, its subdomains must be loosely coupled and, ideally, of comparable size. In that case the partitioning can reduce complexity of the project’s code by cutting accidental dependencies between the subdomains. Moreover, if one of the resulting services grows unmanageably large, it can often be further partitioned by sub-subdomains to form a [*Cell*](#cell-wso2-definition-service-of-services-domain-uber-definition-cluster). This flexibility is paid for through the complexity and performance of use cases which involve multiple subdomains. Another issue to remember is that boundaries between services are [nearly impossible](https://martinfowler.com/bliki/MonolithFirst.html) to move at later project stages as the services grow to vary in technologies and implementation styles, thus separation into services assumes perfect practical knowledge of the domain and relatively stable requirements. > [Research](https://www.qsm.com/team-size-can-be-key-successful-software-project) shows that when more than five programmers work on the same subject, their performance degrades. Therefore, if we want our employees to be efficient, they should be grouped into small teams and each team should be given ownership of a dedicated component. ### Performance Interservice communication is relatively slow and resource-consuming, therefore it should be kept to a minimum. ![Performance of Services is the best when the request is limited to a single service and the worst when the state of several services needs to be synchronized.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Performance/Services.png) The perfect case is when a single service has enough authority to answer a client’s request or process an event. That case should not be that rare as a service often covers a whole subdomain while subdomains are expected to be loosely coupled (by definition). Worse is when an event starts a chain reaction throughout the system, likely looping back a response to the original service or changing the target state of another controlled subsystem. In the slowest scenario a service needs to synchronize its state with multiple other services, usually via *locks* and *distributed transactions*. Multiple [[wiki/concepts/source/basic-metapatterns/shards|instances]] of an individual service may be deployed to improve throughput of the system. However, that will likely need a [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] or [[wiki/concepts/source/extension-metapatterns/proxy|*Load Balancer*]] to distribute interservice requests among the instances and a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]] to store and synchronize any non-shardable (accessed by several instances) state. ### Dependencies When we see a service to *request* help from other services and then receive the results (in a *confirmation* message), that service [[wiki/concepts/source/foundations-of-software-architecture/orchestration|*orchestrates*]] the services it uses. Services often orchestrate each other because the subdomain a service is dedicated to is not independent of other subdomains. ![With request/confirm a service depends on whatever it uses.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Dependencies/Services-1.png) Another way for services to communicate is [[wiki/concepts/source/foundations-of-software-architecture/choreography|*choreography*]] – when a service sends a *command* or publishes a *notification* and does not expect any response. This is characteristic of [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipelines*]] which are covered in the next chapter. Right now we should note that orchestration and choreography may be intermixed, in which case a service depends on all the services it uses or subscribes to. ![With pub/sub a service depends on its notification sources.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Dependencies/Services-2.png) If the system relies on notifications (services publish *domain events*), it is possible to avoid interservice *queries* (pairs of a *read* request and confirmation with the data retrieved) by aggregating data from notifications in a [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*CQRS* (or *materialized*) *View*]], which can reside [in memory](https://martinfowler.com/bliki/MemoryImage.html) or in a database. Views can be planted inside every service that needs data owned by other services or can be gathered into a dedicated [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Query Service*]]. Though the main goal of *CQRS Views* is to resolve distributed joins from databases of multiple services, they also help [[wiki/concepts/source/analytics/indirection-in-commands-and-queries|remove dependencies]] in the code of services and optimize out interservice queries, simplifying APIs and improving performance. Further examples will be discussed in the chapter on [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]]. ![A CQRS view breaks dependencies between services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Dependencies/Services-3.png) In general, a large service should wrap its dependencies with an [[wiki/concepts/source/extension-metapatterns/proxy|*Anticorruption Layer*]], following the ideas of [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]]. The layer consists of [*Adapters*](https://refactoring.guru/design-patterns/adapter) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] between the internal domain model of the service and the APIs of the components it uses. The *Adapters* isolate the business logic from the external environment, granting that no change in the interface of an external service or library may ever take much work to support on the side of the team that writes its own business logic as all the ensuing updates are limited to a small adapter. ![Adapters isolate a service from its dependencies.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Dependencies/Services-4.png) ### Applicability *Services* are good for: - *Large projects.* With multiple services developed independently, a project may grow well above 1 000 000 lines of code and still be comfortable to work on as every team needs to know only the medium-sized component it owns. - *Specialized teams.* Each service would often be written and supported by a dedicated team that invests its time in learning its subdomain. This way no one needs to have a detailed knowledge of the full set of requirements, which is next to impossible in large domains. - *Varied forces*. In systems and embedded programming, components of wildly varying behaviors need to be managed. Each of them is controlled by a dedicated service (called *driver*) which adapts to the specifics of the managed subsystem. - *Flexible scaling.* Some services may be under more load than others. It makes sense to deploy multiple instances of heavily loaded services. *Services* should be avoided in: - *Cohesive domains.* If everything strongly depends on everything, any attempt to cut the knot with interfaces is going to [[wiki/concepts/source/foundations-of-software-architecture/modules-and-complexity|make things worse]] unless the project is already dying because of its huge codebase, in which case you have little to lose. - *Unfamiliar domains*. If you don’t understand the intricacies of the system you are going to build, you may [misalign the interfaces](https://martinfowler.com/bliki/MonolithFirst.html) and, by the time that the mistakes come to light, the architecture will be too hard to change \[[wiki/concepts/source/appendices/books-referenced|[LDDD]]\]. The coupled *Services* you get may actually be worse than a [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]]. - *Quick start*. It takes effort to design good interfaces and contracts for *Services* and managing multiple deployment units is not free of trouble. Debugging will also be an issue. - *Low latency*. If the system as a whole needs to react to events in real time, complex services should be avoided. Nevertheless, an individual service can provide low latency for local use cases (when a single service has enough authority to react to the incoming event), wherefore [simple non-blocking actors](#asynchronous-modules-modular-monolith-modulith-embedded-actors) are widely used in [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|control software]]. ### Relations ![Splitting an Orchestrator into Backends for Frontends.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Relations/Services.png) - Division by subdomain can be applied to [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] to form [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]] (partial subdivision) or [*Service-Oriented Architecture*]() (layers of services); to [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]], [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]], or [[wiki/concepts/source/extension-metapatterns/orchestrator|*API Gateway*]] to make [*Backends for Frontends*](); to a [[wiki/concepts/source/extension-metapatterns/shared-repository|(shared) database]] resulting in [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]]. - Services can be extended with a [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]], [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]], [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]], or [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]]. - Each service can be implemented by [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] (making a [[wiki/concepts/source/fragmented-metapatterns/layered-services|*layered service*]]), [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]], or a [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cell*]]. ## Variants by isolation Division by subdomain is so commonplace that no universal terminology has emerged over the years. Below is my summary, in no way complete, of several ways such systems can vary. Each section lists the well-known architectures it applies to. First and foremost, there are multiple stages between a cohesive [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] and distributed *Services*. You should be careful to stop these incremental changes as soon as the benefits of the next step (color-coded below) don’t outweigh its drawbacks for your project. > I review here only the most common options while a few more esoteric architectures are found in [Volodymyr Pavlyshyn’s overview](https://volodymyrpavlyshyn.medium.com/monoliths-microlith-moduliths-self-contained-systems-a-system-of-systems-nano-services-cf3e9e1869c0). ### Synchronous modules: Modular Monolith (Modulith) The first step to take when designing a large project is the division of its codebase into loosely coupled modules that match subdomains (*bounded contexts* \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]). If successful, that parallelizes development to a team per module while the entire application still runs in a single process, thus it stays easy to debug, the modules can share data, and any crash kills the whole system (meaning that you don’t need to take care of partial failures). You pay by establishing boundaries which will not be easy to move in the future. | *Benefits* | *Drawbacks* | | --- | --- | | Multi-team development | Subdomain boundaries are settled | ### Asynchronous modules: Modular Monolith (Modulith), Embedded [Actors](#actors) The next stage is separating the modules’ execution threads and data. Each module becomes a kind of [*actor*](https://en.wikipedia.org/wiki/Actor_model) that communicates with other components through messaging. Now your modules don’t block each other’s execution and you can [replay events](http://ithare.com/chapter-vc-modular-architecture-client-side-on-debugging-distributed-systems-deterministic-logic-and-finite-state-machines/) at the cost of nightmarish debugging and no clean way to share data between or synchronize the state of the components. | *Benefits* | *Drawbacks* | | --- | --- | | Multi-team development | Subdomain boundaries are settled | | Event replay | No good way to share data or synchronize state | | Some independence of module qualities | Hard to debug | ### Multiple processes There is also the option of running system components as separate binaries which lets them vary in technologies, allows for granular updates, and addresses stability (a web browser does not stop when one of its tabs crashes). But it adds a whole new dimension of painful error recovery that includes partially executed scenarios. Moreover, if the technologies of the components diverge, it is impossible to move code between them. | *Benefits* | *Drawbacks* | | --- | --- | | Multi-team development | Subdomain boundaries are frozen | | Event replay | No good way to share data or synchronize state | | Independence of component qualities and technologies | Hard to debug | | Single-component updates | Needs error recovery routines | | Software fault isolation | Data inconsistencies after partial crashes | | Limited granular scalability | | ### Distributed runtime: Backend [Actors](#actors) Modern distributed [runtimes](https://en.wikipedia.org/wiki/Runtime_system) create virtual namespaces that can be deployed on a single machine or over a network. They may redistribute running components among servers in a way to minimize network communication and may offer distributed debugging. With [*Actors*](https://en.wikipedia.org/wiki/Actor_model), if one of them crashes, that generates a message to another actor which may decide on how to handle the error. The convenience of using a runtime has the dark side of vendor lock-in. | *Benefits* | *Drawbacks* | | --- | --- | | Multi-team development | Subdomain boundaries are frozen | | Event replay | No good way to share data or synchronize state | | Independence of component qualities ~and technologies~ | Hard to debug | | Single-component updates | Needs error recovery routines | | Full fault isolation | ~Data inconsistencies after partial crashes~ | | Full dynamic granular scalability | Vendor lock-in | | | Moderate communication overhead | | | Moderate performance overhead caused by the framework | ### Distributed services: Service-Based Architecture, Space-Based Architecture, Microservices Fully autonomous services run on dedicated servers or virtual machines. This way you employ resources of multiple servers, but the communication between them is both unstable (requests may be lost, reordered or duplicated) and slow and debugging tends to be very hard. [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]]-based ([[wiki/concepts/source/implementation-metapatterns/mesh|*Microservices*]] and [[wiki/concepts/source/implementation-metapatterns/mesh|*Space-Based*]]) architectures provide dynamic scaling under load. | *Benefits* | *Drawbacks* | | --- | --- | | Multi-team development | Subdomain boundaries are frozen | | Event replay | No ~good~ way to share data or synchronize state | | Independence of component qualities and technologies | Very hard to debug | | Single-component updates | Needs error recovery routines | | Full fault isolation | Data inconsistencies after partial crashes | | Full (dynamic for *Mesh*) granular scalability | High communication overhead | ## Variants by communication Services also differ in the way they communicate which influences some of their properties: ### Direct method calls When components run inside the same process and share execution threads, one component can call another. That is blazingly fast and efficient, but you should take care to protect the module’s state from simultaneous access by multiple threads (and yes, [*deadlocks*](https://en.wikipedia.org/wiki/Deadlock_(computer_science)) do happen in practice). Moreover, it is hard to know what the module you call is going to call in its turn, while you are waiting on it – thus no matter how much you optimize your code, its performance depends on that of other components, often in subtle ways. ### RPCs and commands (request/confirm pairs) If a service [calls another service](https://en.wikipedia.org/wiki/Remote_procedure_call) or requests it to act and return results (this is how method calls are implemented in distributed systems) it has to store the state of the scenario it is executing for the duration of the call (until the confirmation message is received). That uses resources: the stored state is kept in RAM and the interruption and resumption of the execution wastes CPU cycles on context switch and on the resulting cache misses. Blocked [[wiki/concepts/source/basic-metapatterns/monolith|threads]] are especially heavy while [[wiki/concepts/source/basic-metapatterns/monolith|coroutines or fibers]] are more lightweight but are still not free. Another trouble with distributed systems comes from error recovery: if your component did not receive a timely response, you don’t know if your request was (or is being, or will be) executed by its target – and you need to be really careful about possible data corruption if you retry it and it is executed twice \[[wiki/concepts/source/appendices/books-referenced|[MP]]\]. > If a request is duplicated (as a slow network, overloaded service, or lost confirmation may cause a retry), it is important to make sure that the second (or parallel) execution of the request does not change the system’s data. This is achieved either by using [*idempotent*](https://en.wikipedia.org/wiki/Idempotence#Computer_science_examples) logic (which is based on assignment instead of increasing or decreasing values in place), or by writing the id of the last processed message to the database (and checking that the incoming message’s id is greater than the one found in the database) \[[wiki/concepts/source/appendices/books-referenced|[MP]]\]. On the bright side, [[wiki/concepts/source/foundations-of-software-architecture/orchestration|*orchestration*]] is human- and debugger-friendly as it keeps consecutive actions close together in the code. Therefore, synchronous interaction is still the default mode of communication in many projects. ### Notifications (pub/sub) and shared data A service may do its part, then publish a notification or [[wiki/concepts/source/foundations-of-software-architecture/shared-data|write results to a shared data store]] for other services to process, and finally forget about the task as it has completed its role. [[wiki/concepts/source/foundations-of-software-architecture/choreography|*Choreography*]] is resource-efficient, but you need to find and read multiple pieces of code which are spread out over several services to understand or debug the whole use case. ### (inexact) No communication Finally, some kinds of services, namely [*device drivers*](#inexact-device-drivers-pedestal) and [*Nanoservices*](#inexact-nanoservices-api-layer), never communicate with each other. Strictly speaking, such services don’t make a system – instead, they are isolated *Monoliths* which are managed by a higher-level component (OS kernel for drivers, client or a [[wiki/concepts/source/extension-metapatterns/proxy|*Gateway*]] for *Nanoservices*). Nevertheless, it is a fun fact that if the services don’t intercommunicate, the main drawbacks of the *Services* architecture disappear: - There is no slow and error-prone interservice communication (they never communicate!). - It’s not hard to debug multi-service use cases (there are no such scenarios!). - The services don’t corrupt data on crash (there are no distributed transactions). ## Variants by size Last but not least, the simplest classification of subdomain-separated components is by their size: ### Whole subdomain: [[wiki/concepts/source/extension-metapatterns/sandwich|(Sub-)Domain Services, Macroservices]] Each *Domain Service* \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] (*Macroservice*) of [*Service-Based Architecture*](#service-based-architecture-sba-macroservices) implements a whole subdomain. It is the product of the full-time work of a dedicated team. A project is unlikely to have more than a dozen of such services (in part because the number of top-level subdomains in any domain is usually limited). ### Part of a subdomain: [Microservices](#microservices) *Microservices* enthusiasts estimate the optimal size of a component of their architecture to be below a month of development by a single team. That allows for a complete rewrite instead of refactoring in case the requirements change. When a team completes one microservice it can start working on another, probably related, one while still maintaining its previous work. A system of *Microservices* is likely to contain from tens to few hundreds of them. Some projects will cluster these into [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cells*]]. ### Class-like: [Actors](#actors) An [*actor*](https://volodymyrpavlyshyn.medium.com/actors-actor-systems-as-massively-distributed-scalability-architecture-5e40f5ea9e86) is an object with a message-based interface. They are used correspondingly. Though the size of an actor may vary, as does the size of an OOP class, it is still very likely to be written by a single programmer. ### Single function: FaaS, Nanoservices A [*nanoservice*](https://medium.com/@ido.vapner/unlocking-the-power-of-nano-services-a-new-era-in-microservices-architecture-22647ea36f22) is a single function ([FaaS](https://en.wikipedia.org/wiki/Function_as_a_service) \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\]) usually deployed to a *serverless* provider. Nanoservices are used as API method handlers or as building blocks for [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipelines*]]. ## Variants by internal structure A service is not necessarily monolithic inside. Because a service is encapsulated from its users by its interface, it can have any kind of internal structure. The most common cases, which can be intermixed together, are: ![A monolithic service, layered service, hexagonal services, scaled service, and a Cell interconnected into a single system.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Subtypes%20of%20Services.png) ### [[wiki/concepts/source/basic-metapatterns/monolith|Monolithic]] service ![A diagram of a monolithic component.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Service%20-%20Monolithic.png) A *monolithic service* is a service with no definite internal structure, probably small enough to allow for complete rewrite instead of refactoring – the ideal of proponents of [*Microservices*](#microservices). It is [simple & stupid](https://en.wikipedia.org/wiki/KISS_principle) to implement but relies on external sources of persistent data. For example, *device drivers* and [*actors*](#actors) usually get their (persisted) configuration during initialization. A monolithic backend service may receive all the data it needs in incoming requests, via a query to another service, or by reading it from a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Database*]]. ### [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Hexagonal]] service ![A core connected to: a protocol adapter, a Database Abstraction Layer with a database behind it, and an adapter with a library behind it.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Service%20-%20Hexagonal.png) A *hexagonal service* has its external dependencies isolated behind vendor-agnostic interfaces. This is a real-world application of [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] which both ensures that the business logic does not depend on specific technologies and protects from vendor lock-in. It is highly recommended for long-lived projects. ### [[wiki/concepts/source/basic-metapatterns/shards|Scaled]] service ![Stateless instances between a load balancer and a database; stateful shards behind a sharding proxy.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Service%20-%20Scaled.png) With *scaled services* there are multiple [[wiki/concepts/source/basic-metapatterns/shards|*instances*]] of a service. In most cases they [[wiki/concepts/source/extension-metapatterns/shared-repository|*share a database*]] (though sometimes the database may be [[wiki/concepts/source/basic-metapatterns/shards|*sharded*]] or [[wiki/concepts/source/basic-metapatterns/shards|*replicated*]] together with the service that uses it) and get their requests through a [[wiki/concepts/source/extension-metapatterns/proxy|*Load Balancer* or *Sharding Proxy*]], making [[wiki/concepts/source/extension-metapatterns/sandwich|a kind of *Sandwich*]]. ### [[wiki/concepts/source/basic-metapatterns/layers|Layered]] service ![The integration, core and database layers.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Service%20-%20Layered.png) A *layered service* is [[wiki/concepts/source/fragmented-metapatterns/layered-services|divided into *layers*]]. This approach is very common both with backend *(micro-)services*, where at least the database is separated from the business logic, and with *device drivers* in system programming, where hardware-specific low-level interrupt handlers and register access are separated from the main logic and high-level OS interface. Layering provides all of the benefits of the [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] pattern, including support for [[wiki/concepts/source/foundations-of-software-architecture/forces-asynchronicity-and-distribution|conflicting forces]], which may manifest, for example, as the ability to deploy the database to a dedicated server for a backend or as a very low latency in the hardware-facing layer of a device driver. Another benefit comes from the existence of the upper integration layer which may [[wiki/concepts/source/foundations-of-software-architecture/orchestration|orchestrate interactions with other services]], isolating the lower layers from external dependencies. ### [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Cell]] (WSO2 definition) (service of services), Domain (Uber definition), Cluster ![Three subservices behind a Cell gateway. Two of them share a database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Service%20-%20Cell.png) When a service is split into a set of subservices, it makes [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|a kind of *Hexagonal Architecture*]] called [*Cell*](https://github.com/wso2/reference-architecture/blob/master/reference-architecture-cell-based.md) (WSO2 name), [*Domain*](https://www.uber.com/blog/microservice-architecture/) (Uber name), or *Cluster* \[[wiki/concepts/source/appendices/books-referenced|[DEDS]]\]. All the incoming communication passes through a [[wiki/concepts/source/extension-metapatterns/proxy|*Cell Gateway*]] which encapsulates the *Cell* from its environment. Outgoing communication may involve the *Cell Gateway* or dedicated [[wiki/concepts/source/extension-metapatterns/proxy|*Adapters*]] (which constitute an *Anticorruption Layer* \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]) A *Cell* may deploy its own [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] and/or [[wiki/concepts/source/extension-metapatterns/shared-repository|*share a database*]] among its components. [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Cell-Based Architecture*]] ([according to WSO2](https://github.com/wso2/reference-architecture/blob/master/reference-architecture-cell-based.md), as opposed to [Amazon's alias](https://docs.aws.amazon.com/wellarchitected/latest/reducing-scope-of-impact-with-cell-based-architecture/what-is-a-cell-based-architecture.html) for [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]]) appears when there is a need to recursively split a service, either because it grew too large or because it makes sense to use several incompatible technologies for its parts. It may also be applied to group services if there are too many of them in the system. [*Domain-Oriented Microservice Architecture*]() (DOMA) is a more complex [*SOA*]()-style layered system of *Cells*. ## Examples *Services* are ubiquitous among advanced architectures which either build around a layer of services that contains the bulk of the business logic (see [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]], [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]], [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] and [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]]) or use small services as an extension of the main monolithic component ([[wiki/concepts/source/implementation-metapatterns/plugins|*PlugIns*]] and [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]]). [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]], [*Backends for Frontends*]() and [*Service-Oriented Architecture*]() go all out partitioning the system into interconnected layers of services. [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchy*]] and [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]] often require the services to implement or use a polymorphic interface to simplify the components that manage them. Examples of *Services* include: - [*Service-Based Architecture*](#service-based-architecture-sba-macroservices) with subdomain-sized services which may share a database. - Highly scalable, and usually smaller, [*Microservices*](#microservices). - [*Actors*](#actors) that are like classes with asynchronous interfaces. - [*Nanoservices*](#inexact-nanoservices-api-layer) – stand-alone functions deployed to a cloud. - [*Device Drivers*](#inexact-device-drivers-pedestal) that interface hardware components inside an operating system. ### [[wiki/concepts/source/extension-metapatterns/sandwich|Service-Based Architecture]] (SBA), [Macroservices](#whole-subdomain-sub-domain-services-macroservices) ![Three interconnected services, two of which share a database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Service-Based%20Architecture.png) Being the simplest use of *Services* where each subdomain gets a dedicated component, a *Service-Based Architecture* (*SBA*) \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] (aka *Macroservices*) tends to consist of a few coarse-grained services, some of which may [[wiki/concepts/source/extension-metapatterns/shared-repository|*share a database*]] and thus have little direct communication. An [[wiki/concepts/source/extension-metapatterns/orchestrator|*API Gateway*]] is often present as well, making the *SBA* into a kind of [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]]. ### Microservices ![Multiple instances of several services connected to their sidecars which are connected to a shared mesh engine. Instances of each service access its single database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Microservices.png) *Microservices* \[[wiki/concepts/source/appendices/books-referenced|[MP]], [[wiki/concepts/source/appendices/books-referenced|FSA]]\] are usually smaller than components in *Service-Based Architecture* and feature multiple services per subdomain with strict decoupling: they never use a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Database*]] and are scaled and deployed independently (and often dynamically). Even [[wiki/concepts/source/foundations-of-software-architecture/orchestration|*orchestration*]] and distributed transactions ([[wiki/concepts/source/extension-metapatterns/orchestrator|*Sagas*]]) are considered to be a smell of bad design. *Microservices* fit loosely coupled domains with parts which [vary drastically](https://medium.com/swlh/stop-this-microservices-madness-8e4e0695805b) in both forces and technologies. Any attempt to use them for an unfamiliar domain is [calling for trouble](https://martinfowler.com/bliki/MonolithFirst.html). Some authors insist that the “micro-” means that a microservice should not be larger in scope than a couple of weeks of work for a programming team. That allows rewriting one from scratch instead of refactoring. Others assert that too high a granularity makes everything [overcomplicated](https://dwmkerr.com/the-death-of-microservice-madness-in-2018/). Such a diversity of opinions may mean that the applicability and the very definition of *Microservices* varies from domain to domain. This architecture usually relies on a [[wiki/concepts/source/extension-metapatterns/middleware|*Service Mesh*]] for [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] where common functionality, like logging, is implemented in co-located [[wiki/concepts/source/extension-metapatterns/proxy|*Sidecars*]]. A layer of [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrators*]] (called *Integration Microservices*) [may be present](https://github.com/wso2/reference-architecture/blob/master/api-driven-microservice-architecture.md), resulting in [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Cell-Based Architecture*]] or [*Backends for Frontends*](). *Dynamically scaled* [[wiki/concepts/source/basic-metapatterns/shards|*Pools*]] of service instances are common thanks to the elasticity of hosting in a cloud. Extreme elasticity requires [[wiki/concepts/source/implementation-metapatterns/mesh|*Space-Based Architecture*]], which puts a [[wiki/concepts/source/extension-metapatterns/shared-repository|distributed in-memory data store]] node within each *Sidecar*. > Some authors [distinguish](https://medium.com/@ali.gelenler/architectural-styles-vs-architectural-patterns-7fab51713470) between *architectural patterns* and *architecture styles* (*architectures*) \[[wiki/concepts/source/appendices/books-referenced|[FSA]], [[wiki/concepts/source/appendices/books-referenced|MP]]\]. The difference is similar to that between libraries and frameworks: you use a library or pattern (e.g. division of a component into [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] or [*Services*](#)) when you think that it will help your needs, but you build your entire system according to the rules of a framework or style (such as *Microservices* or [*Enterprise SOA*]()). This book does not accent that difference – instead, it boils down styles to combinations of patterns. ### [[wiki/concepts/source/implementation-metapatterns/mesh|Actors]] ![Actors running over an actor framework.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Actors.png) An [*actor*](https://volodymyrpavlyshyn.medium.com/actors-actor-systems-as-massively-distributed-scalability-architecture-5e40f5ea9e86) is an entity with private data and a public message queue. They are like objects with the difference that actors communicate only by sending each other asynchronous messages. The fact that a single execution thread may serve thousands of actors makes actor systems an extremely lightweight approach to asynchronous programming. As an actor is usually single-threaded, there is no place for *mutexes* and *deadlocks* in the code and it is possible to [replay events](https://martinfowler.com/eaaDev/EventSourcing.html). Non-blocking [[wiki/concepts/source/basic-metapatterns/monolith|*Proactors*]] are often found in [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|real-time systems]]. > A [*deadlock*](https://en.wikipedia.org/wiki/Deadlock_(computer_science)) happens when several threads in a system wait for each other to release unique resources they have each taken. As no thread involved in the *deadlock* can continue its operation, the system cannot complete its task. A single-threaded actor cannot *deadlock* because it does not contain multiple threads in the first place. *Actors* have long been used in telephony (which is a domain where real-time communication meets complex logic and low resources) and with the invention of distributed runtime environments (e.g. Erlang/OTP or Akka) they expanded to messengers and banking which need to interconnect millions of users while providing personalized experience and history for everyone. Every user gets an actor that represents them in the system by communicating both with other actors (forming a kind of [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]]) and with the user’s client application(s). If we apply a bit of generalization, we can deduce that any server or backend service is an actor because its data cannot be accessed from outside and asynchronous IP packets are its only means of communication. Services of [[wiki/concepts/source/basic-metapatterns/pipeline|*Event-Driven Architecture*]] closely match this definition. ### (inexact) [[wiki/concepts/source/basic-metapatterns/pipeline|Nanoservices]] (API layer) ![Nanoservices dedicated to Get and Post methods between a client and a shared database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Nanoservices%20-%20API%20Layer.png) Though *Nanoservices* are defined by their size (a single function), not system topology, I want to mention a specific application from Diego Zanon’s book *Building Serverless Web Applications*. That example is interesting because it comprises a single layer of isolated functions (each providing one API method) which may share functionality by including code from a common repository. As nanoservices of this kind never interact directly ([[wiki/concepts/source/foundations-of-software-architecture/shared-data|they rely]] on a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Database*]] instead) the common drawbacks of *Services* (poor debugging and high latency) don’t apply to them. ### (inexact) [[wiki/concepts/source/extension-metapatterns/proxy|Device Drivers]], [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Pedestal]] ![Applications call a System Call Interface which dispatches their requests to device drivers.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Drivers.png) An operating system must run efficiently with an unpredictable combination of hardware components, any of which can come from different manufacturers. It is impossible to know all the combinations beforehand. Thus it builds a [*Pedestal*](https://alistair.cockburn.us/hexagonal-architecture) by employing one service (called [[wiki/concepts/source/extension-metapatterns/proxy|*driver*]]) per hardware device. A driver *adapts* a manufacturer- and model-specific hardware interface to the generic interface of the OS *kernel*, allowing for the kernel to operate the hardware it controls without the detailed knowledge of the model. Internally, a driver is usually [[wiki/concepts/source/basic-metapatterns/layers|*layered*]]: - The lowest layer, called the [[wiki/concepts/source/extension-metapatterns/proxy|*Hardware Abstraction Layer*]] (*HAL*), provides a model-independent interface for a whole family of devices from a manufacturer. - The next layer of a driver is likely to contain manufacturer-specific algorithms for efficient use of the hardware. - The third layer, if present, is probably busy with high-level tasks which are common for all devices of the given type and may be implemented by the kernel programmers. The whole system of kernel, drivers, and user applications comprises the [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]] architecture which bridges resource consumers and resource providers. As the drivers don’t need to coordinate themselves (this is done by the kernel), they don’t really make a system of *Services* and thus don’t have the corresponding drawbacks. ## Evolutions *Services* are subject to a wide array of evolutions, just like the other basic metapatterns. These are summarized below and detailed in [[wiki/concepts/source/appendices/evolutions-of-architectures|Appendix E]]. ### [[wiki/concepts/source/appendices/evolutions-of-services-that-restructure-services|Evolutions that restructure services]] *Services* work well when each service matches a subdomain and is developed by a single team. If those premises change, you’ll need to restructure the services: - A new feature request may emerge outside of any of the existing subdomains, creating a new service, or a service may grow too large to be developed by a single team, calling for division. ![A service is split in half.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Services/Services_%20Split.png) - Two services may become so strongly coupled that they fare better if merged together, or the entire system may need to be glued back into a [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] if the domain knowledge changes or if interservice communication strongly degrades performance. ![Two services are merged.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Services/Services_%20Merge.png) - Alternatively, coupled services may be clustered into co-deployed [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cells*]] to reduce operational complexity. ![Services are grouped into Cells, reducing their interdependencies.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Services/Services_%20Cluster.png) ### [[wiki/concepts/source/appendices/evolutions-of-services-that-add-layers|Evolutions that add layers]] The most common modifications of a system of *Services* involve supplementary system-wide layers which compensate for the inability of the services to [[wiki/concepts/source/analytics/sharing-functionality-or-data-among-services|share]] anything among themselves: - A [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] tracks all the deployed service instances. It mediates the [[wiki/concepts/source/basic-metapatterns/layers|communication]] between them and may manage their scaling and failure recovery. ![The communication aspect of services can be covered by a dedicated middleware.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Services/Services%20add%20Middleware.png) - [[wiki/concepts/source/extension-metapatterns/proxy|*Sidecars*]] of a [[wiki/concepts/source/extension-metapatterns/middleware|*Service Mesh*]] make a virtual layer of [[wiki/concepts/source/basic-metapatterns/layers|shared libraries]] for the [*Microservices*](#microservices) it hosts. ![Scaled services reside on a shared layer of sidecars which is placed on top of a shared mesh engine. All instances of each service access the service's database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Multifunctional%20-%20Service%20Mesh.png) - A [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Database*]] simplifies the initial phases of development and interservice communication and enables the use of *Services* in [[wiki/concepts/source/foundations-of-software-architecture/shared-data|data-centric domains]]. ![The data of individual services is merged into a shared repository.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Services/Services%20to%20Shared%20Database.png) - [[wiki/concepts/source/extension-metapatterns/proxy|*Proxies*]] stand between the system and its clients and take care of shared aspects that otherwise would need to be implemented by every service. ![Generic aspects of services move to a shared proxy.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Services/Services%20add%20Proxy.png) - An [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] is the single place where the high-level logic of all [[wiki/concepts/source/basic-metapatterns/layers|use cases]] resides. ![The application logic is extracted from individual services into a shared orchestrator.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Services/Services%20use%20Orchestrator.png) - Transforming *Services* into a [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]] greatly simplifies their integration. ![The application and data parts of services are separated from the domain logic and merged into system-wide layers, resulting in a Sandwich.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Services/Services%20to%20Sandwich.png) ### Evolutions of individual services Each service starts as either a [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] or as [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] and may undergo the corresponding evolutions: - [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] help to reuse third-party components (e.g. a database), organize the code, support conflicting forces, and the upper layer of the service may [[wiki/concepts/source/foundations-of-software-architecture/orchestration|orchestrate other services]]. - [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]] or [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cell*]] subdivide the code into smaller components and allow for the deployment of multiple teams. - A service may use a [[wiki/concepts/source/extension-metapatterns/proxy|*Load Balancer*]] or a load balancing [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] to scale. Its [[wiki/concepts/source/basic-metapatterns/shards|*instances*]] usually rely on a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Database*]] for persistence. - [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]] or [[wiki/concepts/source/fragmented-metapatterns/layered-services|*CQRS*]] may be used inside a service to improve the performance of its data layer. - [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*CQRS Views*]] or a [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Query Service*]] help reconstruct the state of other services from *event sourcing*. - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] isolates the business logic of the service from external dependencies. - Occasionally, [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]] or [[wiki/concepts/source/implementation-metapatterns/microkernel|*Scripts*]] are used to customize the behavior of a service. ## Summary *Services* deal with large projects by dividing them into subdomain-aligned components of smaller sizes which can be handled by dedicated teams. These may vary in technologies and qualities. However, services have a hard time cooperating in anything, from sharing data to debugging, and come with an innate performance penalty. There are several options halfway between [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] and distributed *Services* that have milder benefits and drawbacks. --- title: "Shards" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Basic metapatterns/Shards.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Basic%20metapatterns/Shards source_license_note: "See namespace README; preserve attribution and source links." --- # Shards > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Basic metapatterns/Shards.md`. ![A diagram for Shards, in abstractness-subdomain-sharding coordinates.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Main/Shards.png) *Attack of the clones.* Solve scalability in the most straightforward manner. Known as: Shards, Instances, Replicas \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\]. Structure: A set of functionally identical subsystems with little or no intercommunication. Type: Implementation. | *Benefits* | *Drawbacks* | | --- | --- | | Good scalability | It’s hard to synchronize the whole system’s state | | Good performance | There is operational effort to deploy or update multiple components | | Improved latency and/or fault tolerance | | References: \[[wiki/concepts/source/appendices/books-referenced|[POSA3]]\] is dedicated to pooling and resource management; \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\] reviews *Shards*, *Replicas*, and *Stateless Instances*; \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\] covers sharding and synchronization of *Replicas* in depth; Amazon promotes full-system sharding, calling it [*Cell-Based Architecture*](https://docs.aws.amazon.com/wellarchitected/latest/reducing-scope-of-impact-with-cell-based-architecture/what-is-a-cell-based-architecture.html). *Shards* are multiple and, in most cases, independent instances of a component or subsystem which the pattern is applied to. They provide scalability, often redundancy, and sometimes locality, at the cost of slicing or duplicating the component’s state (writable data), which obviously does not affect inherently stateless components. Most of this pattern’s specific evolutions look for a way to coordinate shards at the logic or data level. > There is a sibling metapattern, namely [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]], in which instances of a component closely communicate among themselves. The difference between the patterns lies in the strength of interactions: while each *shard* exists primarily to serve its clients, a *Mesh node*’s priority is preserving the *Mesh* itself from falling prey to entropy, making the *Mesh* into a reliable distributed (virtual) layer. Some systems, such as distributed databases, hold the middle ground – their shards or nodes both intercommunicate intensely and execute a variety of client requests. ### Performance A *shard* retains the performance of the original subsystem (a [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] in the simplest case) as long as it runs independently. Any task that involves intershard communication has its performance degraded by data serialization and network latency. And as soon as multiple shards need to synchronize their states you find yourself on the horns of a dilemma: accept the possibility of data inconsistency caused by [write conflicts](https://en.wikipedia.org/wiki/Write%E2%80%93write_conflict) or else kill performance with distributed transactions \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\]. ![Performance of Shards is the best when the request is limited to a single shard and the worst when the state of several shards needs to be synchronized.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Performance/Shards.png) A [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]] (or its derivation, the [[wiki/concepts/source/extension-metapatterns/shared-repository|*Space-Based Architecture*]]) is a common solution to let multiple shards access the same dataset. However, it does not solve the performance vs consistency conflict (which is rooted in the [CAP theorem](https://en.wikipedia.org/wiki/CAP_theorem)) but only encapsulates its complexity inside a ready-made third-party component, making your life easier. ### Dependencies You may need to make sure that all the shards are instances of the same version of your software or at least that their interfaces and contracts are backward- and [forward-compatible](https://en.wikipedia.org/wiki/Forward_compatibility). ### Applicability A *sharded* system features properties of the pattern it replicates (a single-component [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]], local [[wiki/concepts/source/basic-metapatterns/layers|*layered*]] application or distributed [[wiki/concepts/source/basic-metapatterns/services|*Services*]]). Its peculiarities that originate with the *Shards’* scalability, are listed below. *Shards* are good for: - *High or variable load.* You need to scale your service up (and sometimes down). With *Shards* you are not limited to a single server’s CPU and memory. - *Survival of hardware failures.* A bad HDD or failing RAM does not affect your business if there is another running instance of your application. Still, make sure that your [[wiki/concepts/source/extension-metapatterns/proxy|*Load Balancer*]] and Internet connection are replicated as well. - *Improving worst case latency*. If your service suffers from latency spikes, you can run a few replicas of it in parallel, broadcasting every user request to all of them, and returning the fastest response. Adding a single replica turns your p90 into [p99](https://dzone.com/articles/mastering-latency-with-p90-p99-and-mean-response-t). - *Improving locality.* A world-wide business optimizes latency and costs by deploying an instance of its software to a local data center in every region of interest. Web and mobile applications that run on client devices are prominent examples of sharding hidden in plain sight. - [*Canary Release*](https://martinfowler.com/bliki/CanaryRelease.html). It is possible to deploy an instance of your application featuring new code along with the old, stable instances. That tests the update in production. *Shards’* weak point is: - *Shared data.* If multiple instances of your application need to modify the same dataset, that means that none of them properly owns the data, thus you have to rely on an external component (a *data layer*, implying [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] and [[wiki/concepts/source/extension-metapatterns/shared-repository|*Space-Based Architecture*]] or another kind of [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]]). ### Relations ![Scaling a single service or the entire system.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Relations/Shards.png) *Shards*: - Applies to a [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] or any kind of subsystem. - Can be extended with [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]], [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]], [[wiki/concepts/source/extension-metapatterns/proxy|*Proxies*]] or [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]]. - Is the foundation for [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]]. ## Variants by isolation There are intermediate steps between a single-threaded component and distributed *Shards* which gradually augment the pros and cons of having multiple instances of a subsystem: ### Multithreading The first and very common advance towards scaling a component is running multiple execution threads. That attempts to utilize all the available CPU cores or memory bandwidth but [requires](http://ithare.com/multi-threading-at-business-logic-level-is-considered-harmful/) protecting the data from simultaneous access from several threads, which in turn may cause deadlocks. | *Benefits* | *Drawbacks* | | --- | --- | | Limited scalability | More complex data access | ### Multiple processes The next stage is running several (usually single-threaded) instances of the component on the same system. If one of them crashes, others survive. However, sharing data among them and debugging multi-instance scenarios becomes non-trivial. | *Benefits* | *Drawbacks* | | --- | --- | | Limited scalability | Non-trivial shared data access | | Software fault isolation | Troublesome multi-instance debugging | ### Distributed instances Finally, instances of the subsystem may be distributed over a network to achieve nearly unlimited scalability and fault tolerance by [sacrificing](https://en.wikipedia.org/wiki/CAP_theorem) the consistency of the whole system’s state. | *Benefits* | *Drawbacks* | | --- | --- | | Full scalability | No shared data access | | Full fault isolation | Hard multi-instance debugging | | | No good way to synchronize states of the instances | ## Examples Sharding can often be transparently applied to individual components of [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|data processing]] systems. That does not hold for [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|control systems]] which make centralized decisions based on the modeled system’s state, which must be accessible as a whole, thus the main business logic that owns the model (last known state of the system) cannot be sharded. Many kinds of *Shards* require an external coordinating component ([[wiki/concepts/source/extension-metapatterns/proxy|*Load Balancer*]]) to assign tasks to the individual instances. In some cases the coordinator may be implicit, e.g. an OS socket or scheduler. In others it may be replicated and co-located with each client (as an [[wiki/concepts/source/extension-metapatterns/proxy|*Ambassador*]]). Shards usually don’t communicate with each other directly. The common exception is [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]] which includes distributed databases and [[wiki/concepts/source/implementation-metapatterns/mesh|*actor*]] systems that explicitly rely on communication between the instances. There are several subtypes of sharding that differ in the way they handle state: - [*Shards* or *Partitions*](#persistent-slice-sharding-shards-partitions-multitenancy-cells-amazon-definition) are long-lived service instances which own unique subsets of the system’s data. - [*Replicas*](#persistent-copy-replica) are also long-lived but all of them hold copies of the same dataset. - [*Stateless Instances*](#stateless-pool-instances-replicated-load-balanced-services-work-queue-lambdas) reset their state after handling a request. - [*Actors*](#temporary-state-create-on-demand-actors) are stateful but exist only for the duration of a client’s session. ### Persistent slice: Sharding, Shards, Partitions, Multitenancy, Cells (Amazon definition) ![A sharding proxy connects a new client to a shard that contains data for the first letter of the client's name.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Shards%20-%20Sharding.png) *Shards* \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\] own non-overlapping slices of the system’s state. For example, a sharded phonebook (or DNS) would use one shard for all contacts with initial “A”, another shard for contacts with initial “B”, and so on (in reality they use hashes \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\]). A large wiki or forum may run several servers, each storing a subset of the articles. This is proper [*sharding*](https://learn.microsoft.com/en-us/azure/architecture/patterns/sharding), which is also called *partitioning* \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\] in the world of databases. > Names are not evenly distributed across the letters of the alphabet. Many names start with A but few start with Q. If we use the first letter of a user’s name to assign them to a shard, the shard that serves users whose names start with A will be much more loaded than the one responsible for the letter Q. Therefore, real-world systems rely on *hashing* \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\] – calculation of a *checksum* of the user’s name which yields a seemingly random number. Then we divide the checksum by the total number of shards we have and use the remainder as the id of the shard that has the user’s data. For example, CRC16(“Bender”) = 52722. If we have 10 shards, Bender goes to (52722 % 10 = 2) the 3rd one. Another use of *Shards* is when a service provider allocates a whole shard to each of its clients to grant them data isolation, stable performance, and security. This approach is contrasted against a cheaper option of [*Multitenancy*](https://en.wikipedia.org/wiki/Multitenancy) where several client organizations (tenants) share a shard. In that case different shards may be customized to vary in functionality, available resources, and [SLA](https://en.wikipedia.org/wiki/Service-level_agreement) to provide better service to higher-paying tenants. *Cells*, according to the [Amazon terminology](https://docs.aws.amazon.com/wellarchitected/latest/reducing-scope-of-impact-with-cell-based-architecture/what-is-a-cell-based-architecture.html), are copies of a whole system deployed to several data centers, each serving local users. The locality improves latency and saves on Internet traffic while having multiple instances of the system up and running provides availability. The downside of this approach is its complexity and the amount of global traffic needed to keep the *Cells* in sync. It usually takes a stand-alone [[wiki/concepts/source/extension-metapatterns/proxy|*Sharding Proxy*]] – a kind of *Load Balancer* – to route a client’s requests to the shard that owns its data. However, there are other options \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\]: - The *Sharding Proxy* may be deployed to each client as a client-side [[wiki/concepts/source/extension-metapatterns/proxy|*Ambassador*]] to avoid the extra network hop. This approach requires a means for keeping the *Ambassadors* up-to-date with your system’s code. - You can publish your *sharding function* \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\] and the number of shards in your public API to let your clients choose which shard to access without your help. That may work for internal clients implemented by your own or a neighbor team. - Finally, each shard may be able to forward client requests to any other shard – making each shard into a kind of *Sharding Proxy* and an entry point into the resulting [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]]. If your client accesses a wrong shard, the request is still served, though a little slower, through being forwarded between the shards \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\]. *Sharding* solves scaling of an application both in regard to the number of its clients and to the size of its data. However, it works well only if each client’s data is independent from other clients. Moreover, if one of the shards crashes, the information it owns becomes unavailable unless [*replication*](#persistent-copy-replica) (see below) has been set up as well. ### Persistent copy: Replica ![A load balancer connects a new client to a free replica which propagates the changes made by the client to other replicas.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Shards%20-%20Replica.png) *Replicas* \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\] are identical copies of a stateful (sub)system. Replication improves the system’s throughput (as each replica serves client requests) and its stability (as a fault in one replica does not affect others which may quickly take up the failed replica’s clients). Replicas may also be used to improve tail latency through [*Request Hedging*](https://grpc.io/docs/guides/request-hedging/): each request is sent to several replicas in parallel and the first response received is returned to the client. Mission-critical hardware [runs as three copies](https://en.wikipedia.org/wiki/Triple_modular_redundancy) and relies on majority voting for computation results. The hard part comes from the need to keep the replicas’ data in sync. The ordinary way is to let the replicas talk to each other on each data update. If the communication is synchronous that may greatly slow down the processing of requests, yet if it is asynchronous the system suffers data conflicts when multiple clients change the same value simultaneously. Synchronization code is quite complex, thus you will likely use a ready-made [[wiki/concepts/source/implementation-metapatterns/mesh|*Space-Based Architecture*]] framework instead of writing one of your own. Another option found in the field is keeping the replicas only loosely identical. That happens when isolated cache servers make a [[wiki/concepts/source/extension-metapatterns/proxy|*Caching Layer*]]. As clients tend to send similar requests, the data inside each *cache* is more or less the same by the law of large numbers. And if your traffic is read-heavy, you may turn to [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]] by segregating your replicas into the roles of a fully-functional *leader* \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\] and [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|derived, read-only *followers*]]. The followers serve only the read requests while the leader processes the write requests which make it update its data and broadcast the changes to all its followers. And if the leader dies, one of its followers is elected to become a new leader. As a refinement of this idea, the code of the service itself may be separated into write (*command*) and read (*query*) services (see [[wiki/concepts/source/fragmented-metapatterns/layered-services|*Command Query Responsibility Segregation*]] aka *CQRS*). Finally, you can mix sharding and replication to make sure that the data of each shard is replicated, either in whole among identical components \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\] or piecemeal all over the system \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\]. That achieves fault tolerance for volumes of data too large to store unsharded. ### Stateless: Pool, Instances, [[wiki/concepts/source/extension-metapatterns/sandwich|Replicated Load-Balanced Services]], Work Queue, Lambdas ![A load balancer connects a new client to a free instance of a stateless backend that accesses a database shared among all the backend instances.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Shards%20-%20Pool.png) A predefined number (*pool* \[[wiki/concepts/source/appendices/books-referenced|[POSA3]]\]) of instances (*workers*) is created during the initialization of the system (sometimes called a *Work Queue* \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\]). When the system receives a task, a [[wiki/concepts/source/extension-metapatterns/proxy|*Load Balancer*]] assigns it to one of the idle instances, called *Replicated Load-Balanced Services* \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\], from the pool. As soon as the instance finishes processing its task it returns to the pool and its state is reset. A well-known example of this pattern is [FastCGI](https://en.wikipedia.org/wiki/FastCGI). This approach allows for rapid allocation of a worker to any incoming task, but it uses a lot of resources even when there are no requests to serve, yet the system may still be overwhelmed at peak load. Moreover, a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]] (database or file storage) is usually involved for the sake of persistence, further limiting the pattern’s scalability. Many cloud services implement *dynamic* pools, the number of instances ([*lambdas*](https://jesseduffield.com/Notes-On-Lambda/)) growing and shrinking according to the overall load: if all the current instances are busy serving user requests, new instances are created and added to the pool. If some of the instances are idle for a while, they are destroyed. Dynamic pooling is often implemented through [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]], as in [[wiki/concepts/source/implementation-metapatterns/mesh|*Microservices*]] or [[wiki/concepts/source/implementation-metapatterns/mesh|*Space-Based Architecture*]]. ### Temporary state: Create on Demand, [[wiki/concepts/source/basic-metapatterns/services|Actors]] ![A new stateful instance is created when a new client connects to the system.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Shards%20-%20Create%20on%20Demand.png) An instance is created for supporting an incoming client session and is destroyed when the session is closed. Upon creation it is initialized with all the client-related data which makes the instance able to interact with its client without much help from the backend. Examples include web applications that run in users’ browsers and user-dedicated [[wiki/concepts/source/basic-metapatterns/services|*actors*]] in backends of instant messengers. This approach provides responsiveness, perfect elasticity, and flexibility of deployment at the cost of slower session establishment and it usually relies on an external shared layer for persistence: instances of a frontend are initialized from and send their updates to a backend which itself uses a database. ## Evolutions There are two kinds of evolutions for *Shards*: those intrinsic to the component sharded and those specific to the *Shards* pattern. All of them are summarized below while [[wiki/concepts/source/appendices/evolutions-of-architectures|Appendix E]] provides more details on the second kind. ### Evolutions of a sharded monolith When *Shards* are applied to a single component, which is a [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]], the resulting (sub)system follows most of the [[wiki/concepts/source/basic-metapatterns/monolith|evolutions of *Monolith*]]: - [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] allow for the parts of the system to [[wiki/concepts/source/foundations-of-software-architecture/forces-asynchronicity-and-distribution|differ]] in *qualities*, technologies, and [[wiki/concepts/source/foundations-of-software-architecture/forces-asynchronicity-and-distribution|deployment]]. Various third-party components can be integrated and the code becomes better structured. - [[wiki/concepts/source/basic-metapatterns/services|*Services*]] or [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]] help to distribute the work among multiple teams and may decrease the project’s complexity if the division results in loosely coupled components. - [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]] and its subtypes, namely [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] and [[wiki/concepts/source/implementation-metapatterns/microkernel|*Scripts*]], make the system more adaptable. ![Diagrams of scaled Layers, Services with a middleware, Pipeline, Plugins, Hexagonal Architecture, and Scripts.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Shards/Shards%20-%20General.png) There is a benefit of such transformations which is important in the context of *Shards*: in many cases the resulting components can be scaled independently, arranging for a better resource utilization by the system when compared to scaling a *Monolith*. However, scaling individual services usually requires a [[wiki/concepts/source/extension-metapatterns/proxy|*Load Balancer*]] or [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] to distribute requests among the scaled instances. ### [[wiki/concepts/source/appendices/evolutions-of-shards-that-share-data|Evolutions that share data]] The issue peculiar to *Shards* is that of coordinating deployed instances, especially if their data becomes coupled. The most direct solution is to let every instance access the shared data: - If the whole dataset needs to be shared, it can be split into a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]] layer. ![The data of shards moves to a shared database. The shards become stateless and are deployed behind a load balancer.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Shards/Shards%20to%20Shared%20DB.png) - If data collisions are tolerated, [[wiki/concepts/source/extension-metapatterns/shared-repository|*Space-Based Architecture*]] promises low latency and dynamic scalability. ![The data of the shards moves to a Data Grid, resulting in a Space-Based Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Shards/Shards%20to%20Space-Based%20Architecture.png) - If a part of the system’s data becomes coupled, only that part can be moved to a *Shared Repository*, making each instance manage [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|two stores of data: private and shared]]. ![A coupled subset of the system's data is stored in a shared repository, while the bulk of the data is sharded.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Shards/Shards%20add%20Shared%20DB.png) - Another possible option is to split a [[wiki/concepts/source/basic-metapatterns/services|service]] that owns the coupled data and is always deployed as a single instance. The remaining parts of the system become coupled to that service, not to each other. ![Coupled business logic and data is separated from shards into a shared singletone service.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Shards/Shards%20split%20Shared%20Service.png) ### [[wiki/concepts/source/appendices/evolutions-of-shards-that-share-logic|Evolutions that share logic]] Other cases are better solved by extracting the logic that couples the shards: - Splitting a [[wiki/concepts/source/basic-metapatterns/services|service]] (as discussed above) yields a component that represents both shared data and shared logic. - Adding a [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] lets the shards communicate with each other without maintaining direct connections. It also may do housekeeping: error recovery, replication, and scaling. ![A middleware manages shards and lets them communicate to each other.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Shards/Shards%20add%20Middleware.png) - A [[wiki/concepts/source/extension-metapatterns/proxy|*Sharding Proxy*]] hides the shards from the system’s clients. ![A sharding proxy relieves clients from the need to find the appropriate shard.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Shards/Shards%20add%20Load%20Balancer.png) - An [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] calls multiple shards to serve a user request. That relieves the shards of the need to coordinate their states and actions by themselves. ![The high-level logic of shards moves to a shared orchestrator which integrates the data stored within and processed by individual shards.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/Shards/Shards%20use%20Orchestrator.png) ## Summary *Shards* are multiple instances of a component or subsystem which is thus made scalable and more fault tolerant. *Sharding* does not change the nature of the component it applies to and it usually relies on a [[wiki/concepts/source/extension-metapatterns/proxy|*Load Balancer*]] or [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] to manage the instances it spawns. Its main weakness appears when the *shards* need to intercommunicate, often to the end of synchronizing their data. --- title: "Extension metapatterns" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Extension metapatterns/Extension metapatterns.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Extension%20metapatterns/Extension%20metapatterns source_license_note: "See namespace README; preserve attribution and source links." --- # Extension metapatterns > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Extension metapatterns/Extension metapatterns.md`. These patterns extend [[wiki/concepts/source/basic-metapatterns/services|*Services*]], [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]], or even a [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] with a layer that provides an aspect or two of the system’s behavior and often glues other components together. ### [[wiki/concepts/source/extension-metapatterns/middleware|Middleware]] ![A diagram of Services with a middleware, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Middleware.png) A [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] is a layer which provides communication with instances of the system’s components and it may also manage those instances. This way each instance is relieved of the need to track the other instances which it accesses. *Includes*: (Message) Broker and Deployment Manager; Message Bus, Event Mediator, Enterprise Service Bus, and Service Mesh. ### [[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Repository]] ![A diagram of Services with a shared repository, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Shared%20Repository.png) A [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]] stores the system’s data, maintains its integrity through transactions, and may support subscriptions to changes in subsets of the data. That lets other system components concentrate on implementing the business logic. *Includes*: Shared Database, Blackboard, Data Grid of Space-Based Architecture, Shared Memory, and Shared File System. ### [[wiki/concepts/source/extension-metapatterns/proxy|Proxy]] ![A diagram of Services with a proxy, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Proxy.png) A [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]] mediates between a system and its clients, transparently taking care of some generic functionality. *Includes*: Full Proxy and Half-Proxy; Sidecar and Ambassador; Firewall, Response Cache, Load Balancer, Reverse Proxy and various Adapters, e.g. Anticorruption Layer, Open Host Service, many Abstraction Layers, Repository, and even User Interface. ### [[wiki/concepts/source/extension-metapatterns/orchestrator|Orchestrator]] ![A diagram of Services with an orchestrator, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Orchestrator.png) An [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] implements use cases as sequences of calls to the underlying components which are usually left unaware of each other’s existence. *Includes*: Workflow Owner, Application Layer, Facade, Mediator; API Composer, Scatter-Gather, MapReduce, Process Manager, Saga Execution Component, and Integration (Micro-)Service. ### [[wiki/concepts/source/extension-metapatterns/sandwich|Sandwich]] ![A diagram of Sandwich Architecture, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Sandwich.png) [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]] subdivides the largest and loosely coupled [[wiki/concepts/source/basic-metapatterns/layers|*domain* layer]] into modules or services while the other layers remain monolithic. *Includes*: Service-Based Architecture, Space-Based Architecture, Blackboard Architecture, Nanoservices, and Command Query Responsibility Segregation (CQRS). --- title: "Middleware" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Extension metapatterns/Middleware.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Extension%20metapatterns/Middleware source_license_note: "See namespace README; preserve attribution and source links." --- # Middleware > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Extension metapatterns/Middleware.md`. ![A diagram for Services with a middleware, in abstractness-subdomain-sharding coordinates.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Main/Middleware.png) *The line between disorder and order lies in logistics.* Use a shared transport. Known as: (Distributed) Middleware. Structure: A low-level layer that provides connectivity. Type: Extension component. | *Benefits* | *Drawbacks* | | --- | --- | | Separates connectivity concerns from the services that use it | May become a single point of failure | | Transparent scaling of components | May increase latency | | Available off the shelf | A generic *Middleware* may not fit specific communication needs | References: \[[wiki/concepts/source/appendices/books-referenced|[EIP]]\] has a lot of content on the implementation of messaging *Middleware*. \[[wiki/concepts/source/appendices/books-referenced|[POSA4]]\] features a chapter on *Middleware*. However, those books are old. \[[wiki/concepts/source/appendices/books-referenced|[DEDS]]\] is about Kafka, but it goes too far advertising it as a [*Shared Event Store*](#persistent-event-log-shared-event-store). There is also a [Wikipedia article](https://en.wikipedia.org/wiki/Middleware_(distributed_applications)). Extracting transport into a separate layer relieves the components that implement business logic of the need to know the addresses and statuses of each other’s instances. An industrial-grade, third-party *Middleware* is likely to be more stable and provide better error recovery than anything an average company can afford to implement on its own. A *Middleware* may function as: - A *Message Broker* \[[wiki/concepts/source/appendices/books-referenced|[POSA1]], [[wiki/concepts/source/appendices/books-referenced|POSA4]], [[wiki/concepts/source/appendices/books-referenced|EIP]], [[wiki/concepts/source/appendices/books-referenced|MP]]\] which provides a unified means of communication and implements some cross-cutting concerns like the persisting or logging of messages. - A *Deployment Manager* \[[wiki/concepts/source/appendices/books-referenced|[SAP]], [[wiki/concepts/source/appendices/books-referenced|FSA]]\] which collects telemetry and manages service instances for error recovery and dynamic scaling. As *Middleware* is ubiquitous and does not affect business logic, it is usually omitted from structural diagrams. ### Performance A *Middleware* may negatively affect performance when compared to direct communication between services. Old implementations (star topology) relied on a *Broker* \[[wiki/concepts/source/appendices/books-referenced|[POSA1]], [[wiki/concepts/source/appendices/books-referenced|POSA4]], [[wiki/concepts/source/appendices/books-referenced|EIP]]\] that used to add an extra network hop for each message and limited scalability. Newer [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]]-based variants avoid those drawbacks but are very complex and may have consistency issues (according to the [CAP theorem](https://en.wikipedia.org/wiki/CAP_theorem)). A more subtle drawback is that transports which a *Middleware* supports or uses by default may be suboptimal for some of the interactions in your system, causing programmers to hack around the limitations or build higher-level protocols on top of your *Middleware*. Both cases can be ameliorated by adding means for direct communication between the services to bypass the *Middleware* or by using multiple specialized kinds of *Middleware*. However, that adds to the complexity of the system – the very issue the *Middleware* promised to help with. ### Dependencies Each service depends both on the *Middleware* and on the API of every service it communicates with. ![Each service depends on the middleware and on every service which it uses.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Dependencies/Middleware.png) You may decide to use an [[wiki/concepts/source/extension-metapatterns/proxy|*Anticorruption Layer*]] over your *Middleware* just in case you may need to change its vendor in the future. That will trade performance for flexibility. ### Applicability *Middleware* helps: - *Multi-component systems.* When a service has several instances deployed which need to be accessed, its clients must know the addresses of (or have channels to) its instances and use an algorithm for instance selection. As the number of services grows, so does the amount of information about the instances of other services that each service needs to track. Even worse, services sometimes crash or are being redeployed, requiring complicated algorithms that queue messages to deliver them in order once the recipient service returns to life. It makes all the sense in the world to use a dedicated component to take care of all of that. - *Dynamic scaling.* It is good to have a single, even if virtual, component that manages routes for interservice messaging to account for newly deployed (or destroyed) service instances. - [*Blue-green deployment*](https://martinfowler.com/bliki/BlueGreenDeployment.html)*,* [*canary release*](https://martinfowler.com/bliki/CanaryRelease.html)*, or* [*dark launching*](https://martinfowler.com/bliki/DarkLaunching.html) *(traffic mirroring)*. It is easier to switch, whether fully or in part, to a new version of a service when the communication is centralized. - *System stability.* Most implementations of *Middleware* guarantee message delivery with unstable networks and failing components. Many persist messages to be able to recover from failures of the *Middleware* itself. *Middleware* hurts: - *Critical real-time paths.* An extra layer of message processing is bad for latency. Such messages may need to bypass the *Middleware*, likely via pre-established message channels. ### Relations ![Middleware for Services, Shards, and Service-Oriented Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Relations/Middleware.png) *Middleware*: - Extends [[wiki/concepts/source/basic-metapatterns/services|*Services*]], [*Service-Oriented Architecture*]() or, in rare cases, [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]] or [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]]. - Can participate in a [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Bus of Buses*]] ([[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchy*]]) or be merged with other *extension metapatterns*. - Is closely related to [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]]. A persistent *Middleware* employs a *Shared Repository*. - Is usually implemented by a [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]] or [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]] (which is often based on a *Mesh*). ## Variants by functionality There are several dimensions of freedom with a *Middleware*, some of them may be configurable: ### By addressing (channels, [[wiki/concepts/source/implementation-metapatterns/mesh|actors]], pub/sub, gossip) Systems vary in the way their components address each other: - *Channels* (one to one) – components which need to communicate establish a message channel between them. Once created, the channel accepts messages or a data stream on its input end and transparently delivers them to its output end. Examples: sockets, Unix pipes, and private chats. - *Actors* (many to one) – each component has a public mailbox. Any other component that knows its address or name may push a message into it. Examples: e-mail. - *Publish/subscribe* (one to many) – a component can publish events to topics. Other components subscribe to those topics and one or all of the subscribers receive copies of a published event. Examples: IP multicast and subscriptions for e-mail notifications. - [*Gossip*](https://highscalability.com/gossip-protocol-explained/) (many to many) – an instance of a component periodically synchronizes its version of the system's state with random other instances, which further spread the updates. As time passes, more and more participants become aware of the changes in the system, leading to eventual consistency of the [[wiki/concepts/source/basic-metapatterns/shards|*replicas*]] of the system's state. ### By flow (notifications, request/confirm, RPC) Control flow may take one or more of the following approaches: - *Notifications* – a component sends a message about an event that occurred and does not really care about the further consequences or wait for a response. - *Request/confirm* – a component sends a message which requests that another component does something and sends back the result. The sender may execute other tasks meanwhile. A request usually includes a unique id that will be added to the confirmation for the *Middleware* to know which of the requests in progress the confirmation belongs to. - *Remote procedure call* (RPC) is usually built on top of a request/confirm protocol. The difference is that the sender blocks on the *Middleware* while waiting for the confirmation, thus to the application code the whole process of sending the request and waiting for the confirmation looks like a single method call. ### By delivery guarantee If the transport (network) or the destination fails, a message may not be processed, or may be processed twice because of retries. A *Middleware* may [promise to deliver messages](https://blog.bytebytego.com/p/at-most-once-at-least-once-exactly): - *Exactly once*. This is the slowest case which is [implemented through distributed transactions](https://docs.confluent.io/kafka/design/delivery-semantics.html). If the network, *Middleware* itself, or the message handler fails, there is no side effect, and the whole process of delivering and executing the message is repeated. The *exactly once* contract is used for financial systems and accounting where money should never disappear or duplicate during a transfer. - *At least once*. On failure the message is redelivered, but the previous message could have already been processed (if only the confirmation was lost), thus there is a chance for a message to be processed twice. If the message is *idempotent* \[[wiki/concepts/source/appendices/books-referenced|[MP]]\], meaning that it sets a value (x = 42) instead of incrementing or decrementing it (x = x \+ 2), then we can more or less safely process it multiple times (x = 42; x = 42; x = 42;) and use the relatively fast *at least once* guarantee. - *At most once*. If anything fails, the message is lost and never retried. This is the fastest of the three guarantees, suitable for such monitoring applications as weather sensors – it is not too bad if a single temperature measurement disappears when you receive hundreds of them every day. - *At will* (no guarantee). As with the bare [UDP transport](https://en.wikipedia.org/wiki/User_Datagram_Protocol#Reliability_and_congestion_control), a message may disappear, become duplicated, or arrive out of order. That fits real-time streaming protocols (video or audio calls) where it is acceptable to skip a frame while a frame coming too late is of no use at all. Each frame contains its sequence number, and it is up to the application to reorder and deduplicate the frames it receives. ### By persistence A *Middleware* with a delivery guarantee needs to store messages whose delivery has not yet been confirmed. They may be: - Written to a data store of the *broker*. - Persisted in a distributed data store in brokerless ([[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]]) systems. - Replicated over an in-memory *Mesh* storage (like [[wiki/concepts/source/extension-metapatterns/shared-repository|*Data Grid*]]). If the messages are stored indefinitely, the *Middleware* [becomes](#persistent-event-log-shared-event-store) a *Persistent* [*Event Log*](https://medium.com/sundaytech/event-sourcing-audit-logs-and-event-logs-deb8f3c54663) or even a *Shared* [*Event Store*](https://cloudnative.ly/event-driven-architectures-edas-vs-event-sourcing-c8582578e87) with the schemas of the stored events coupling the involved services \[[wiki/concepts/source/appendices/books-referenced|[DEDS]]\] just like the database schema does in [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]]. ### By structure ([[wiki/concepts/source/implementation-metapatterns/microkernel|Microkernel]], [[wiki/concepts/source/implementation-metapatterns/mesh|Mesh]], Broker) ![In middleware each service is co-located with a generic component which may either be a node of a mesh or forward the service's requests to a centralized broker as a proxy.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Middleware%20-%20Structure.png) A *Middleware* may be: - Implemented by an underlying operating or virtualization system (see [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]]). - Run as a [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]] of identical modules co-deployed with the distributed components in their [[wiki/concepts/source/extension-metapatterns/proxy|*Sidecars*]]. - Rely on a single *broker* \[[wiki/concepts/source/appendices/books-referenced|[POSA1]], [[wiki/concepts/source/appendices/books-referenced|POSA4]], [[wiki/concepts/source/appendices/books-referenced|EIP]]\] for coordination. The last configuration is simpler but features a single point of failure unless multiple instances of the broker are deployed and kept synchronized. ## Examples There are several patterns which extend *Middleware* with other functions: - A [*Persistent Event Log*](#persistent-event-log-shared-event-store) allows for replaying past events. - A [*Service Mesh*](#service-mesh) adds a *Sidecar* with shared libraries to every service instance it hosts. - A [*Message Bus*](#message-bus) uses *Adapters* to support a variety of protocols. - An [*Event Mediator*](#event-mediator) orchestrates request processing for the services it connects. - An [*Enterprise Service Bus*](#enterprise-service-bus-esb) both supports multiple protocols and orchestrates request processing. ### [[wiki/concepts/source/extension-metapatterns/shared-repository|Persistent Event Log, Shared Event Store]] ![Both persistent event log and shared event store merge the functionality of middleware and shared repository.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Middleware%20-%20Shared%20Event%20Store.png) When a *Middleware* persists messages, it takes on the function (and drawbacks) of a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]]. A *Persistent Event Log* allows to replay events incoming to a given service (to help a debug session or fix data corrupted by a bug) while a *Shared Event Store* also captures changes of the internal states of the services \[[wiki/concepts/source/appendices/books-referenced|[DEDS]]\], thus [replacing their private databases](https://cloudnative.ly/event-driven-architectures-edas-vs-event-sourcing-c8582578e87). However, with either approach, changing an event field impacts all the services that use the event and may involve rewriting the entire event log (the system’s history) \[[wiki/concepts/source/appendices/books-referenced|[DEDS]]\]. ### [[wiki/concepts/source/implementation-metapatterns/mesh|Service Mesh]] ![Multiple instances of several services connected to their sidecars which are connected to a shared mesh engine. Instances of each service access its single database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Microservices.png) *Service Mesh* \[[wiki/concepts/source/appendices/books-referenced|[FSA]], [[wiki/concepts/source/appendices/books-referenced|MP]]\] is a smart [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]]-based *Middleware* that manages service instances and employs at least one co-located [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]] (called [[wiki/concepts/source/extension-metapatterns/proxy|*Sidecar*]]) per service instance deployed. The *Sidecars* may provide protocol translation and cover cross-cutting concerns such as encryption or logging. They make a good place for shared libraries. The internals of [[wiki/concepts/source/implementation-metapatterns/mesh|*Service Mesh*]] are discussed in the [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh* chapter]]. ### Message Bus ![A message bus has an adapter per service to allow each service to use its own protocol.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Message%20Bus.png) A *Message Bus* \[[wiki/concepts/source/appendices/books-referenced|[EIP]]\] employs one or more [[wiki/concepts/source/extension-metapatterns/proxy|*Adapters*]] per service to let the services intercommunicate even if they differ in protocols. That helps to integrate legacy services without major changes to their code but it degrades the overall performance as up to two protocol translations per message are involved. ### [[wiki/concepts/source/extension-metapatterns/orchestrator|Event Mediator]] ![An event mediator calls event processors one by one.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Event%20Mediator.png) *Event Mediator* \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\], which pervades both [[wiki/concepts/source/basic-metapatterns/pipeline|*Event-Driven Architecture*s]] and [[wiki/concepts/source/basic-metapatterns/pipeline|*Nanoservices*]], combines a *Middleware* (used for the delivery of messages) and an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] (that coordinates high-level use cases). A message arrives to a service and is responded to without any explicit support on the service’s side – it appears *out of thin Middleware* which implements the entire integration logic. Slightly more details on the *Event Mediator* are [[wiki/concepts/source/extension-metapatterns/orchestrator|provided in the *Orchestrator* chapter]]. ### [[wiki/concepts/source/extension-metapatterns/orchestrator|Enterprise Service Bus]] (ESB) ![An Enterprise Service Bus is between the fragmented task and entity layers of Service-Oriented Architecture. It mediates all calls and messages between the system components.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Enterprise%20Service%20Bus.png) [*Enterprise Service Bus*](https://www.confluent.io/learn/enterprise-service-bus/) (*ESB*) \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] is a mixture of *Message Bus* and *Event Mediator*. A *ESB* blends a *Middleware* and an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] and adds an [[wiki/concepts/source/extension-metapatterns/proxy|*Adapter*]] per service as a topping. It emerged to connect components that originated in incompatible networks of organizations that had been acquired by a corporation. See the [chapter about *Service-Oriented Architecture*](). ## [[wiki/concepts/source/appendices/evolutions-of-a-middleware|Evolutions]] A *Middleware* is unlikely to be removed (though it may be replaced) once it is built into a system. There are [[wiki/concepts/source/appendices/evolutions-of-a-middleware|few evolutions for *Middleware*]] because it is usually a third-party product and thus unlikely to be modified in-house: - If the *Middleware* in use does not fit the preferred mode of communication between some of your services, there is the option to deploy a second, specialized *Middleware*. ![A specialized middleware added to a system that already has a generic middleware.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Middleware%20add%20Middleware.png) - If several existing systems need to be merged, that is accomplished by adding yet another layer of *Middleware*, resulting in a [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Bottom-up Hierarchy*]] *(Bus of Buses)*. ![A low-level middleware interconnects several higher-level middlewares.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Middleware%20to%20Bus%20of%20Buses.png) ## Summary A *Middleware* is a ready-to-use component that provides a system of services with means of communication, scalability, and error recovery. It is very common in distributed backends. --- title: "Orchestrator" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Extension metapatterns/Orchestrator.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Extension%20metapatterns/Orchestrator source_license_note: "See namespace README; preserve attribution and source links." --- # Orchestrator > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Extension metapatterns/Orchestrator.md`. ![A diagram for Services with an orchestrator, in abstractness-subdomain-sharding coordinates.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Main/Orchestrator.png) *One ring to rule them all.* Make a component to integrate other components. Known as: Orchestrator \[[wiki/concepts/source/appendices/books-referenced|[MP]], [[wiki/concepts/source/appendices/books-referenced|FSA]]\], Orchestrated Services, Service Layer \[[wiki/concepts/source/appendices/books-referenced|[PEAA]]\], [[wiki/concepts/source/basic-metapatterns/layers|Application Layer]] \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\], Wrapper Facade \[[wiki/concepts/source/appendices/books-referenced|[POSA4]]\], Multi-Worker \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\], Control, Workflow Owner \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] of [[wiki/concepts/source/basic-metapatterns/services|Microservices]], and Processing Grid \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] of [[wiki/concepts/source/extension-metapatterns/sandwich|Space-Based Architecture]]. Structure: A layer of high-level business logic built on top of lower-level services. Type: Extension component. | *Benefits* | *Drawbacks* | | --- | --- | | Separates integration concerns from the services – decouples the services’ APIs | May increase latency for global use cases | | Global use cases can be changed and deployed independently from the services | Qualities of the services become coupled to an extent | | Decouples the services from the system’s clients | API design is an extra step before implementation | References: \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] discusses orchestration in its chapters on *Event-Driven Architecture*, *Service-Oriented Architecture*, and *Microservices*. \[[wiki/concepts/source/appendices/books-referenced|[MP]]\] describes orchestration-based *Sagas* and its Order Service acts as an *Application Service* without explicitly naming the pattern. \[[wiki/concepts/source/appendices/books-referenced|[POSA4]]\] defines several variants of *Facade*. An *Orchestrator*, named after the person that assigns musical parts in an orchestra, takes care of global use cases (those involving multiple services) thus allowing each service to specialize in its own subdomain and, ideally, forget about the existence of all the other services. This way the entire system’s high-level logic (which is subject to frequent changes) is kept (and deployed) together, isolated from usually more complex subdomain-specific services. Dedicating a [[wiki/concepts/source/basic-metapatterns/layers|layer]] to global scenarios makes them relatively easy to implement and debug, while the corresponding development team that communicates with clients shelters the other narrowly-focused teams from disruptions. The cost of employing an *Orchestrator* is both degraded performance when compared to basic [[wiki/concepts/source/basic-metapatterns/services|*Services*]] that rely on [[wiki/concepts/source/foundations-of-software-architecture/choreography|*choreography*]] \[[wiki/concepts/source/appendices/books-referenced|[FSA]], [[wiki/concepts/source/appendices/books-referenced|MP]]\] and some coupling of the properties of the orchestrated services as the *Orchestrator* usually treats every service in the same way. An *Orchestrator* fulfills two closely related roles: - As a [*Mediator*](https://refactoring.guru/design-patterns/mediator) \[[wiki/concepts/source/appendices/books-referenced|[GoF]], [[wiki/concepts/source/appendices/books-referenced|SAHP]]\] it keeps the states of the underlying components consistent by propagating changes that originate in one component to the rest of the system. This role is prominent in [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|*control* software]], pervading automotive, aerospace, and IoT industries. The *Mediator* role also emerges as [*Saga*](#orchestrated-saga-saga-orchestrator-saga-execution-component-transaction-script-coordinator). - As a [*Facade*](https://refactoring.guru/design-patterns/facade) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] it builds high-level scenarios out of smaller steps provided by the services or modules it controls. This role is obvious for [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|*processing* systems]] where clients communicate with the *Facade*, but it is also featured in *control* software, because sometimes a simple event may trigger a complex multi-component scenario managed by the system’s *Orchestrator*. ![Control flows in a facade and mediator.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Misc/Orchestrator.png) [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|Data *processing*]] systems, such as backends, may deploy multiple [[wiki/concepts/source/basic-metapatterns/shards|instances]] of stateless *Orchestrators* to improve stability and performance. In contrast, an *Orchestrator* in [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|*control* software]] incorporates a high-level view of the system’s state thus it cannot be easily replicated (as any replicated state must be kept synchronized, introducing delay or inconsistency in decision-making). ### Performance When compared to [[wiki/concepts/source/foundations-of-software-architecture/choreography|*choreography*]], [[wiki/concepts/source/foundations-of-software-architecture/orchestration|*orchestration*]] usually worsens latency as it involves extra steps of communication between the *Orchestrator* and orchestrated components. However, the effects should be estimated on case by case basis, as there are exceptions in at least the following cases: - An *Orchestrator* may cache the state of the orchestrated system, gaining the ability to immediately respond to read requests with no need to query the underlying components. This is very common with [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|*control* systems]]. - An *Orchestrator* may persist a write request, respond to the client, and then start the actual processing (similar to the [[wiki/concepts/source/foundations-of-software-architecture/choreography|*early response* optimization]] in choreography). Persistence grants that the request will eventually be completed as it can be restarted. - An *Orchestrator* may run multiple subrequests in parallel, reducing latency compared to a chain of choreographed events. - In a highly loaded or latency-critical system, orchestrated services may establish direct data streams that bypass the *Orchestrator*. A classic example is [VoIP](https://en.wikipedia.org/wiki/Voice_over_IP) where the call establishment logic (SIP) goes through an orchestrating server while the voice or video (RTP) streams directly between the clients. ![Caching, early response, parallel execution, and direct communication between services as optimization techniques for Orchestrated Services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Performance/Orchestrator.png) I don’t see how orchestration can affect throughput as in most cases the *Orchestrator* can be scaled. However, scaling weakens consistency (or requires centralized locking, with a chance for a locked use case to hang if the *Orchestrator* instance which has locked it crashes) as then no instance of the *Orchestrator* has exclusive control over the system’s state. ### Dependencies An *Orchestrator* may depend on the *APIs* of the services it orchestrates or define *SPIs* for them to implement, with the first mode being natural for its [*Facade*](https://refactoring.guru/design-patterns/facade) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] aspect and the second one for the [*Mediator*](https://refactoring.guru/design-patterns/mediator) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\]: ![A facade depends on every service. Contrariwise, every service depends on a mediator.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Dependencies/Orchestrator.png) If an *Orchestrator* is added to integrate existing components, it will use their APIs. In large projects, where each service gets a separate team, the APIs need to be negotiated beforehand, and will likely be owned by the orchestrated services. Smaller (single-team) systems tend to be developed top-down, with the *Orchestrator* being the first component to implement, thus it defines the interfaces it uses. Likewise, [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|control systems]] tend to reverse the dependencies, with their services depending on the orchestrator’s SPI to provide for polymorphism between the low-level components. See the [[wiki/concepts/source/foundations-of-software-architecture/orchestration|chapter on *orchestration*]] for [[wiki/concepts/source/foundations-of-software-architecture/orchestration|more details]]. ### Applicability *Orchestrators* shine with: - *Large projects.* The partition of business logic into a high-level [[wiki/concepts/source/basic-metapatterns/layers|*application*]] (*Orchestrator*) and the multiple [[wiki/concepts/source/basic-metapatterns/services|subdomain *Services*]] it relies on provides perfect code decoupling and team specialization. - *Specialized teams.* As an improvement over [[wiki/concepts/source/basic-metapatterns/services|*Services*]], the teams which develop deep knowledge of subdomains will delegate communication with customers to the application team. - *Complex and unstable requirements*. The *integration* layer (*Orchestrator*) should be high-level and simple enough to be easily extended or modified to cover most of the customer requests or marketing experiments without much help from the domain teams. *Orchestrators* fail in: - *Huge projects.* At least one aspect of complexity is going to hurt. Either the number of the subdomain services and the size of their APIs will make it impossible for an *Orchestrator* programmer to find the correct methods to call, or the *Orchestrator* itself will become unmanageable due to the sheer number and length of its use cases. This can be addressed by dividing the *Orchestrator* into a layer of services (resulting in [*Backends for Frontends*]() or [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Cell-Based Architecture*]]) or multiple layers (often yielding a [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Top-Down Hierarchy*]]). It is also possible to go for the [*Service-Oriented Architecture*]() as that has more fine-grained components. - *Small projects*. The implementation overhead of defining and stabilizing service APIs and the performance penalty of the extra network hop may outweigh the extra flexibility of having the *Orchestrator* as a separate system component. - *Low latency*. Any system-wide use case will make multiple calls between the application (*Orchestrator*) and services, with each interaction adding to the latency. ### Relations ![Orchestrator for a monolith, layers, shards and services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Relations/Orchestrator.png) *Orchestrator*: - Extends [[wiki/concepts/source/basic-metapatterns/services|*Services*]] or, rarely, [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]], [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]], or [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] (forming *Layers*). - Can be merged with a [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]] into an [[wiki/concepts/source/extension-metapatterns/proxy|*API Gateway*]], with a [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] into an [[wiki/concepts/source/extension-metapatterns/middleware|*Event Mediator*]], or with a [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] and [[wiki/concepts/source/extension-metapatterns/proxy|*Adapters*]] into an [[wiki/concepts/source/extension-metapatterns/middleware|*Enterprise Service Bus*]]. - Is a special case (single service) of [*Backends for Frontends*](), [*Service-Oriented Architecture*]() or (2-layer) [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchy*]]. - Is a part of [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]]. - Can be implemented by a [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]]. ## Variants by transparency It seems that an *Orchestrator*, just like a [[wiki/concepts/source/basic-metapatterns/layers|*layer*]], which it is, [[wiki/concepts/source/basic-metapatterns/layers|can be]] *open* (*relaxed*) or *closed* (*strict*): ### Closed or strict ![An orchestrator mediates every request from every client.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Orchestrator%20-%20Closed.png) A *strict* or *closed Orchestrator* isolates the orchestrated services from their users – all the requests go through the *Orchestrator*, and the services don’t need to intercommunicate. ### Open or relaxed ![An orchestrator mediates a multi-step client request while it is transparent to simpler requests.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Orchestrator%20-%20Open.png) An *open Orchestrator* implements a subset of system-wide scenarios that require strict data consistency while less demanding requests go from the clients directly to the underlying services, which rely on [[wiki/concepts/source/foundations-of-software-architecture/choreography|*choreography*]] or [[wiki/concepts/source/foundations-of-software-architecture/shared-data|*shared data*]] for communication. Such a system sacrifices the clarity of design to avoid some of the drawbacks of both *choreography* and *orchestration*: - The orchestrator development team, which may be overloaded or slow to respond, is not involved in implementing the majority of use cases. - Most of the use cases avoid the performance penalty caused by the orchestration. - Failure of the *Orchestrator* does not disable the entire system. - The relaxed *Orchestrator* still allows for synchronized changes of data in multiple services, which is rather hard to achieve with choreography. ## Variants by structure (can be combined) The orchestration ([[wiki/concepts/source/basic-metapatterns/layers|application]] \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\] / [integration](https://github.com/wso2/reference-architecture/blob/master/event-driven-api-architecture.md) / [composite](https://github.com/wso2/reference-architecture/blob/master/event-driven-api-architecture.md)) layer has several structural (implementation) options: ### [[wiki/concepts/source/basic-metapatterns/monolith|Monolithic]] ![An orchestrator communicates with several services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Orchestrator%20-%20Monolythic.png) A single *Orchestrator* is deployed. This option fits ordinary medium-sized projects but fails for anything more demanding as the *Orchestrator* limits throughput, becomes a single point of failure, and may grow too complex for comfortable development. ### [[wiki/concepts/source/basic-metapatterns/shards|Scaled]] ![Multiple instances of a stateless orchestrator are behind a load balancer and they persist their actions into a dedicated shared database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Orchestrator%20-%20Scaled.png) High availability requires multiple [[wiki/concepts/source/basic-metapatterns/shards|instances]] of a stateless *Orchestrator* to be deployed. A *Mediator* ([*Saga*](#orchestrated-saga-saga-orchestrator-saga-execution-component-transaction-script-coordinator), writing *Orchestrator*) may store the current transaction’s state in a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Database*]] to assure that if it crashes there is always another instance ready to take up its job. High load systems also require multiple instances of *Orchestrators* because a single instance is not enough to handle the incoming traffic. > Not all data is made equal. For example, an online store has different requirements for reliability of its buyers’ cart contents and the payments. If the current buyers’ carts become empty when the store’s server crashes, that makes only a minor annoyance (unless it happens repeatedly). However, any financial data loss or a corrupted money transfer is a real trouble. Therefore, an online store may implement its cart with a simple in-memory *Orchestrator*, but should be very careful about the payments workflow, every step of which must be persisted to a reliable database. ### [[wiki/concepts/source/basic-metapatterns/layers|Layered]] ![A simple orchestrator calls services or a complex orchestrator, which also calls the same services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Orchestrator%20-%20Layered.png) \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] describes an option of a layered [*Event Mediator*](#event-mediator). A client’s request comes to the topmost layer of the *Orchestrator* which uses the simplest (and least flexible) framework. If the request is found to be complex, it is forwarded to the second layer which is based on a more powerful technology. And if it fails or requires a human decision then it is forwarded again to the even more complex custom-tailored orchestration layer. That allows the developers to gain the benefits of a high-level declarative language in a vast majority of scenarios while falling back to hand-written code for a few complicated cases. This choice is not free as the programmers need to learn multiple technologies, interlayer debugging is anything but easy, and performance is likely to be worse than that of a monolithic *Orchestrator*. A similar example is using an [*API Composer*](#api-composer-remote-facade-gateway-aggregation-composed-message-processor-scatter-gather-mapreduce) for the top layer, followed by a [*Process Manager*](#process-manager-orchestrator) and a [*Saga Engine*](#orchestrated-saga-saga-orchestrator-saga-execution-component-transaction-script-coordinator). ### A service per client type ([Backends for Frontends]()) ![Each client communicates with its own orchestrator.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Orchestrator%20-%20BFF.png) If your clients strongly differ in workflows (e.g. [OLAP](https://en.wikipedia.org/wiki/Online_analytical_processing) and [OLTP](https://en.wikipedia.org/wiki/Online_transaction_processing), or user and admin interfaces), implementing dedicated *Orchestrators* is an option to consider. That both makes each client-specific *Orchestrator* smaller and more cohesive than the unified implementation would be and gives more independence to the teams responsible for different kinds of clients. This pattern is known as [*Backends for Frontends*]() and has a chapter of its own. ### A service per subdomain ([[wiki/concepts/source/fragmented-metapatterns/hierarchy|Hierarchy]]) ![A top-level orchestrator communicates with lower-level Orchestrators each of which manages a group of services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Orchestrator%20-%20Hierarchy.png) In a large system a single *Orchestrator* is very likely to become overgrown and turn into a development bottleneck (see [*Enterprise Service Bus*](#enterprise-service-bus-esb)). Building a [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*hierarchy*]] of *Orchestrators* may help \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\], but that requires the domain itself to be hierarchical. The top-level component may even be a [[wiki/concepts/source/extension-metapatterns/proxy|*Reverse Proxy*]] if no use cases cross subdomain borders or if the sub-orchestrators employ [[wiki/concepts/source/foundations-of-software-architecture/choreography|choreography]], resulting in a flat [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Cell-Based Architecture*]]. Otherwise it is a tree-like [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Orchestrator of Orchestrators*]]. ### A service per use case ([SOA]()-style) ![There are several orchestrators which use the same set of services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Orchestrator%20-%20SOA.png) \[[wiki/concepts/source/appendices/books-referenced|[SAHP]]\] advises for single-purpose *Orchestrators* in [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]]: each *Orchestrator* manages one use case. This enables fine-grained scalability but will quickly lead to integration hell as new scenarios keep getting added to the system. Overall, such a use of *Orchestrators* resembles the [*task layer*]() of [*SOA*](). ## Examples An *Orchestrator* may function in one of the following ways: - An [*API Composer*](#api-composer-remote-facade-gateway-aggregation-composed-message-processor-scatter-gather-mapreduce) distributes parts of a client request to multiple services for parallel execution. - A [*Process Manager*](#process-manager-orchestrator) splits a request into consecutive steps and oversees their execution. - A [*Saga*](#orchestrated-saga-saga-orchestrator-saga-execution-component-transaction-script-coordinator) applies a change to multiple services in an “all or nothing” manner. - An [*Integration Service*](#integration-micro-service-application-service) is a full-featured service which utilizes other services while providing for its users. - [*Front Controller*](#inexact-front-controller) describes the first service in a *Pipeline* if it receives notifications from other services to know the ongoing status of every request. There are also several patterns that have other functions in addition to orchestration: - An [*API Gateway*](#api-gateway) blends an *API Composer* (orchestrating request) and a *Gateway* (dealing with protocols and logging). - An [*Event Mediator*](#event-mediator) is a *Process Manager* with *Middleware* capabilities. - An [*Enterprise Service Bus*](#enterprise-service-bus-esb) is an orchestrating *Message Bus* (multi-protocol *Middleware*). ### API Composer, Remote Facade, Gateway Aggregation, Composed Message Processor, Scatter-Gather, MapReduce ![An API Composer calls services in parallel. A Scatter/Gather or MapReduce calls shards in parallel.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/API%20Composer.png) *API Composer* \[[wiki/concepts/source/appendices/books-referenced|[MP]]\] is a kind of [*Facade*](https://refactoring.guru/design-patterns/facade) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] which decreases the system’s latency by translating a high-level incoming message into a set of lower-level internal messages, sending them to the corresponding [[wiki/concepts/source/basic-metapatterns/services|services]] in parallel, waiting for results, and collecting the latter into a response to the original message. Such a logic may often be defined declaratively in a third-party tool without writing any low-level code. *Remote Facade* \[[wiki/concepts/source/appendices/books-referenced|[PEAA]]\] is a similar pattern which makes synchronous calls to the underlying components – its goal is implementing a coarse-grained protocol for the system’s clients, so that a client may achieve whatever it needs through a single request. [*Gateway Aggregation*](https://learn.microsoft.com/en-us/azure/architecture/patterns/gateway-aggregation) is a generalization of these patterns. *Composed Message Processor* \[[wiki/concepts/source/appendices/books-referenced|[EIP]]\] disassembles *API Composer* into smaller components: it uses a *Splitter* \[[wiki/concepts/source/appendices/books-referenced|[EIP]]\] to subdivide the request into smaller parts, a *Router* \[[wiki/concepts/source/appendices/books-referenced|[EIP]]\] to send each part to its recipient, and an *Aggregator* \[[wiki/concepts/source/appendices/books-referenced|[EIP]]\] to collect the responses into a single message. Unlike *API Composer*, it can also address [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]] or [[wiki/concepts/source/basic-metapatterns/shards|*Replicas*]]. A [*Scatter-Gather*](https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/scatter-gather.html) \[[wiki/concepts/source/appendices/books-referenced|[EIP]], [[wiki/concepts/source/appendices/books-referenced|DDS]]\] broadcasts a copy of the incoming message to each recipient, thus it lacks a *Splitter* (though \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\] seems to ignore this difference). [*MapReduce*](https://en.wikipedia.org/wiki/MapReduce) \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\] is similar to *Scatter-Gather* except that it summarizes the results received in order to yield a single value instead of concatenating them. If an *API Composer* needs to conduct sequential actions (e.g. first get user id by user name, then get user data by user id), it becomes a [*Process Manager*](#process-manager-orchestrator) which may require some coding. An *API Composer* is usually deployed as a part of an [*API Gateway*](#api-gateway). Example: Microsoft has an [article](https://learn.microsoft.com/en-us/azure/architecture/patterns/gateway-aggregation) on aggregation. ### Process Manager, Orchestrator ![The orchestrator calls several services one by one.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Process%20Manager.png) *Process Manager* \[[wiki/concepts/source/appendices/books-referenced|[EIP]], [[wiki/concepts/source/appendices/books-referenced|LDDD]]\] (referred to simply as *Orchestrator* in \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\]) is a kind of *Facade* that translates high-level tasks into sequences of lower-level steps. This subtype of *Orchestrator* receives a client request, stores its state, runs pre-programmed request processing steps, and returns a response. Each of the steps of a *Process Manager* is similar to a whole task of an *API Composer* in that it generates a set of parallel requests to internal services, waits for the results, and stores them for the future use in the following steps or final response. The scenarios it runs may branch on conditions. A *Process Manager* may be implemented in a general-purpose programming language, a declarative description for a third-party tool, or a mixture thereof. A *Process Manager* is usually a part of an [*API Gateway*](#api-gateway), [*Event Mediator*](#event-mediator) or [*Enterprise Service Bus*](#enterprise-service-bus-esb). Example: \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] provides several examples. ### (Orchestrated) Saga, Saga Orchestrator, [[wiki/concepts/source/implementation-metapatterns/microkernel|Saga Execution Component]], Transaction Script, Coordinator ![An atomically consistent saga rolls back changes after a failed write. An eventually consistent saga retries the failed write till it succeeds.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Saga.png) *(Orchestrated* \[[wiki/concepts/source/appendices/books-referenced|[SAHP]]\]*) Saga* \[[wiki/concepts/source/appendices/books-referenced|[LDDD]]\], *Saga Orchestrator* \[[wiki/concepts/source/appendices/books-referenced|[MP]]\] or [*Saga Execution Component*](https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf) is a subtype of *Process Manager* which is specialized in *distributed transactions*. Its name comes from epic stories woven from multiple episodes. - An *Atomically Consistent Saga* \[[wiki/concepts/source/appendices/books-referenced|[SAHP]]\] (which is the default meaning of the term) comprises a pre-programmed sequence of \{“do”, “undo”\} action pairs. When it is run, it iterates through the “do” sequence till it either completes (meaning that the transaction succeeded) or fails. A failed *Atomically Consistent Saga* begins iterating through its “undo” sequence to roll back the changes that were already made. - In contrast, an *Eventually Consistent Saga* \[[wiki/concepts/source/appendices/books-referenced|[SAHP]]\] always retries its writes till all of them succeed. A *Saga* is often programmed declaratively in a third-party [[wiki/concepts/source/implementation-metapatterns/microkernel|*Saga Framework*]] which can be integrated into any service that needs to run a *distributed transaction*. However, it is quite likely that such a service itself is an [*Integration Service*](#integration-micro-service-application-service) as it seems to [[wiki/concepts/source/foundations-of-software-architecture/orchestration|orchestrate]] other services. A *Saga* plays the roles of both *Facade* by translating a single transaction request into a series of calls to the services’ APIs and *Mediator* by keeping the states of the services consistent (the transaction succeeds or fails as a whole). Sometimes a *Saga* may include requests to external services (which are not parts of the system you are developing). A *Transaction Script* \[[wiki/concepts/source/appendices/books-referenced|[PEAA]], [[wiki/concepts/source/appendices/books-referenced|LDDD]]\] is a procedure that executes a transaction, possibly over multiple data stores \[[wiki/concepts/source/appendices/books-referenced|[LDDD]]\]. Unlike a *Saga*, it is synchronous, written in a general programming language, and does not require a dedicated framework to run. It operates the data store(s) directly while a *Saga* usually sends commands to services. A *Transaction Script* may return data to its caller. *Coordinator* \[[wiki/concepts/source/appendices/books-referenced|[POSA3]]\] is a generalized pattern for a component which manages multiple tasks (e.g. software updates of multiple components) to achieve “all or nothing” results (if any update fails, other components are rolled back). Example: \[[wiki/concepts/source/appendices/books-referenced|[SAHP]]\] investigates many kinds of *Sagas* while \[[wiki/concepts/source/appendices/books-referenced|[MP]]\] has a shorter description. ### Integration (Micro-)Service, Application Service ![An integration service is a full-featured service that stands between the client and the remaining services of the system.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Integration%20Service.png) An [*Integration Service*](https://github.com/wso2/reference-architecture/blob/master/event-driven-api-architecture.md) is a full-scale service (often with a dedicated database) that runs high-level scenarios while delegating the bulk of the work to several other services (remarkably, delegating to a single component forms [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]]). Though an *Integration Service* usually features both functions of *Orchestrator*, in a [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|*control* system]] its *Mediator* role is more prominent while in [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|*processing* software]] it is going to behave more like the *Facade*. A system with an *Integration Service* often resembles a shallow [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Top-Down Hierarchy*]]. Example: Order Service in \[[wiki/concepts/source/appendices/books-referenced|[MP]]\] seems to fit the description. ### (inexact) [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Front Controller]] ![A Front Controller is the first service of a pipeline which receives status notifications from every other service and responds to the client's get status query.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Front%20Controller.png) *Front Controller* \[[wiki/concepts/source/appendices/books-referenced|[SAHP]] [[wiki/concepts/source/analytics/ambiguous-patterns|but not]] [[wiki/concepts/source/appendices/books-referenced|PEAA]]\] is the name for the first (client-facing) service of a [[wiki/concepts/source/basic-metapatterns/pipeline|*pipeline*]] in [[wiki/concepts/source/basic-metapatterns/pipeline|*Choreographed Event-Driven Architecture*]] when that service collects information about the status of each request it has processed and forwarded down the *pipeline.* The status is received by listening for notifications from the downstream services and is readily available for the *Front Controller*’s clients, which makes *Front Controller* resemble [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Query Service*]] ([[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]]) and [*Application Service*](#integration-micro-service-application-service). ### [[wiki/concepts/source/extension-metapatterns/proxy|API Gateway]] ![An API Gateway both translates from the client's to the system's protocol and calls services in parallel.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/API%20Gateway.png) An *API Gateway* \[[wiki/concepts/source/appendices/books-referenced|[MP]]\] is a component that not only processes client requests (and encapsulates an implementation of a client protocol(s)) as a [[wiki/concepts/source/extension-metapatterns/proxy|*Gateway*]] (a kind of [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]]) but also splits every client request into multiple requests to internal services as an [*API Composer*](#api-composer-remote-facade-gateway-aggregation-composed-message-processor-scatter-gather-mapreduce) or [*Process Manager*](#process-manager-orchestrator) (which are *Orchestrators*). It is a common pattern for backend solutions as it does everything necessary to isolate the stable core of the system’s implementation from its fickle clients. Usually a third-party framework implements and colocates both its aspects, namely *Proxy* and *Orchestrator*, thus simplifying deployment and improving latency. Example: a thorough article from [Microsoft](https://learn.microsoft.com/en-us/azure/architecture/microservices/design/gateway). ### [[wiki/concepts/source/extension-metapatterns/middleware|Event Mediator]] ![An event mediator calls event processors one by one.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Event%20Mediator.png) *Event Mediator* \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] is an *orchestrating* [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]]. It not only receives requests from clients and turns each request into a multistep use case (as a [*Process Manager*](#process-manager-orchestrator)) but it also manages the deployed instances of services and acts as a medium which calls them. Moreover, unlike an ordinary *Middleware*, it seems to be aware of all of the kinds of messages in the system and which service each message must be forwarded to, resulting in overwhelming complexity concentrated in a single component which does not even follow the principle of separation of concerns. \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] recommends [building a stack of *Event Mediators*](#layered) from several vendors, further complicating this architecture. Example: Mediator Topology in the chapter of \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] on Event-Driven Architecture. ### [[wiki/concepts/source/extension-metapatterns/middleware|Enterprise Service Bus]] (ESB) ![An Enterprise Service Bus is between the fragmented task and entity layers of Service-Oriented Architecture. It mediates all calls and messages between the system components.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Enterprise%20Service%20Bus.png) [*Enterprise Service Bus*](https://www.confluent.io/learn/enterprise-service-bus/) (*ESB*) \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] is an overgrown [*Event Mediator*](#event-mediator) that incorporates lots of [*cross-cutting concerns*](https://en.wikipedia.org/wiki/Cross-cutting_concern), including protocol translation for which it utilizes at least one [[wiki/concepts/source/extension-metapatterns/proxy|*Adapter*]] per service (just as a [[wiki/concepts/source/extension-metapatterns/middleware|*Message Bus*]] does). The combination of a central role in organizations and its complexity was among the main reasons for the demise of [*Enterprise Service-Oriented Architecture*](). Example: Orchestration-Driven Service-Oriented Architecture in \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\], [how it is born](http://memeagora.blogspot.com/2009/01/tactics-vs-strategy-soa-tarpit-of.html) and [how it dies](http://memeagora.blogspot.com/2009/03/triumph-of-hope-over-reason-soa-tarpit.html) by Neal Ford. ## [[wiki/concepts/source/appendices/evolutions-of-an-orchestrator|Evolutions]] Employing an *Orchestrator* has two pitfalls: - The system becomes slower because too much communication is involved. - A single *Orchestrator* may grow too large and rigid. There is [[wiki/concepts/source/appendices/evolutions-of-an-orchestrator|one way to counter the first point and more than one to solve the second]]: - Subdivide the *Orchestrator* by the system’s subdomains, forming [[wiki/concepts/source/fragmented-metapatterns/layered-services|*Layered Services*]] and minimizing network communication. ![An orchestrator is subdivided into subdomain components which become the application layers of respective services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Orchestrator%20to%20Layered%20Services.png) - Subdivide the *Orchestrator* by the type of client, forming [*Backends for Frontends*](). ![An orchestrator is subdivided into Backends for Frontends.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Orchestrator%20to%20Backends%20for%20Frontends.png) - Add another [[wiki/concepts/source/basic-metapatterns/layers|*layer*]] of orchestration. ![An orchestrator is subdivided into a pair of simple and complex orchestrators.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Orchestrator%20add%20Orchestrator.png) - Build a [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Top-Down Hierarchy*]]. ![An orchestrator is subdivided into a hierarchy.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Orchestrator%20to%20Hierarchy.png) ## Summary An *Orchestrator* distills the high-level logic of your system and keeps it together in a layer which integrates other components. However, the separation of business logic into two layers results in much communication which impairs performance. --- title: "Proxy" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Extension metapatterns/Proxy.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Extension%20metapatterns/Proxy source_license_note: "See namespace README; preserve attribution and source links." --- # Proxy > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Extension metapatterns/Proxy.md`. ![A diagram for Services with a proxy, in abstractness-subdomain-sharding coordinates.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Main/Proxy.png) *Should I build the wall?* A layer of indirection between your system and its clients. Known as: [Proxy](https://refactoring.guru/design-patterns/proxy) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\]. Structure: A layer that pre-processes and/or routes user requests. Type: Extension component. | *Benefits* | *Drawbacks* | | --- | --- | | Separates cross-cutting concerns from the services | A single point of failure | | Decouples the system from its clients | Most proxies degrade latency | | Low attack surface | | | Several kinds of Proxies are available off the shelf | | References: Half of \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\] is about the use of *Proxies*. See also: \[[wiki/concepts/source/appendices/books-referenced|[POSA4]]\] on *Proxy*; [Chris Richardson](https://microservices.io/patterns/apigateway.html) and [Microsoft](https://learn.microsoft.com/en-us/azure/architecture/microservices/design/gateway) on *API Gateway*; [Martin Fowler](https://martinfowler.com/articles/gateway-pattern.html) on *Gateway*, *Facade*, and *API Gateway*. A *Proxy* stands between a (sub)system’s implementation and its users. It receives a request from a client, does some pre-processing, then forwards the request to a lower-level component. In other words, a *Proxy* encapsulates selected aspects of the system’s communication with its clients by serving as yet another layer of indirection. It may also decouple the system’s internals from changes in the public protocol. The [main functions](https://learn.microsoft.com/en-us/azure/architecture/microservices/design/gateway) of a *Proxy* include: - *Isolation* – the *Proxy* hides the internals of the system behind it from the clients. This both improves security because access to other system components is supervised and permits changes to the system’s components or structure as nothing external knows what’s behind the *Proxy*. - *Translation* – the *Proxy* may convert between the system’s internal protocol and its published interfaces. [*User Interface*](#user-interface-presentation-layer-separated-presentation-command-line-interface-cli-graphical-user-interface-gui-frontend-human-machine-interface-hmi-man-machine-interface-mmi-operator-interface) is a translating *Proxy* taken to extremes: it represents the system’s internal data and commands as human-readable information. - *Routing* – the *Proxy* tracks addresses of deployed instances of the system’s components and is able to forward a client’s request to the [[wiki/concepts/source/basic-metapatterns/shards|*shard*]] or [[wiki/concepts/source/basic-metapatterns/services|*service*]] which can handle it. Clients need to know only the public address of the *Proxy*. A *Proxy* may also respond on its own if the request is invalid or there is a matching response in the *Proxy*’s cache. - *Offloading* – a *Proxy* may implement generic aspects ([*cross-cutting concerns*](https://en.wikipedia.org/wiki/Cross-cutting_concern)) of the system’s public interface, such as authentication, authorisation, encryption, or request logging which would otherwise need to be implemented by the underlying system components. That allows for the services to concentrate on what you write them for – the business logic. ### Performance Most kinds of proxies trade latency (the extra network hop) for some other quality: - A [*Firewall*](#firewall-api-rate-limiter-api-throttling) slows down processing of good requests but *protects* the system from attacks. - Both a [*Load Balancer*](#load-balancer-sharding-proxy-cell-router-messaging-grid-scheduler) and a [*Dispatcher*](#dispatcher-reverse-proxy-ingress-controller-edge-service-microgateway) allow for the use of multiple servers (with identical or specialized components, respectively) to improve the system’s *throughput* but they still increase its minimal latency. - An [*Adapter*](#adapter-anticorruption-layer-abstraction-layer-open-host-service-gateway-message-translator-api-service-cell-gateway-inexact-backend-for-frontend-database-access-layer-data-mapper-repository-driver) adds *compatibility* but its latency cost is higher than with other *Proxies* as it not only forwards the original message but also changes its payload – an activity which involves data processing and serialization. A [*Cache*](#response-cache-read-through-cache-write-through-cache-write-behind-cache-cache-caching-layer-distributed-cache-replicated-cache) is a bit weird in that respect. It improves latency and throughput for repeated requests but degrades latency for unique ones. Furthermore, it is often colocated with some other kind of *Proxy* to avoid the extra network hop between the *Proxies*, which makes caching almost free in terms of latency. ### Dependencies *Proxies* widely vary in their functionality and level of intrusiveness. The most generic proxies, like *Firewalls*, may not know anything about the system or its clients. A *Response Cache* or *Adapter* must parse incoming messages, thus it depends on the communication protocol and message format. A *Load Balancer* or *Dispatcher* is aware of both the protocol and system composition. In fact, because *Proxies* tend to be configurable (on startup or through their APIs), there is no need to modify the code of a *Proxy* each time something changes in the underlying system. ### Applicability *Proxy* helps with: - *Multi-component systems.* Having multiple types and/or instances of services means that a client needs to know the components’ addresses to access them. A *Proxy* encapsulates that knowledge and may also provide other common functionality as an extra benefit. - *Dynamic scaling or sharding.* The *Proxy* both knows the system’s structure (the address of each instance of a service) and delivers user requests, thus it is the place to implement *sharding* (when a service instance is [[wiki/concepts/source/basic-metapatterns/shards|dedicated to a subset of users]]) or *load balancing* (when any service instance can [[wiki/concepts/source/basic-metapatterns/shards|serve any user]]) and even manage the size of the *Pool* of service instances. - *Multiple client protocols*. When the *Proxy* is the endpoint for the system’s users it may translate multiple external (user-facing) protocols into a unified internal representation. See also [*Backends for Frontends*](). - *System security.* Though a *Proxy* does not make a system more secure, it takes away the burden of security considerations from the services which implement the business logic, improving the separation of concerns and making the system components more simple and stupid. An off-the-shelf *Proxy* may be less vulnerable compared to in-house services (but don’t disregard [security through obscurity](https://en.wikipedia.org/wiki/Security_through_obscurity)!). *Proxy* hurts: - *Critical real-time paths.* It adds an extra hop in request processing, increasing latency for thoroughly optimized use cases. Such requests may need to bypass *Proxies*. ### Relations ![A proxy for a monolith, shards, layers, and services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Relations/Proxy.png) *Proxy*: - Extends [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] or [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] (forming *Layers*), [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]], or [[wiki/concepts/source/basic-metapatterns/services|*Services*]]. - Can be extended with another *Proxy* or merged with an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] into an [[wiki/concepts/source/extension-metapatterns/orchestrator|*API Gateway*]]. - Can be a part of a [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]]. - At least one *Proxy* per [[wiki/concepts/source/basic-metapatterns/services|*service*]] is employed by [[wiki/concepts/source/extension-metapatterns/middleware|*Message Bus*]], [[wiki/concepts/source/extension-metapatterns/middleware|*Enterprise Service Bus*]], [[wiki/concepts/source/implementation-metapatterns/mesh|*Service Mesh*]], and [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]]. - Is a special case (when there is a single kind of client) of [*Backends for Frontends*](). ## Variants by transparency A *Proxy* [may either fully isolate the system which it represents or merely help establish connections](https://community.f5.com/kb/technicalarticles/what-is-a-proxy/282718) between clients and servers. This resembles [[wiki/concepts/source/basic-metapatterns/layers|closed and open layers]] because a *Proxy* is a [[wiki/concepts/source/basic-metapatterns/layers|layer]] between a system and its clients. ### Full Proxy ![A full proxy mediates all messages between a client and a server.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Full%20Proxy.png) A *Full Proxy* processes every message between the system and its clients. It completely isolates the system and may meddle with the protocols but it is resource-heavy and adds to latency. [*Adapters*](#adapter-anticorruption-layer-abstraction-layer-open-host-service-gateway-message-translator-api-service-cell-gateway-inexact-backend-for-frontend-database-access-layer-data-mapper-repository-driver) and [*Response Caches*](#response-cache-read-through-cache-write-through-cache-write-behind-cache-cache-caching-layer-distributed-cache-replicated-cache) are always *Full Proxies*. ### Half-Proxy ![A half-proxy intercepts only the session establishment request and is transparent to the following in-session communication between the client and server.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Half%20Proxy.png) A *Half-Proxy* intercepts, analyzes, and routes the session establishment request from a client but then goes out of the loop. It may still forward the subsequent messages without looking into their content or it may even help connect the client and server directly, which is known as [*direct server return*](https://www.haproxy.com/glossary/what-is-direct-server-return-dsr) *(DSR)*. This approach is faster and much less resource-hungry but is also less secure and less flexible than that of *Full Proxy*. A [*Firewall*](#firewall-api-rate-limiter-api-throttling), [*Load Balancer*](#load-balancer-sharding-proxy-cell-router-messaging-grid-scheduler), or [*Reverse Proxy*](#dispatcher-reverse-proxy-ingress-controller-edge-service-microgateway) may act as a *Half-Proxy*. IP telephony servers often use *DSR*: the server helps call parties find each other and then establish direct media communication. ## Variants by placement As a *Proxy* stands between a (sub)system and its client(s), we can imagine a few ways to deploy it and then generalize our observation to other kinds of system components: ### Separate deployment: Standalone ![A standalone proxy is placed between a client and a layer of services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Proxy%20placement%20-%20Standalone.png) We can deploy a *Proxy* as a separate system component. This has the downside of an extra network hop (higher latency) in the way of every client request to the system and back but that is unavoidable in the following cases: - The *Proxy* uses a lot of system resources, thus it cannot be colocated with another component. This mostly affects [*Firewall*](#firewall-api-rate-limiter-api-throttling) and [*Cache*](#response-cache-read-through-cache-write-through-cache-write-behind-cache-cache-caching-layer-distributed-cache-replicated-cache). - The *Proxy* is stateful and deals with multiple services, which is true for a [*Load Balancer*](#load-balancer-sharding-proxy-cell-router-messaging-grid-scheduler), [*Reverse Proxy*](#dispatcher-reverse-proxy-ingress-controller-edge-service-microgateway), or [*API Gateway*](#api-gateway). ### On the system side: Sidecar ![A sidecar is co-located with the services and translates from the client's protocol to the service's API.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Proxy%20placement%20-%20Sidecar.png) We can often co-locate a *Proxy* with our system when the latter is not distributed. That avoids the extra network delay, traffic, and operational complexity and does not add any new hardware which can fail at the most untimely moments. Such a placement is called *Sidecar* \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\] (after the motorcycle add-on) and it is mostly applicable to [*Adapters*](#adapter-anticorruption-layer-abstraction-layer-open-host-service-gateway-message-translator-api-service-cell-gateway-inexact-backend-for-frontend-database-access-layer-data-mapper-repository-driver). It should be noted that *Sidecar* – co-locating a generic component and business logic – is more of a DevOps approach than an architectural pattern, thus we can see it used in a variety of ways \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\]: - As a *Proxy* between a component and its clients. - As an extra [[wiki/concepts/source/basic-metapatterns/services|*service*]] that provides observability or configures the main service. - As a [[wiki/concepts/source/basic-metapatterns/layers|*layer*]] containing general-purpose utilities. - As an *Adapter* for [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]]. ![A proxy between a service and its client; one between a service and a middleware; an extension aside of a service; a utility layer below a service.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Sidecars.png) [[wiki/concepts/source/implementation-metapatterns/mesh|*Service Mesh*]] (the [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] for [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]]) makes heavy use of *Sidecars* for co-locating any kind of generic code with every instance of a *Microservice*. ### On the client side: Ambassador ![An ambassador runs on the client side and translates the client's protocol into the one in use with the service.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Proxy%20placement%20-%20Ambassador.png) Finally, a *Proxy* may be co-located with a component’s clients, making it an *Ambassador* \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\], which is good for: - Low-latency systems with [[wiki/concepts/source/basic-metapatterns/shards|stateful *shards*]] – each client should access the shard that has their data, which only the *Proxy* knows how to choose. - [*Adapters*](#adapter-anticorruption-layer-abstraction-layer-open-host-service-gateway-message-translator-api-service-cell-gateway-inexact-backend-for-frontend-database-access-layer-data-mapper-repository-driver) that help client applications use an optimized or secure protocol. Notably, a [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugin*]] may act as an *Ambassador* for its origin subsystem. It makes local decisions in some scenarios while others cause it to communicate with the service it represents. See [[wiki/concepts/source/implementation-metapatterns/plugins|*Ambassador Plugin*]]. ![An ambassador plugin is a part of one service hosted inside another service. When called, it may consult its origin service or make independent decisions.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Ambassador%20Plugin.png) ## Examples *Proxies* are ubiquitous in backend systems as using one or several of them frees the underlying code from the need to provide boilerplate non-business-logic functionality. It is common to have several kinds of *Proxies* deployed sequentially (e.g. [*API Gateways*](#api-gateway) behind [*Load Balancers*](#load-balancer-sharding-proxy-cell-router-messaging-grid-scheduler) behind a [*Firewall*](#firewall-api-rate-limiter-api-throttling)) with many of them [[wiki/concepts/source/basic-metapatterns/shards|*pooled*]] to improve performance and stability. It is also possible to employ multiple kinds of *Proxies*, each serving its own kind of client, in parallel, resulting in [*Backends for Frontends*](). As *Proxies* are used for many purposes, there are a variety of their specializations and names. Below is a very rough categorization, complicated by the fact that real-world *Proxies* often implement several categories at once: > For example, NGINX claims to be: an HTTP web server, [*Reverse Proxy*](#dispatcher-reverse-proxy-ingress-controller-edge-service-microgateway), content [*Cache*](#response-cache-read-through-cache-write-through-cache-write-behind-cache-cache-caching-layer-distributed-cache-replicated-cache), [*Load Balancer*](#load-balancer-sharding-proxy-cell-router-messaging-grid-scheduler), TCP/UDP *Proxy* server, and mail *Proxy* server – all at once. - A [*Firewall*](#firewall-api-rate-limiter-api-throttling) defends a system from attacks. - A [*Response Cache*](#response-cache-read-through-cache-write-through-cache-write-behind-cache-cache-caching-layer-distributed-cache-replicated-cache) reuses a system’s responses to reduce load on the system. - A [*Load Balancer*](#load-balancer-sharding-proxy-cell-router-messaging-grid-scheduler) evenly distributes requests over several instances of a service. - A [*Reverse Proxy*](#dispatcher-reverse-proxy-ingress-controller-edge-service-microgateway) dispatches requests that come through a single entry point to several internal services. - An [*Adapter*](#adapter-anticorruption-layer-abstraction-layer-open-host-service-gateway-message-translator-api-service-cell-gateway-inexact-backend-for-frontend-database-access-layer-data-mapper-repository-driver) translates between a pair of protocols or interfaces. - An [*API Gateway*](#api-gateway) parses a client protocol, interprets compound requests, and forwards the subrequests to multiple internal services. - A [*User Interface*](#user-interface-presentation-layer-separated-presentation-command-line-interface-cli-graphical-user-interface-gui-frontend-human-machine-interface-hmi-man-machine-interface-mmi-operator-interface) represents a system in a way convenient for human interaction. ### Firewall, (API) Rate Limiter, API Throttling ![A firewall lets a request from a good client pass through while requests from a malicious client are blocked.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Firewall.png) The *Firewall* is a component for white- and black-listing network traffic, mostly to protect against attacks. It is possible to use both generic hardware *Firewalls* on the external perimeter for brute force (D)DoS protection and more complex access rules at a second layer of software *Firewalls* to protect critical data and services from unauthorized access. [*Rate Limiting*](https://testfully.io/blog/api-rate-limit/) makes sure that no single client uses too much of the system’s resources – it sets a limit on how many requests from a single source the system can process over a unit of time. Any requests over the limit are rejected. *Throttling* differs from *Rate Limiting* in that over-the-limit requests are queued for later processing, effectively slowing down communication by overly active clients. ### Response Cache, Read-Through Cache, Write-Through Cache, Write-Behind Cache, Cache, Caching Layer, [[wiki/concepts/source/extension-metapatterns/shared-repository|Distributed Cache, Replicated Cache]] ![A cache proxies requests and remembers responses to shortcircuit the processing of future requests.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Cache.png) If a system often receives identical requests, it is possible to remember its responses to most frequent of them and return the cached response without fully re-processing the request. The real thing is more complicated because users tend to change the data which the system stores, necessitating a variety of *cache refresh policies*. A *Response Cache* may be co-located with a [*Load Balancer*](#load-balancer-sharding-proxy-cell-router-messaging-grid-scheduler) or it may be \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\] [[wiki/concepts/source/basic-metapatterns/shards|*sharded*]] (each *Cache* processes a unique subset of requests) and/or [[wiki/concepts/source/basic-metapatterns/shards|*replicated*]] (all the *Caches* are similar) and thus require a *Load Balancer* of its own. This kind of component is called *Response Cache* because it stores the system’s responses to requests of its users or just *Cache* \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\] because it is the most common kind of a *Cache* in systems architecture. If the cached subsystem is a database, we can discern between read and write requests: - [*Read-Through Cache*](https://www.enjoyalgorithms.com/blog/read-through-caching-strategy) is when the *Cache* is updated on a miss for a read request but is transparent to or invalidated by write requests. - [*Write-Through Cache*](https://www.enjoyalgorithms.com/blog/write-through-caching-strategy) is when the *Cache* is updated by write requests that pass through it. - [*Write-Behind*](https://www.enjoyalgorithms.com/blog/write-behind-caching-pattern) is when the *Cache* aggregates multiple write requests to later send them to the database as a batch, saving bandwidth and possibly merging multiple updates of the same key. It is possible to combine multiple servers into a virtual *Caching Layer* \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\]: - In the simplest case, which does not require any additional instrumentation aside from a [*Load Balancer*](#load-balancer-sharding-proxy-cell-router-messaging-grid-scheduler), the instances of the *Caches* are independent and may return stale results. - In a *Distributed Cache*, driven by a [*Sharding Proxy*](#load-balancer-sharding-proxy-cell-router-messaging-grid-scheduler), every server ([[wiki/concepts/source/basic-metapatterns/shards|*shard*]]) holds a subset of the cached data, thus allowing for caching datasets which don’t fit in a single computer’s memory. - In a *Replicated Cache* the datasets of all the servers are identical and synchronized on any modification. This scales the cache’s throughput but requires a kind of synchronization engine, e.g. a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Data Grid*]]. ### Load Balancer, Sharding Proxy, Cell Router, Messaging Grid, Scheduler ![A load balancer forwards a client's request to any idle instance of a stateless service. A sharding proxy forwards a client's request to the shard that contains the client's data.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Load%20Balancer.png) Here we have a hardware or software component which distributes user traffic among multiple [[wiki/concepts/source/basic-metapatterns/shards|instances]] of a service: - A *Sharding Proxy* \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\] selects a [[wiki/concepts/source/basic-metapatterns/shards|*shard*]] based on specific data which is present in a request (OSI level 7 request routing) for a system where each shard owns a part of the system’s state, therefore only one (or a few for [[wiki/concepts/source/basic-metapatterns/shards|*replicated*]] *shards*) of the shards has the data required to process the client’s request. - A [*Load Balancer*](https://en.wikipedia.org/wiki/Load_balancing_(computing)) \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\] for a [[wiki/concepts/source/basic-metapatterns/shards|*Pool of stateless instances*]] or [[wiki/concepts/source/basic-metapatterns/shards|*Replicas*]], or a *Messaging Grid* \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] of [[wiki/concepts/source/extension-metapatterns/sandwich|*Space-Based Architecture*]] evenly distributes the incoming traffic over identical request processors ([OSI level](https://en.wikipedia.org/wiki/OSI_model) 4 load balancing) to protect any instance of the underlying system from overload. In some cases it needs to be session-aware (process OSI level 7) to assure that all the requests from a client are forwarded to the same instance of the service \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\]. - It may forward read requests to [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Read-Only Replicas*]] of the data while write requests are sent to the *leader* database ([*CQRS*](https://learn.microsoft.com/en-us/azure/architecture/patterns/cqrs)-like behavior). - A [*Cell Router*](https://docs.aws.amazon.com/wellarchitected/latest/reducing-scope-of-impact-with-cell-based-architecture/cell-routing.html) chooses a data center which is the closest to the user’s location. *Load Balancers* are very common in high-load backends. High-availability systems deploy multiple instances of a *Load Balancer* in parallel to remain functional if one of the *Load Balancers* fails. CPU-intensive applications (e.g. 3D games) often post asynchronous tasks for execution by *Thread Pools* under the supervision of a *Scheduler*. A similar pattern is found in OS kernels and *fiber* or *actor* frameworks where a limited set of CPU-affined threads is scheduled to run a much larger number of tasks. ### Dispatcher, Reverse Proxy, Ingress Controller, Edge Service, Microgateway ![A dispatcher exposes an interface which merges the interfaces of the services below the dispatcher.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Dispatcher.png) A [*Reverse Proxy*, *Ingress Controller*](https://traefik.io/blog/reverse-proxy-vs-ingress-controller-vs-api-gateway/), [*Edge Service*](https://medium.com/knerd/api-infrastructure-at-knewton-whats-in-an-edge-service-51a3777aeb41), or [*Microgateway*](https://github.com/wso2/reference-architecture/blob/master/event-driven-api-architecture.md) is a router that stands between the Internet and the organization’s internal network. It allows clients to use a public address for the system without knowing how and where their requests are processed. It parses user requests and forwards them to an internal server based on the requests’ bodies. A *Reverse Proxy* can be extended with a [*firewall*](#firewall-api-rate-limiter-api-throttling), *SSL termination*, [*load balancing*](#load-balancer-sharding-proxy-cell-router-messaging-grid-scheduler), and [*caching*](#response-cache-read-through-cache-write-through-cache-write-behind-cache-cache-caching-layer-distributed-cache-replicated-cache) functionality. Examples include Nginx. *Dispatcher* \[[wiki/concepts/source/appendices/books-referenced|[POSA1]]\] is a similar component for a single-process application. It serves a complex command line interface by receiving and preprocessing user commands only to forward each command to a module which knows how to handle it. The modules may register their commands with the *Dispatcher* at startup or there may be a static dispatch table in the code. You could have noticed that *Dispatcher* or *Reverse Proxy* is quite similar to [*Load Balancer* or *Sharding Proxy*](#load-balancer-sharding-proxy-cell-router-messaging-grid-scheduler) – they differ mostly in what kind of system lies below them: [[wiki/concepts/source/basic-metapatterns/services|*Services*]] or [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]]. ### Adapter, [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Anticorruption Layer, Abstraction Layer]], [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Open Host Service]], Gateway, Message Translator, API Service, Cell Gateway, (inexact) Backend for Frontend, Database Access Layer, Data Mapper, Repository, [[wiki/concepts/source/basic-metapatterns/services|Driver]] ![An adapter between a client and a service provider translates between their protocols.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Adapter.png) An [*Adapter*](https://refactoring.guru/design-patterns/adapter) \[[wiki/concepts/source/appendices/books-referenced|[GoF]], [[wiki/concepts/source/appendices/books-referenced|DDS]]\] is a mostly stateless *Proxy* that translates between an internal and public protocol and API formats. It may often be co-located with a [*Reverse Proxy*](#dispatcher-reverse-proxy-ingress-controller-edge-service-microgateway). When it adapts messages, it is called a *Message Translator* \[[wiki/concepts/source/appendices/books-referenced|[EIP]], [[wiki/concepts/source/appendices/books-referenced|POSA4]]\]. *Adapters* are often found between two system components (in [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]]) or between a component and [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] (in [[wiki/concepts/source/extension-metapatterns/middleware|*Enterprise Service Bus*]] and [[wiki/concepts/source/extension-metapatterns/middleware|*Service Mesh*]]). In \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\], when one component (*consumer*) depends on another (*supplier*), there may be an *Adapter* in between to [[wiki/concepts/source/analytics/indirection-in-commands-and-queries|decouple them]]. It is called *Anticorruption Layer* \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\] (as it protects its host from changes in its dependencies) when owned by the *consumer*’s team or *Open Host Service* \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\] (for its readiness to serve any clients) if the *supplier* adds it to grant one or more stable interfaces (*Published Languages* \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]). A *Gateway* \[[wiki/concepts/source/appendices/books-referenced|[PEAA]]\] or an [*API Service*](https://backendless.com/what-is-api-as-a-service/) often implies an *Adapter* with extra functionality, like *Reverse Proxy*, authorization, and authentication. [*Cell Gateway*](https://github.com/wso2/reference-architecture/blob/master/reference-architecture-cell-based.md) is a *Gateway* for a [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cell*]]. When a *Gateway* translates a single public API method into several calls towards internal services, it becomes an [*API Gateway*](#api-gateway) \[[wiki/concepts/source/appendices/books-referenced|[MP]]\] which is an aggregate of *Proxy* (protocol translation) and [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] (integration of the lower-level services). An *Adapter* between an end-user client (web interface, mobile application, etc.) and the system’s API is often called [*Backend for Frontend*](). It decouples the UI from the backend-owned system’s API, giving the teams behind both components the freedom to work more independently as changes in one component no longer directly affect the other. Adapters between software and hardware components are called (*device*) *Drivers*. There is also a whole bunch of *Abstraction Layers* that aim to protect the business logic from its environment, the idea which [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|is perfected]] by [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]]: - [*Hardware Abstraction Layer*](https://en.wikipedia.org/wiki/Hardware_abstraction) (*HAL*) hides details of hardware to make the code which controls it portable. - [*Operating System Abstraction Layer*](https://en.wikipedia.org/wiki/Operating_system_abstraction_layer) (*OSAL*) or *Platform Abstraction Layer* (*PAL*) abstracts the OS to make the application cross-platform. - [*Database Abstraction Layer*](https://en.wikipedia.org/wiki/Database_abstraction_layer) (*DBAL* or *DAL*), *Database Access Layer* \[[wiki/concepts/source/appendices/books-referenced|[POSA4]]\] or *Data Mapper* \[[wiki/concepts/source/appendices/books-referenced|[PEAA]]\] attempts to help building database-agnostic applications by making all the databases look the same. - [*Repository*](https://martinfowler.com/eaaCatalog/repository.html) \[[wiki/concepts/source/appendices/books-referenced|[PEAA]], [[wiki/concepts/source/appendices/books-referenced|DDD]]\] provides methods to access a record stored in a database as if it were an object in the application’s memory. > An *Adapter* creates a [layer of indirection](https://en.wikipedia.org/wiki/Fundamental_theorem_of_software_engineering) between your code and a library or service which it uses. If the external component’s interface changes, or you need to substitute a component with an incompatible implementation from another vendor, and your code accesses the component directly, you will have to make many changes throughout your code. However, if there is an *Adapter* in-between, your code depends only on the interface of the *Adapter*. And when the external component changes or is replaced, only the relatively small *Adapter*’s implementation needs to change while your main code is blessed with ignorance of what lies beyond the *Adapter*’s borders. ### [[wiki/concepts/source/extension-metapatterns/orchestrator|API Gateway]] ![An API Gateway both translates from the client's to the system's protocol and calls services in parallel.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/API%20Gateway.png) *API Gateway* \[[wiki/concepts/source/appendices/books-referenced|[MP]]\] is a fusion of [*Gateway*](#adapter-anticorruption-layer-abstraction-layer-open-host-service-gateway-message-translator-api-service-cell-gateway-inexact-backend-for-frontend-database-access-layer-data-mapper-repository-driver) (*Proxy*) and [[wiki/concepts/source/extension-metapatterns/orchestrator|*API Composer*]] ([[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]]). The *Gateway* aspect encapsulates the external (public) protocol while the *API Compose*r translates the system’s high-level public API methods into multiple (usually parallel) calls to the APIs of internal components, collects the results, and conjoins them into a response. *API Gateway* is [[wiki/concepts/source/extension-metapatterns/orchestrator|discussed in more detail]] under *Orchestrator*. ### User Interface, Presentation Layer, [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Separated Presentation]], Command Line Interface (CLI), Graphical User Interface (GUI), Frontend, Human-Machine Interface (HMI), Man-Machine Interface (MMI), Operator Interface ![A user interface stands between a human and software. It receives mouse input and produces output on a display.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/User%20Interface.png) An *Adapter* between a human and a computer system is called a [[wiki/concepts/source/basic-metapatterns/layers|*User Interface*]] (*UI*) or *Presentation Layer* \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]. Though a *UI* mainly translates user actions into commands to the underlying system and presents the results which the system returns and other information supposedly important to the user, it may also include [[wiki/concepts/source/basic-metapatterns/layers|*integration logic*]] (use cases) which are occasionally tightly coupled to the visual behavior. *UI* comes in several flavors: - *Command Line Interface* (*CLI*) is text-based and sequential – it executes one command at a time. It’s the simplest kind of *UI*. - *Graphical User Interface* (*GUI*) is built around graphical representation of information and controls. It may rely on the windowing system of the underlying OS, a third-party framework, or build something unique, which takes place in games. - *Frontend* is an [*Ambassador*](#on-the-client-side-ambassador) executed by a client’s browser on the system’s behalf. - [*Human-Machine Interface*](https://en.wikipedia.org/wiki/User_interface#Terminology) (*HMI*) or *Man-Machine Interface* (*MMI*) is usually used for human interaction with an embedded system. Sometimes this term may include both input / output hardware (e.g. mouse and display, or touch screen) and software that operates it. - [*Operator Interface*](https://en.wikipedia.org/wiki/User_interface#Terminology) is an *HMI* that grants its user access to a system of several embedded devices. [*Separated Presentation*](https://martinfowler.com/eaaDev/SeparatedPresentation.html) is, basically, another name for *User Interface* except that this pattern focuses on dispensability of any implementation of a *UI*: the same system can be driven by a *CLI*, *GUI*, or *Frontend* without noticing any difference. Many variants of *Separated Presentation* are known as [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*MVP*]] and [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*MVC*]] families of patterns discussed under [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]]. ## [[wiki/concepts/source/appendices/evolutions-of-a-proxy|Evolutions]] It usually makes little sense to get rid of a *Proxy* once it has been integrated into a system. The only real drawback to using a *Proxy* is a slight increase in latency for user requests which may be mitigated through the creation of [bypass channels](#half-proxy) between the clients and a service which needs low latency. The other drawback of the pattern, the *Proxy* being a single point of failure, is countered by deploying multiple instances of the *Proxy*. As *Proxies* are usually third-party products, there is not much that [[wiki/concepts/source/appendices/evolutions-of-a-proxy|we can change about them]]: - We can add another kind of a *Proxy* on top of an existing one. ![A proxy is added on top of an existing proxy.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Proxy%20add%20Proxy.png) - We can use a stack of *Proxies* per client, making [*Backends for Frontends*](). ![A proxy is subdivided into Backends for Frontends.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Proxy%20to%20Backends%20for%20Frontends.png) ## Summary A *Proxy* represents your system to its clients and takes care of some aspects of the communication. It is common to see multiple *Proxies* deployed sequentially as they are often stackable. --- title: "Sandwich" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Extension metapatterns/Sandwich.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Extension%20metapatterns/Sandwich source_license_note: "See namespace README; preserve attribution and source links." --- # Sandwich > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Extension metapatterns/Sandwich.md`. ![A diagram for Sandwich Architecture, in abstractness-subdomain-sharding coordinates.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Main/Sandwich.png) *Follow the line of least resistance.* Divide where it is loosely coupled. Structure: A layer of domain-level services between shared integration and data layers. Type: System topology, implementation. | *Benefits* | *Drawbacks* | | --- | --- | | Supports medium to large codebases | Aggressive optimization is impossible | | Multiple specialized development teams | Low fault tolerance | | There are many ways to evolve the system | | References: None I know of. A *Sandwich* is a (sub)system midway between [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] and [[wiki/concepts/source/basic-metapatterns/services|*Services*]]. It emerges when a *layered* component outgrows its architecture and its middle ([[wiki/concepts/source/basic-metapatterns/layers|*domain*]]) layer – which tends to be both the largest and least cohesive – is divided into subdomains while the [[wiki/concepts/source/basic-metapatterns/layers|*application*]] and [[wiki/concepts/source/basic-metapatterns/layers|*data*]] layers remain intact. Another, less common, origin is a (sub)system of [[wiki/concepts/source/basic-metapatterns/services|*(Micro)Services*]] which becomes so tightly coupled that it needs to be partially merged. A *Sandwich*, named after the shape of its diagram, includes the following components: - A shared [[wiki/concepts/source/introduction/system-topologies|*managing layer*]] which receives client requests and dispatches them to the underlying *domain-level services*. Though this layer may either implement [[wiki/concepts/source/basic-metapatterns/layers|*use cases*]] as an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]], forward each client action to a matching service as a [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]], or do both as an [[wiki/concepts/source/extension-metapatterns/orchestrator|*API Gateway*]], it remains the component that ties the entire system together. - [[wiki/concepts/source/basic-metapatterns/layers|*Domain-level*]] [[wiki/concepts/source/basic-metapatterns/services|*services*]] (if distributed) or [[wiki/concepts/source/basic-metapatterns/services|*modules*]] (otherwise) that implement the bulk of the system’s business logic and thus contain most of the system’s code. - A shared [[wiki/concepts/source/basic-metapatterns/layers|*data layer*]] which the upper layer’s services operate on. It may be a persistent database or an in-memory application state. *Sandwiches* often occur naturally without being recognized as a distinct architecture. In fact, I had to make up a name for this [[wiki/concepts/source/introduction/system-topologies|system topology]]. ### Performance As [[wiki/concepts/source/basic-metapatterns/layers|*domain*-level]] services rarely interact among themselves, the performance of a *Sandwich* is similar to that of [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] with the same kind of deployment (components of a *Sandwich* may or may not be colocated). ![Control and data flow is identical in Sandwich and Layers.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Performance/Sandwich.png) ### Dependencies The *Sandwich*’s [[wiki/concepts/source/basic-metapatterns/layers|*integration* layer]] depends on every service inside the *Sandwich*. The services themselves depend on the [[wiki/concepts/source/basic-metapatterns/layers|*data* layer]]. ![The integration layer depends on every service. Every service depends on the data layer.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Dependencies/Sandwich.png) Having two shared layers provides three options for invoking the [[wiki/concepts/source/basic-metapatterns/layers|*domain*]] components: - The most common approach is [[wiki/concepts/source/foundations-of-software-architecture/orchestration|*orchestration*]] by the integration layer. - [[wiki/concepts/source/foundations-of-software-architecture/programming-and-architectural-paradigms|Data-centric]] domains may rely on [[wiki/concepts/source/foundations-of-software-architecture/shared-data|*data change notifications*]]. - [[wiki/concepts/source/foundations-of-software-architecture/choreography|*Choreography*]] via publish/subscribe is rare because it is inferior to the other two options: - It is not enough to decouple the subdomain services which are already bound together by the shared data layer. - *Publish/subscribe* requires additional libraries and setup, while the data store, which often supports notifications, is already in place. - Furthermore, *orchestration* allows for much better control over use case logic and error handling than *choreography*. ### Applicability *Sandwich* fits: - *Medium-sized projects with complex domain logic.* The subdivision of the *domain* layer keeps the code complexity under control and supports development by multiple teams with little increase of operational burden. - *Rapid development of ordinary systems.* If you don’t face any challenging forces, you should keep your architecture [simple and stupid](https://en.wikipedia.org/wiki/KISS_principle) but still flexible – which is the pragmatic *Sandwich*. You will be able to evolve it in the future if needed. - *Data-centric domains.* This architecture allows all the services to work on the same dataset, each reading and writing the parts that are involved in its business logic. *Sandwich* does not help: - *Projects with a large number of use cases.* As the [[wiki/concepts/source/basic-metapatterns/layers|*integration* layer]] remains monolithic, it still can grow out of control, requiring the system to transform into [[wiki/concepts/source/fragmented-metapatterns/layered-services|*Layered Services*]]. - *Huge systems.* Both the [[wiki/concepts/source/basic-metapatterns/layers|*integration*]] and [[wiki/concepts/source/basic-metapatterns/layers|*data*]] layers are cumbersome in that case. - *Demanding forces.* The *Sandwich* architecture is a jack of all trades, master of none. In general, it is an average backend, and is neither highly elastic, flexible, nor low latency. ### Relations ![Transitions between Layers, a Sandwich, a Service-Based Architecture, and Layered Services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Relations/Sandwich.png) *Sandwich*: - Is a system in transition between [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] and [[wiki/concepts/source/basic-metapatterns/services|*Services*]]. - Is [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] with a middle layer split into services or [[wiki/concepts/source/basic-metapatterns/services|*Services*]] sandwiched between layers. - May implement a [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] or *service*. - Often provides the internals of a [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cell*]]. - Includes a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]] and an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] and/or [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]]. > A [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cell*]] encapsulates several services so that they can be treated as a single entity with the goal of reducing the number of top-level system components. Typically, interdependent and closely communicating services are clustered into a single *Cell*, predisposing the *Cell*’s internals for further merging to reduce the communication overhead and the amount of boilerplate code. Such a scenario often results in a *Sandwich* trapped inside a *Cell*. ## Examples Though most real-world *Sandwiches* stay beneath the radar as non-standard architectures, there are two well-documented and highly specialized occurrences of this topology in [[wiki/concepts/source/foundations-of-software-architecture/programming-and-architectural-paradigms|data-centric]] domains and a few less stringent generic cases: - [*Blackboard System*](#blackboard-system) describes how specialized algorithms cooperate to try solving a non-deterministic problem. - [*Space-Based Architecture*](#space-based-architecture) runs multiple instances of services co-located with a distributed in-memory data store. - [*Service-Based Architecture*](#service-based-architecture) comprises large services that often share a database. - [*Nanoservices*](#nanoservices) is a system of functions deployed to a cloud. - [*Command Query Responsibility Segregation*](#command-query-responsibility-segregation-cqrs) uses different models for read-write and read-only scenarios. - [*Replicated Load-Balanced Services*](#inexact-replicated-load-balanced-services-lambdas) are identical instances of a stateless service that access a shared database. ### [[wiki/concepts/source/extension-metapatterns/shared-repository|Blackboard]] System ![A Blackboard System includes a control which orchestrates knowledge sources which access a blackboard with shared data.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Blackboard.png) Some domains are [complex and ill-structured](http://i.stanford.edu/pub/cstr/reports/cs/tr/86/1123/CS-TR-86-1123.pdf): there is only a vague understanding of how the inputs relate to outputs, therefore you cannot write a single algorithm to solve it. If you are lucky to have a huge labeled dataset, you can attempt training a neural network. If there are not so many examples, you are in trouble. [*Blackboard Systems*](https://en.wikipedia.org/wiki/Blackboard_system) \[[wiki/concepts/source/appendices/books-referenced|[POSA1]], [[wiki/concepts/source/appendices/books-referenced|POSA4]]\] were invented half a century ago to tackle non-deterministic problems: speech or image recognition, X-ray crystallography of proteins, or even tracking enemy submarines or stealth aircraft. In these cases inputs are noisy and scarce and outputs can vary indefinitely, therefore one must go through trial and error proposing, refining, and comparing many possible solutions to arrive at something plausible. The system consists of: - A [[wiki/concepts/source/extension-metapatterns/shared-repository|*blackboard*]] – a [[wiki/concepts/source/extension-metapatterns/shared-repository|*shared data store*]] that contains all the current knowledge about the problem, namely inputs and hypotheses. It is subdivided into abstraction levels which range from the original inputs through multiple intermediate representations to the output data structure. Each level usually contains multiple solution attempts. - *Knowledge sources* – independent, specialized components that process data from a lower level to create a higher-level hypothesis. For example, in voice recognition, a knowledge source may read a sound wave and write a letter which that wave encodes. Another knowledge source may read letters and output English words, while another one tries to find French words. And the final one collects words into sentences. - A [[wiki/concepts/source/extension-metapatterns/orchestrator|*control*]] – a [[wiki/concepts/source/extension-metapatterns/proxy|*scheduler*]] that assigns processor time to knowledge sources. It balances the quality and speed of the solution attempts based on the current progress and remaining time. This architecture makes best use of every system layer: - The *control* is required to prune the exponential growth of possible solutions because there are not enough system resources to check all of them. - The independent *knowledge sources* are easy to add or remove, allowing the developers to try various algorithms without touching other components. - The *blackboard* enables the cooperation of knowledge sources, each of which is limited to a small part of the overall solution. ### [[wiki/concepts/source/implementation-metapatterns/mesh|Space-Based Architecture]] ![Space-Based Architecture comprises the following layers: a messaging grid, a processing grid, scaled processing units, a data grid, a deployment manager, and a persistent database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Multifunctional%20-%20Space-Based%20Architecture.png) *Space-Based Architecture* \[[wiki/concepts/source/appendices/books-referenced|[SAP]], [[wiki/concepts/source/appendices/books-referenced|FSA]]\] is an extremely elastic alternative to [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]] which works well for [[wiki/concepts/source/foundations-of-software-architecture/programming-and-architectural-paradigms|data-centric]] domains. Each *processing unit* – a [[wiki/concepts/source/basic-metapatterns/layers|domain-level]] service – is co-located with an in-memory replica of the entire system’s data, which makes both data access and creation of new instances of *processing units* blazingly fast. As is common for *Sandwiches*, *processing units* can be [[wiki/concepts/source/foundations-of-software-architecture/orchestration|*orchestrated*]] by the [[wiki/concepts/source/extension-metapatterns/orchestrator|*processing grid*]] or they can [[wiki/concepts/source/foundations-of-software-architecture/shared-data|*subscribe to changes*]] in the shared [[wiki/concepts/source/extension-metapatterns/shared-repository|*data grid*]], this architecture being flexible enough to allow for choosing a [[wiki/concepts/source/foundations-of-software-architecture/arranging-communication|communication paradigm]] on per request basis. Aside from *processing units*, which contain the main business logic, *Space-Based Architecture* involves: - A *messaging grid* which is a [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]] (combination of [[wiki/concepts/source/extension-metapatterns/proxy|*Gateway*]], [[wiki/concepts/source/extension-metapatterns/proxy|*Dispatcher*]], and [[wiki/concepts/source/extension-metapatterns/proxy|*Load Balancer*]]) that receives, preprocesses, and persists client requests. Simple requests are forwarded to the least loaded *processing unit* while anything complicated goes to the *processing grid*. - A [[wiki/concepts/source/extension-metapatterns/orchestrator|*processing grid*]] is an optional [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] which manages multi-step workflows for complicated requests that involve branching and error handling. - A [[wiki/concepts/source/extension-metapatterns/shared-repository|*data grid*]] is a distributed in-memory data store. It is built of caching [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]] nodes which are co-located with instances of *processing units*, making the data store access extremely fast. However, the speed and scalability is paid for with stability – any data in memory is prone to disappearing. Therefore the *data grid* backs up all the changes to a slower *persistent database*. - A [[wiki/concepts/source/extension-metapatterns/middleware|*deployment manager*]] is a [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] that creates and destroys instances of *processing units* (which are paired to the nodes of the *data grid*), just like [[wiki/concepts/source/extension-metapatterns/middleware|*Service Mesh*]] does for [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]] (which are paired to [[wiki/concepts/source/extension-metapatterns/proxy|*Sidecars*]]). However, in contrast to *Service Mesh*, it does not provide a messaging infrastructure because *processing units* communicate by sharing data via the *data grid*, not by sending messages. As *Space-Based Architecture* runs every component in a [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]], it avoids the fault tolerance and database performance drawbacks inherent to *Sandwich*, trading them for possibility of write conflicts when multiple clients cause changes to the same piece of data simultaneously. ### [[wiki/concepts/source/basic-metapatterns/services|Service-Based Architecture]] ![A Sandwich-like topology with user interface, a layer of domain services, and a shared database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Service-Based%20Architecture.png) *Service-Based Architecture* \[[wiki/concepts/source/appendices/books-referenced|[FSA]] [[wiki/concepts/source/analytics/ambiguous-patterns|but not]] [[wiki/concepts/source/appendices/books-referenced|DEDS]]\] is the most pragmatic and loosely defined of topologies based on *Services* (hence the name). In a basic *Service-Based Architecture* the [[wiki/concepts/source/basic-metapatterns/services|*subdomain services*]] are integrated by a [[wiki/concepts/source/extension-metapatterns/proxy|*User Interface*]] layer, usually a *Frontend*, and there is a single [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Database*]]. However, as there are no written rules for *Service-Based Architecture*, multiple databases or finer-grained *GUI*s may be used for programmers’ convenience, disintegrating the *Sandwich* topology for the sake of less coupled [[wiki/concepts/source/fragmented-metapatterns/layered-services|*Layered Service*s]]. ![A Sandwich-like topology with shared user interface and database is gradually transformed into layered services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Service-Based%20to%20Layered%20Services.png) ### [[wiki/concepts/source/basic-metapatterns/services|Nanoservices]] ![Nanoservices form a Sandwich-shaped architecture. The upper layer is an API Gateway for an orchestrated system or a gateway for pipelined Nanoservices. The lower layer is a shared datastore.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Nanoservices.png) *Nanoservices* is another loosely defined architecture built of single-purpose *functions* ([FaaS](https://en.wikipedia.org/wiki/Function_as_a_service)) individually deployed to the cloud. It is highly elastic and relies on a cloud provider for operational support, saving costs for small businesses. As a single *nanoservice* is too small to implement a use case, there must be an [[wiki/concepts/source/basic-metapatterns/layers|*integration layer*]] that receives client or user requests, runs several *nasoservices* to execute it, and returns a response. The integration layer may be a [[wiki/concepts/source/extension-metapatterns/proxy|*Frontend*]], [[wiki/concepts/source/extension-metapatterns/orchestrator|*API Gateway*]], an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Event Mediator*]] for [[wiki/concepts/source/basic-metapatterns/services|*orchestrated Nanoservices*]], or an ordinary [[wiki/concepts/source/extension-metapatterns/proxy|*Gateway*]] that initiates a [[wiki/concepts/source/basic-metapatterns/pipeline|*pipelined*]] scenario and receives its response from a [[wiki/concepts/source/basic-metapatterns/pipeline|*choreographed* system]]. As each *nanoservice* is stateless, for the system to be stateful there must be a *data store*. And as each *nanoservice* can do only one thing (either read, write, or search), the *data store* must be [[wiki/concepts/source/extension-metapatterns/shared-repository|*shared*]] to be of any use. Please note how a fine-grained decomposition of business logic necessarily results in a *Sandwich* architecture. ### [[wiki/concepts/source/fragmented-metapatterns/layered-services|Command Query Responsibility Segregation]] (CQRS) ![A large read and smaller write models between a user interface and database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/CQRS.png) [*Command Query Responsibility Segregation*](https://martinfowler.com/bliki/CQRS.html) (*CQRS*) is a principle which calls for separation of components that process *commands* (requests that change the system’s data) and *queries* (requests that analyze the data). The subdivision necessarily happens at the [[wiki/concepts/source/basic-metapatterns/layers|*domain*]] (*model*) level, resulting in separate [*Write Model*](https://learn.microsoft.com/en-us/azure/architecture/patterns/cqrs) ([*Command Model*](https://martinfowler.com/bliki/CQRS.html)) and [*Read Model*](https://learn.microsoft.com/en-us/azure/architecture/patterns/cqrs) ([*Query Model*](https://martinfowler.com/bliki/CQRS.html), [*Thin Read Layer*](https://cqrs.wordpress.com/wp-content/uploads/2010/11/cqrs_documents.pdf)). In the simplest case the [[wiki/concepts/source/basic-metapatterns/layers|*data* layer]] is kept monolithic, as shown in the diagram above. *CQRS* helps decouple the logic-heavy object-oriented code which maintains data consistency and business constraints during editing data records from the search and aggregation code that needs direct database access to run complex SQL queries. If that is not enough, it is possible to trade complexity for performance by employing specialized databases: [*OLTP*](https://en.wikipedia.org/wiki/Online_transaction_processing) for use with the *Write Model* and [*OLAP*](https://en.wikipedia.org/wiki/Online_analytical_processing) for the *Read Model*, as described in the [[wiki/concepts/source/fragmented-metapatterns/layered-services|corresponding section of the *Layered Services* chapter]]: ![The single database of a Sandwich-like CQRS with a shared database is subdivided into OLTP and OLAP databases, forming Layered Services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/CQRS%20to%20Layered%20Services.png) ### (inexact) [[wiki/concepts/source/basic-metapatterns/shards|Replicated Load-Balanced Services]], Lambdas ![A load balancer connects a new client to a free instance of a stateless backend that accesses a database shared among all the backend instances.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/1/Shards%20-%20Pool.png) Replicating a system’s [[wiki/concepts/source/basic-metapatterns/layers|*domain*]] [[wiki/concepts/source/basic-metapatterns/layers|*tier*]] along the [[wiki/concepts/source/introduction/metapatterns|*sharding* axis]] also results in a *Sandwich*-like [[wiki/concepts/source/introduction/system-topologies|topology]], albeit with altered properties: while the original *Sandwich*’s subdivision of the codebase along the [[wiki/concepts/source/introduction/metapatterns|*subdomain* axis]] allows for multiteam development and easy integration of new functionality, replication along the *sharding* axis only makes it simple to add or remove instances of the middle tier as the system load changes. *Replicated Load-Balanced Services* \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\] is the general name for running multiple instances of a stateless service that [[wiki/concepts/source/extension-metapatterns/shared-repository|*share a database*]] and run under a [[wiki/concepts/source/extension-metapatterns/proxy|*Load Balancer*]]. [*Lambdas*](https://jesseduffield.com/Notes-On-Lambda/) is a synonym for a similar setup from cloud computing providers (see also [*Nanoservices*](#nanoservices)). ## Evolutions The components of a *Sandwich* provide plenty of ways to alter the system, with unique evolutions detailed in [[wiki/concepts/source/appendices/evolutions-of-a-sandwich|Appendix E]]: ### [[wiki/concepts/source/appendices/evolutions-of-a-sandwich|Primary evolutions]] [[wiki/concepts/source/appendices/evolutions-of-a-sandwich|Some evolutions]] involve the system’s [[wiki/concepts/source/basic-metapatterns/layers|domain logic]] or its topology: - The *domain-level services* are independent enough to be easily added or removed. ![One of the domain-level services is removed and another one is added.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Sandwich%20add%20remove%20Service.png) - In most cases they share technologies, allowing for splitting or merging of the services. ![One domain-level service is split in half while two other services are merged together.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Sandwich%20split%20merge%20Services.png) - If the services are found to be strongly coupled, they can be merged into a monolithic layer, likely to be subdivided in a better way later on. ![The entire domain layer is merged, resulting in Layers.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Sandwich%20to%20Layers.png) - Alternatively, the subdomains can be further decoupled. ![The integration and data layers are divided into subdomains, producing Three-Layered Services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Sandwich%20to%20Layered%20Services.png) ### [[wiki/concepts/source/appendices/evolutions-of-a-shared-repository|Evolutions of the data layer]] If the [[wiki/concepts/source/basic-metapatterns/layers|*data* layer]] becomes a performance bottleneck, it can be [[wiki/concepts/source/extension-metapatterns/shared-repository|split as a *Shared Repository*]]: - [[wiki/concepts/source/basic-metapatterns/shards|*Shard*]] the database if its records are mutually independent or [[wiki/concepts/source/basic-metapatterns/shards|*replicate*]] it if it is the read traffic which overloads the system. - Apply [[wiki/concepts/source/extension-metapatterns/shared-repository|*Space-Based Architecture*]] if elasticity is more important than consistency. - Divide the data into databases, private to the domain-level services, thus transforming the system into [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrated*]] [[wiki/concepts/source/basic-metapatterns/services|*Services*]]. - Deploy specialized data stores ([[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]]). ### [[wiki/concepts/source/appendices/evolutions-of-an-orchestrator|Evolutions of the application layer]] If the [[wiki/concepts/source/basic-metapatterns/layers|*integration* layer]] contains use cases and becomes cumbersome, it should be subdivided following the [[wiki/concepts/source/extension-metapatterns/orchestrator|evolutions of *Orchestrator*]]: - Into [[wiki/concepts/source/basic-metapatterns/services|*Services*]] if the use cases cluster around subdomains. - Into [*Backends for Frontends*]() if the system serves several kinds of clients. - Into [[wiki/concepts/source/extension-metapatterns/orchestrator|*Layers*]] if some use cases are simple while others are complicated. - Into a [[wiki/concepts/source/extension-metapatterns/orchestrator|*Hierarchy*]] if the use cases include both generic and specialized logic. ## Summary *Sandwich* is a pragmatic architecture midway between [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] and [[wiki/concepts/source/basic-metapatterns/services|*Services*]]. It combines simplicity and flexibility, avoiding unnecessary effort for the present while retaining many paths to evolve in the future. --- title: "Shared Repository" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Extension metapatterns/Shared Repository.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Extension%20metapatterns/Shared%20Repository source_license_note: "See namespace README; preserve attribution and source links." --- # Shared Repository > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Extension metapatterns/Shared Repository.md`. ![A diagram for Services with a shared repository, in abstractness-subdomain-sharding coordinates.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Main/Shared%20Repository.png) *Knowledge itself is power.* Sharing data is simple (& stupid). Known as: Shared Repository \[[wiki/concepts/source/appendices/books-referenced|[POSA4]]\]. Structure: A layer of data shared among higher-level components. Type: Extension for [[wiki/concepts/source/basic-metapatterns/services|*Services*]] or [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]]. | *Benefits* | *Drawbacks* | | --- | --- | | Supports domains with coupled data | A single point of failure | | Implements data access and synchronization (consistency) concerns | All the services depend on the schema of the shared data | | Helps saving on hardware, licenses, traffic, and administration | A single data store technology may not fit the needs of all the services equally well | | Quick start for a project | Limits scalability | References: \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\] is all about databases; \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] has chapters on *Service-Based Architecture* and *Space-Based Architecture*; \[[wiki/concepts/source/appendices/books-referenced|[DEDS]]\] deals with *Shared Event Store.* A *Shared Repository* builds communication in the system around its data, which is natural for [[wiki/concepts/source/foundations-of-software-architecture/shared-data|data-centric domains]] and multiple [[wiki/concepts/source/basic-metapatterns/shards|instances of stateless services]] and may often simplify the development of a system of [[wiki/concepts/source/basic-metapatterns/services|*Services*]] which need to exchange data. It covers the following concerns: - Storage of the entire domain data. - Keeping the data self-consistent by providing atomic transactions for use by the application code. - Communication between the services (if the repository supports notifications on data change). - Data aggregation and analytics (if the database engine supports complex queries). The drawbacks are extensive coupling (it’s hard to alter a thing which is used in many places throughout the entire system) and limited scalability (even distributed databases struggle against distributed locks and the need to keep their nodes’ data in sync). ### Performance A shared database with consistency guarantees ([ACID](https://en.wikipedia.org/wiki/ACID)) is likely to lower the total resource consumption compared to one database per service (as the services don’t need to implement and keep updated [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*CQRS views*]] of other services’ data) but it increases latency and it may become the system’s performance bottleneck. Moreover, by using a shared database services lose the ability to choose the database technologies which best fit their tasks and data. Another danger lies with locking records inside the database. Different services may use different order of tables in transactions, hitting deadlocks in the database engine which show up as transaction timeouts. Non-transactional distributed data stores may be very fast when colocated with the services (see [[wiki/concepts/source/implementation-metapatterns/mesh|*Space-Based Architecture*]]) but the resource consumption becomes very high because of the associated data duplication (as every instance of each service gets a copy of the entire dataset) and simultaneous writes may corrupt the data (cause inconsistencies or merge conflicts). ### Dependencies Normally, every service depends on the repository. If the repository does not provide notifications on changes to the data, the services may need to communicate directly, in which case they will also depend on each other through [[wiki/concepts/source/foundations-of-software-architecture/choreography|*choreography*]] or *mutual* [[wiki/concepts/source/foundations-of-software-architecture/orchestration|*orchestration*]]. ![If the shared repository supports notifications, services depend only on the repository. Otherwise each service also depends on its event sources.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Dependencies/SharedRepository-1.png) Any dependencies on the repository technology and the data schema are dangerous for long-running projects as both of them may need to change sooner or later. Decoupling the code from the data storage is done with [yet another layer of indirection](https://en.wikipedia.org/wiki/Fundamental_theorem_of_software_engineering) which is called a [[wiki/concepts/source/extension-metapatterns/proxy|*Database Abstraction Layer*]] (*DAL*), a *Database Access Layer* \[[wiki/concepts/source/appendices/books-referenced|[POSA4]]\], or a *Data Mapper* \[[wiki/concepts/source/appendices/books-referenced|[PEAA]]\]. The DAL, which translates between the data schema and database’s API on one side and the business logic’s SPI on the other side, may reside inside each service or wrap the database: ![Each service may have a private Database Abstraction Layer, or there may be one shared Database Abstraction Layer colocated with the shared repository.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Dependencies/SharedRepository-2.png) Still, the DAL does not remove shared dependencies and only adds some flexibility. It seems that there is a peculiar kind of coupling through shared components: if one of the services needs to change the database schema or technology to better suit its needs, it is unable to do so because other components rely on (and exploit) the old schema and technology. Even deploying a second database, private to the service, is often not an option, as there is no convenient way to keep the databases in sync. ### Applicability *Shared Repository* is good for: - *Data-centric domains.* If most of your domain’s data is used in every subdomain, keeping any part of it private to a single service will be a pain in the system design. Examples include a [[wiki/concepts/source/foundations-of-software-architecture/shared-data|ticket reservation system]] and even the minesweeper game. - *A scalable service.* When you run several [[wiki/concepts/source/basic-metapatterns/shards|instances]] of a service, like in [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]], the instances are likely to be identical and stateless, with the service’s data pushed out to a database shared among the instances. - *Huge datasets*. Sometimes you may need to deal with a lot of data. It is unwise (meaning expensive) to stream and replicate it between your services just for the sake of ensuring their isolation. Share it instead. If the data does not fit in an ordinary database, some kind of [[wiki/concepts/source/implementation-metapatterns/mesh|*Space-Based Architecture*]] (which [was invented to this end](https://en.wikipedia.org/wiki/Space-based_architecture#History)) may become your friend. - *Quick simple projects.* Don’t over-engineer if the project won’t live long enough to benefit from its flexibility. You may also save a buck or two on the storage. *Shared Repository* is bad for: - *Quickly evolving, complex projects.* As everything changes, you just cannot devise a stable schema, while every change of the database schema breaks all the services. - *Varied forces and algorithms*. Different services may require different kinds of data stores to work efficiently. - *Big data with random writes*. Your data does not fit on a single server. If you want to avoid write conflicts, you must keep all the database nodes synchronized, which kills performance. If you let them all broadcast their changes asynchronously, you get collisions. You may want to first decouple and [[wiki/concepts/source/basic-metapatterns/shards|*shard*]] the data as much as possible, and then turn your attention to esoteric data stores, specialized caches, and even tailor-made [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] to get out of the trouble. ### Relations ![A shared repository for Services, Shards, and Service-Oriented Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Relations/Shared%20Repository.png) *Shared Repository*: - Extends [[wiki/concepts/source/basic-metapatterns/services|*Services*]], [*Service-Oriented Architecture*](), [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]], or occasionally [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]]. - Is a part of a [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]], [[wiki/concepts/source/extension-metapatterns/middleware|persistent *Middleware*]], or [[wiki/concepts/source/basic-metapatterns/pipeline|*Nanoservices*]]. - Is [closely related](https://itnext.io/a-practical-guide-to-modular-monoliths-with-net-59da23c01137) to [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]]. - May be implemented by a [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]]. ## Examples *Shared Repository* is a sibling of [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]]. While a *Middleware* assists direct communication between services (*shared-nothing* messaging), a *Shared Repository* grants them indirect communication through access to an external state (similar to *shared memory*) which usually stores all the data for the domain. A *Shared Repository* may provide a generic interface (e.g. SQL) or a custom API (with a domain-aware [[wiki/concepts/source/extension-metapatterns/proxy|*Adapter*]] / [*ORM*](https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping) for the database). The *repository* itself can be anything ranging from a trivial OS file system or a memory block accessible from all the components to an ordinary database to a [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]]-based, distributed [*tuple space*](https://en.wikipedia.org/wiki/Tuple_space): - A [*Shared Database*](#shared-database-integration-database-data-domain-database-of-service-based-architecture), [*Shared File System*](#shared-file-system), and [*Shared Memory*](#shared-memory) are just what you think they are. - A [*Blackboard*](#blackboard) provides a shared solution space for several algorithms to cooperate on a task. - A [*Data Grid*](#data-grid-of-space-based-architecture-sba-replicated-cache-distributed-cache) is a distributed in-memory data store that replicates data among many instances of multiple services. - A [*Persistent Event Log*](#persistent-event-log-shared-event-store) stores every message sent between services for a possible future use. - [*Stamp Coupling*](#inexact-stamp-coupling) is an approach for collecting data spread over the components of a *Pipeline*. ### Shared Database, Integration Database, Data Domain, Database of [[wiki/concepts/source/extension-metapatterns/sandwich|Service-Based Architecture]] ![Several services access a shared database and optionally communicate with each other directly.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Shared%20Database.png) *Shared Database* \[[wiki/concepts/source/appendices/books-referenced|[EIP]]\], [*Integration Database*](https://martinfowler.com/bliki/IntegrationDatabase.html)*,* or *Data Domain* \[[wiki/concepts/source/appendices/books-referenced|[SAHP]]\] is a single database available to several [[wiki/concepts/source/basic-metapatterns/services|services]]. The services may subscribe to data change triggers in the database itself or notify each other directly about domain events. The latter is often the case with [[wiki/concepts/source/basic-metapatterns/services|*Service-Based Architecture*]] which consists of large services dedicated to subdomains. ### Shared File System ![An algorithm processes a batch of input files and writes output files. Its output becomes an input for another algorithm. The algorithms make a pipeline.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Shared%20files.png) As a file system is a kind of shared dictionary, writing and reading files can be used to transfer data between applications. A [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|data processing]] [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]] which stores intermediate results in files benefits from the ability to restart its calculation from the last successful step because files are persistent \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\]. ### Shared Memory ![Areas of shared memory (ring buffers) between two processes make a pair of event channels.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Shared%20memory.png) Several actors (processes, modules, device drivers) communicate through one or more mutually accessible data structures (arrays, trees, or dictionaries). Accessing a shared object may require some kind of synchronization (e.g. taking a *mutex*) or employ [*atomic variables*](https://codescoddler.medium.com/concurrency-made-simple-the-role-of-atomic-variables-8327b9b35023). Notwithstanding that communication via *shared memory* is the archenemy of ([*shared-nothing*](https://www.scylladb.com/glossary/shared-nothing-architecture/)) messaging it is actually used to implement messaging: high-load multi-process systems (web browsers and high-frequency trading) rely on shared memory *mailboxes* for messaging between their [[wiki/concepts/source/basic-metapatterns/services|constituent processes]]. ### [[wiki/concepts/source/extension-metapatterns/sandwich|Blackboard]] ![A Blackboard System includes a control which orchestrates knowledge sources which access a blackboard with shared data.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Blackboard.png) [*Blackboard*](https://hillside.net/plop/plop97/Proceedings/lalanda.pdf) \[[wiki/concepts/source/appendices/books-referenced|[POSA1]], [[wiki/concepts/source/appendices/books-referenced|POSA4]]\] was used for non-deterministic calculations where several algorithms were concurring and collaborating to gradually build a solution from incomplete inputs. The *control* ([[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]]) component schedules the work of several *knowledge sources* ([[wiki/concepts/source/basic-metapatterns/services|*Services*]]) which encapsulate algorithms for processing the data stored in the *blackboard* (*Shared Repository*) named after the well-known collaborative tool used for a brainstorming session. This approach has mostly been superseded by convolutional neural networks. Examples: several use cases are [mentioned on Wikipedia](https://en.wikipedia.org/wiki/Blackboard_system). ### Data Grid of [[wiki/concepts/source/extension-metapatterns/sandwich|Space-Based Architecture]] (SBA), [[wiki/concepts/source/extension-metapatterns/proxy|Replicated Cache, Distributed Cache]] ![A layer of scaled processing units each connected to a node of an in-memory database over a data replication engine which communicates with a persistent database through readers and writers.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Data%20Grid.png) The [*Space-Based Architecture*](https://en.wikipedia.org/wiki/Space-based_architecture) (*SBA*) \[[wiki/concepts/source/appendices/books-referenced|[SAP]], [[wiki/concepts/source/appendices/books-referenced|FSA]]\] is a [[wiki/concepts/source/implementation-metapatterns/mesh|*Service Mesh*]] (a [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]]-based [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] with at least one [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]] per service instance) which also implements an in-memory [*tuple space*](https://en.wikipedia.org/wiki/Tuple_space) (shared dictionary). Although it does not provide a full-featured database interface it has very good performance, elasticity, and fault tolerance, while some implementations allow for dealing with datasets which are much larger than anything digestible by ordinary databases. Its drawbacks include write collisions and high operating costs (huge traffic for data replication and lots of RAM to store the [[wiki/concepts/source/basic-metapatterns/shards|replicas]]). The main components of the architecture are: - *Processing Units* – the [[wiki/concepts/source/basic-metapatterns/services|*Services*]] with the business logic. There may be one class of *Processing Units*, making *SBA* look like [[wiki/concepts/source/basic-metapatterns/shards|*Replicated Load-Balanced Services*]], or multiple classes, in which case the architecture becomes similar to [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]] with a *Shared Database*. - *Data Grid* (*Replicated Cache* \[[wiki/concepts/source/appendices/books-referenced|[SAHP]]\]) – a [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]]-based in-memory data store. Each node of the *Data Grid* is co-located with a single instance of a *Processing Unit*, providing the latter with very fast access to the data. Changes to the data are replicated across the grid by its virtual *Data Replication Engine* which is usually implemented by every node of the grid. - *Persistent Database* – an external database which the *Data Grid* replicates (caches). Its schema is encapsulated in the *Readers* and *Writers*. - *Data Readers* – components that read any data not found in the *Data Grid* from the *Persistent Database*. Most setups employ *Readers* upon starting the system to upload the entire contents of the database into the memory of the nodes. - *Data Writers* – components that replicate the changes done in the *Data Grid* to the persistent storage to assure that no updates are lost if the system is shut down. There can be a pair of *Reader* and *Writer* per class of *Processing Units* (subdomain) or a global pair that processes all read and write requests. *SBA* provides nearly perfect scalability (high read and write throughput as all the data is [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|cached]]) and elasticity (new instances of *Processing Units* are created and initialized very quickly as they copy their data from already running units with no need to access the *Persistent Database*). Though for smaller datasets the entire database is [[wiki/concepts/source/basic-metapatterns/shards|replicated]] to every node of the grid (the *Replicated Cache* mode), *Space-Based Architecture* also allows for processing datasets that don’t fit into the memory of a single node by assigning each node a [[wiki/concepts/source/basic-metapatterns/shards|*shard*]] of the dataset (the *Distributed Cache* mode). The drawbacks of this architecture include: - Structural and operational complexity. - Very basic dictionary-like interface of the *tuple space* (no joins or other complex operations). - High traffic for data replication among the nodes. - Data collisions if multiple clients change the same piece of data simultaneously. ### [[wiki/concepts/source/extension-metapatterns/middleware|Persistent Event Log, Shared Event Store]] ![A service posts a message to a shared event log which both persists the message to a shared event store and forwards the message to other services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Shared%20Database%20-%20Event%20Log.png) A data store for events (an *event log* for interservice events or an *event store* for internal state changes) [[wiki/concepts/source/foundations-of-software-architecture/shared-data|can be used]] as a [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]]: an event producer writes its events to a topic in the repository while the event consumers get notified as soon as a new record appears. ### (inexact) Stamp Coupling ![A message collects pieces of data while passing through a pipeline.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/2/Stamp%20Coupling.png) *Stamp Coupling* \[[wiki/concepts/source/appendices/books-referenced|[SAHP]]\] happens when a single data structure passes through an entire [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]], with separate fields of the data structure matching individual processing steps. A [[wiki/concepts/source/foundations-of-software-architecture/choreography|*choreographed*]] system with no shared databases does not provide any way to aggregate the data spread over its multiple services. If we need to collect everything known about a user or purchase, we pass a query message through the system, and every service appends to it whatever it knows of the subject, just as administrative offices used to rubber stamp the paper documents which passed through them. The unified message becomes a kind of virtual (temporary) *Shared Repository* which the services (*Content Enrichers* according to \[[wiki/concepts/source/appendices/books-referenced|[EIP]]\]) write to. This also manifests in the dependencies: all the services [[wiki/concepts/source/foundations-of-software-architecture/choreography|depend on the format of the query message]] as they would on the schema of a *Shared Repository*, instead of depending on one of their neighbors, as is usual with *Pipelines*. ## [[wiki/concepts/source/appendices/evolutions-of-a-shared-repository|Evolutions]] Once a database appears, it is unlikely to go away. I see the [[wiki/concepts/source/appendices/evolutions-of-a-shared-repository|following evolutions]] to improve performance of the data layer: - [[wiki/concepts/source/basic-metapatterns/shards|Shard]] the database. ![The shared database is sharded so that each database instance holds a subset of data,](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Shared%20Database_%20Shard.png) - Use [[wiki/concepts/source/implementation-metapatterns/mesh|*Space-Based Architecture*]] for dynamic scalability. ![The shared database is migrated to a Data Grid, resulting in Space-Based Architecture](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Shared%20Database%20to%20Space-Based%20Architecture.png) - Divide the data into private databases. ![The shared database is split into databases dedicated to subdomains, resulting in Layered Services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Shared%20Database%20to%20Services.png) - Deploy specialized data stores ([[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]]). ![The shared database is migrated to specialized databases.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/2/Shared%20Database%20to%20Polyglot%20Persistence.png) ## Summary A *Shared Repository* encapsulates a system’s data, allowing for [[wiki/concepts/source/foundations-of-software-architecture/shared-data|data-centric]] development and kickstarting [[wiki/concepts/source/basic-metapatterns/services|*Service-Based*]] architectures through simplifying interservice interactions. Its downsides are a frozen data schema and limited performance. --- title: "Arranging communication" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Foundations of software architecture/Arranging communication/Arranging communication.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Foundations%20of%20software%20architecture/Arranging%20communication/Arranging%20communication source_license_note: "See namespace README; preserve attribution and source links." --- # Arranging communication > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Foundations of software architecture/Arranging communication/Arranging communication.md`. As a project grows, it tends to become subdivided into services, modules, or whatever you call the [[wiki/concepts/source/basic-metapatterns/services|components that match its subdomains]] (or *bounded contexts*, if you prefer the \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\] convention). Still, there remain system-wide use cases that require collaboration from many or all of the system’s parts – otherwise the components don’t even form a single system. Let’s see how they can be integrated. ![A monolithic system is subdivided into several services but it is an open question how the resulting components should be integrated.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Monolith%20to%20Services.png) As integration is not unique to distributed systems – it is present even in smaller programs that need to make data, functions, and classes work together – we’ll take a look at programming and architectural paradigms next. ## Contents: - [[wiki/concepts/source/foundations-of-software-architecture/programming-and-architectural-paradigms|Programming and architectural paradigms]] - [[wiki/concepts/source/foundations-of-software-architecture/orchestration|Orchestration]] - [[wiki/concepts/source/foundations-of-software-architecture/choreography|Choreography]] - [[wiki/concepts/source/foundations-of-software-architecture/shared-data|Shared data]] - [[wiki/concepts/source/foundations-of-software-architecture/comparison-of-communication-styles|Comparison of communication styles]] --- title: "Choreography" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Foundations of software architecture/Arranging communication/Choreography.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Foundations%20of%20software%20architecture/Arranging%20communication/Choreography source_license_note: "See namespace README; preserve attribution and source links." --- # Choreography > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Foundations of software architecture/Arranging communication/Choreography.md`. Another integration option, named *choreography* after seemingly spontaneous interactions between dancers, is to build a [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]] to pass every client’s request through a chain of components: ![After a monolith is subdivided into services, the services are assembled into a pipeline.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Services%20to%20Pipeline.png) In that case there is no owner for *workflows* – each request is just a data packet which is transformed multiple times as it passes through the *Pipeline*. Debugging is mostly limited to reading logs as there is no dedicated component to connect a debugger to for single-step execution of a use case. Nor is there a single piece of code to define each of the system-wide scenarios – their logic emerges from the graph of event channels between services and from message types that each involved event handler sends. Maintaining the consistency of the services’ states is the responsibility of the services themselves as there is none to supervise them. On the bright side, there is no communication overhead caused by response messages as there are no responses – the processing cost is one message per service, half of the cost for an orchestrated architecture. Still, messages in choreographed systems tend to be longer than those used with [[wiki/concepts/source/foundations-of-software-architecture/orchestration|orchestration]] as each message needs to carry the entire request’s state – there is no [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] to own the state and distribute parts of the request’s payload among involved services. ![A request collects data from every service in a pipeline as it passes those services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Pipeline%20Enricher.png) Latency may also be suboptimal as parallelizing execution of a request is easier said than done because in a purely choreographed system there is no place (called *Aggregator* \[[wiki/concepts/source/appendices/books-referenced|[EIP]]\]) to collect multiple related messages, which also means that there is no associated cost in resources (RAM and CPU time) for storing their payloads. Please note that an *Aggregator*, when added, starts orchestrating the system – it stands between the client and services and meddles with the traffic and logic. It spends resources to store the received messages for aggregation, and the messages start forming request/confirm pairs – which are characteristic of orchestration. ![An Orchestrator can run subrequests in parallel which is impossible for a sequential pipeline.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Pipeline%20Not%20Parallel.png) Still another trouble with choreography comes from its weakness in error processing. When a service in the middle of a request processing pipeline encounters an error, it cannot generate the normal output which would have been sent further downstream. One option is to fill in a null (or error) value but in that case each receiver of the message should remember to check for null and know how to deal with the error. Another way is adding a dedicated error channel for each service to push failed requests into, but that complicates the high-level system’s architecture. Moreover, a failure in the middle of processing a request may cause the services to end up with inconsistent data if no special attention (i.e. a new kind of request to compensate the original one) is paid to roll back the partial change. Please note that all of the above is comfortably handled by an *Orchestrator*. Essentially, the exception handling, which an *Orchestrator* covers within its code, in a choreographed system escalates to the system’s architecture level. ![Rollback of changes done by services arranged into a pipeline.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Pipeline%20Error.png) ## Early response The ordinary mode of action for a pipeline – sending the final results of processing to the client – requires either for the tail of the pipeline to send data to its head or for existence of a stateful intermediate component – [[wiki/concepts/source/extension-metapatterns/proxy|*Gateway*]] – to receive the client’s request, forward it to the head of the pipeline, wait on the pipeline’s tail for the result of processing, and return it to the client. That is necessary because a client would usually open a single connection which is impossible to share between multiple services, namely the (receiving) head and (sending) tail of the pipeline. ![The component that receives a client request should send back the response. It can be a dedicated Gateway or the first service of a looped pipeline.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Pipeline%20Gateway.png) The gateway, if used, may parallelize processing of [scatter-gather](https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/scatter-gather.html) requests by turning into an [[wiki/concepts/source/extension-metapatterns/orchestrator|*API Gateway*]] which is a kind of *Orchestrator*. Which means that the system changes its paradigm from choreography to orchestration. ![An API Gateway runs subrequests in parallel while a pipeline runs them consecutively by passing a message through a chain of services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Gateway%20to%20API%20Gateway.png) It is possible to avoid both adding a *Gateway* and having the cyclic dependency if clients don’t immediately need the final results of processing their requests. In such a case the service which receives the original request does its (first) step of processing, sends the response to the client, and then notifies services down the pipeline. Though such a use case seems to be unlikely, it happens in real life, for example, with pizza delivery. As soon as a buyer fills in their contact details and pays for the food, the order can be confirmed and forwarded to the kitchen. When it is ready it’s forwarded to the delivery, and finally the physical goods appear at the buyer’s door. ![The first service of a pipeline responds to the client immediately while forwarding the client's request to other services, which will eventually produce the result.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Pipeline%20Early%20Response.png) *Early response* allows for choreography to shine in its purest form: with extensibility, high performance, but also high latency. A similar approach may be used in [[wiki/concepts/source/basic-metapatterns/services|*Service-Based Architecture*]] (aka *Macroservices*) [for communication between the services](https://learn.microsoft.com/en-us/azure/architecture/patterns/choreography) (*bounded contexts* \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]) if they only need to notify each other of events without waiting for responses. ## Dependencies A pipeline may be built with downstream or upstream dependencies or with a shared schema. If services communicate through commands, each service depends on all the direct destinations of its commands as it must know each of the APIs which it uses. This mode of communication is mostly used with [[wiki/concepts/source/basic-metapatterns/services|*Actors*]] that power embedded, telecom, messengers, and some banking systems. Downstream dependencies make it easy to add input chains (upstream services that deal with new hardware or external clients) although changing anything at the output end of the pipeline is going to break the input parts that send messages to the component changed. ![Adding an upstream component in a command-based pipeline.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Downstream%20Dependencies.png) Upstream dependencies come from the [publish/subscribe](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) model ([*Event Collaboration*](https://martinfowler.com/eaaDev/EventCollaboration.html)) where each service broadcasts notifications to any interested subscriber about what it has done. This way of building systems engines [[wiki/concepts/source/basic-metapatterns/pipeline|*Event-Driven Architecture*]] which is used in high-load backends. Extending or truncating an already implemented request processing tree is as easy as adding or removing subscribers to existing events but the creation of a new event source will require changes in the downstream components. The easy addition of downstream branches supports new customer experiences and analytical features which businesses are hungry for. ![Downstream services are easily added to a pub/sub pipeline, turning it into a tree.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Upstream%20Dependencies.png) The final option is for the entire pipeline to use a uniform message format ([[wiki/concepts/source/extension-metapatterns/shared-repository|*Stamp Coupling*]]) which often contains one dedicated field per service involved. This way a service depends only on the message header (with the list of the fields and a record id) and the format of the single field it reads (stores data) or writes (retrieves data as a *Content Enricher* \[[wiki/concepts/source/appendices/books-referenced|[EIP]]\]). That works well with system-wide queries but binds all the services to the schema of the message in a way similar to accessing a shared database (to be discussed [[wiki/concepts/source/foundations-of-software-architecture/shared-data|below]]). Such an architecture decouples the services to the extent that any of them can be freely added or removed, together with the message field(s) it fills or reads. ![Each component depends on the message header and the message field(s) it accesses.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Shared%20Message%20Format.png) ![A service in a pipeline with a shared message format can be replaced with another service if the message fields which it uses are also replaced.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Add%20Remove%20with%20Shared%20Message.png) A peculiar feature of choreography is the ability to cut and cross-link pipelines with compatible interfaces by changing a single service (or even system configuration if you build it with communication channels). That gives it a lot of flexibility – as long as you can comprehend all the dependencies (and channels) in the system, which becomes non-trivial as it grows. ![Cross-linking independent pipelines by establishing new data or event streams.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Cross-link%20Pipeline.png) ## Multi-choreography It is very common for a service to participate in multiple pipelines, especially if it owns a database – as there should be a use case which fills in the data and at least one other scenario which reads from that database. Each pipeline makes the service depend on one or more of the interfaces it communicates with, which often belong to multiple services, thus increasing the coupling between system components and impairing future structural changes. ![A set of services participates in multiple pipelines.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Multi-choreography.png) ## Summary Overall, choreography seems to be a lightweight approach that prioritizes throughput over latency and is suitable for highly-loaded scenarios of limited complexity. However, a choreographed system will likely become unintelligible if it is made to support more than a few use cases. There is a decent [overview from Microsoft](https://learn.microsoft.com/en-us/azure/architecture/patterns/choreography). --- title: "Comparison of communication styles" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Foundations of software architecture/Arranging communication/Comparison of communication styles.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Foundations%20of%20software%20architecture/Arranging%20communication/Comparison%20of%20communication%20styles source_license_note: "See namespace README; preserve attribution and source links." --- # Comparison of communication styles > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Foundations of software architecture/Arranging communication/Comparison of communication styles.md`. We have briefly discussed three approaches to communication: orchestration, choreography, and shared data. Let’s recall when it makes the most sense to use each of them. - [[wiki/concepts/source/foundations-of-software-architecture/orchestration|*Orchestration*]] is built around [[wiki/concepts/source/basic-metapatterns/layers|use cases]]. They are easy to program and add, no matter how complex they become. Thus, if your (sub)domain is coupled, or your understanding of it is still evolving, this is the way to go, as you will be able to change the high-level logic in any imaginable way because you express it as convenient imperative code. - [[wiki/concepts/source/foundations-of-software-architecture/shared-data|*Shared data*]] is all about… er… domain data. If you really (believe that you) know your domain, and it deals with coupled data – this is your chance. You may even add in an *Orchestrator* if there are use cases that involve multiple subdomains. The business logic is going to be easy to extend while changes to the data schema are sure to cause havoc. - [[wiki/concepts/source/foundations-of-software-architecture/choreography|*Choreography*]] pays off for weakly coupled domains with a few simple use cases. It has good performance and flexibility, but lacks the expressive power of orchestration and becomes very messy as the number of tasks and components grows. It works best with independent teams and delayed processing – when users are not waiting for the final results of their actions. There is advice [from Microsoft](https://learn.microsoft.com/en-us/azure/architecture/patterns/choreography) and \[[wiki/concepts/source/appendices/books-referenced|[DEDS]]\] which makes perfect sense: use choreography for communication between *bounded contexts* (subdomains) \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\] but revert to orchestration (or maybe shared data) inside each context. Indeed, subdomains are likely to be loosely coupled while most user requests don’t traverse subdomain boundaries – which kindles hope that their interactions are few and not time-critical. If we follow the advice, we get [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Cell-Based Architecture*]] ([WSO2 definition](https://github.com/wso2/reference-architecture/blob/master/reference-architecture-cell-based.md)), which collects the best of two worlds: orchestration and/or shared data for strongly coupled parts and choreography between them. ![A diagram of the Cell-Based Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Cell-Based%20Architecture.png) By the way, you could have noticed a few odd cases: - An [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] in a [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|control system]] does not run scenarios and its mode of action resembles choreography. - A choreographed system may use a [[wiki/concepts/source/extension-metapatterns/shared-repository|shared message format]], which makes it resemble a system with shared data, even though no shared database is present. - A shared database may be used to [[wiki/concepts/source/extension-metapatterns/shared-repository|implement messaging]] for an orchestrated or choreographed system, effectively becoming a [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]]. That likely means that our distinction between the modes of communication is a bit artificial and there exists a yet unknown deeper model to look for. --- title: "Forces, asynchronicity, and distribution" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Foundations of software architecture/Forces, asynchronicity, and distribution.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Foundations%20of%20software%20architecture/Forces%2C%20asynchronicity%2C%20and%20distribution source_license_note: "See namespace README; preserve attribution and source links." --- # Forces, asynchronicity, and distribution > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Foundations of software architecture/Forces, asynchronicity, and distribution.md`. Many systems rely on asynchronous communication between their components or are distributed over a network. Why is dividing a system into modules or classes then not enough in real life? ## Requirements and forces Any software is built to meet a set of (explicit or implicit) *requirements*. As a bare minimum, you as a programmer must have at least a vague vision of how your software is expected to operate. At the most, business analysts bring you volumes of incomprehensible documentation they wrote for the sole purpose of forcing you to practice *Domain-Driven Design* \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]. Some requirements are *functional*, others are *non-functional*. *Functional requirements* describe what the system must do: a night vision device must be able to represent heat radiation as a video stream; a multiplayer game must create a shared virtual world for users to interact with over a network; a tool for formatting floppies … er, formats floppies. [*Non-functional requirements*](https://en.wikipedia.org/wiki/Non-functional_requirement) (*NFR*s) define the expected qualities of the system and are known to drive architectural decisions \[[wiki/concepts/source/appendices/books-referenced|[POSA1]], [[wiki/concepts/source/appendices/books-referenced|POSA5]]\]. They may be formulated or implied: “our game should be fast enough and stable enough”. “A medical application should be extremely well-tested”. “An online shop should provide an easy way to add new goods”. Notice all those “fast enough”, “stable enough”, “well”, and “easy” terms on the wishlist – those are *soft* requirements, open to subjective interpretation and compromises. There are also *hard* NFRs, either as an [SLA](https://en.wikipedia.org/wiki/Service-level_agreement) with numbers: “your service should be available 99.999% of the time” or as (presumably) verifiable statements: “no user should ever see the personal data of another user”. *Forces* – the drivers of architectural decisions – include both non-functional requirements and the business reality: it makes little sense to start a year-long development project if competitors are expected to have their products ready in three months, and you cannot plan for five teams if your budget is limited to two. Let’s take an example. A night vision surveillance camera may spend seconds compressing its video stream to limit the required network bandwidth – this kind of system sacrifices low latency in favor of low traffic. The device will need a fast CPU (probably a DSP) and lots of RAM to store multiple frames for efficient compression. A night vision camera of a drone should have moderately low latency as the drone (and likely its operator) uses the video stream for navigation. Thus it should send out every frame immediately, except that it may still spend some time compressing the frame to JPEG to achieve a balance between latency and bandwidth. Pushing for extremely low latency of the camera does not help much because the whole system is limited by the delay of the radio communication and the human operator in the loop. Night vision goggles or helmets are [stringent on latency](https://ntrs.nasa.gov/api/citations/20050192646/downloads/20050192646.pdf) to the extent which no ordinary digital system satisfies, thus [expensive analog devices](https://gloomgroup.com/blogs/night-vision-info/night-vision-digital-vs-analog) have to be used. Here we see how forces – namely, latency, bandwidth, and cost – impact all the stuff all the way down to hardware. The same happens with multiplayer games: while a chess client is a simple web page, a fighting tournament or a first-person shooter is very likely to need a client-installed application that processes much of the game logic locally while relying on a highly customized network protocol to decrease communication latency. Another example is the choice of programming language: you can quickly write your system in Java or Python sacrificing its performance or you can spend much more time with C or C\+\+ and manual optimization to achieve top performance at the cost of development speed. And your choice also depends on which programmers are readily available: if there are C\+\+ programmers waiting on the bench while those with Java skills would need to be hired, that may well shift your decision towards writing the project in C\+\+ because that will give you a flying start. ## Conflicting forces We see that forces influence architecture. That becomes way more interesting when a system is shaped by conflicting forces – the ones that, though opposing each other, still need to be met by the architecture. Remember how old Windows used to freeze on formatting a floppy or when it encountered one with a bad cluster? Let’s see how such things could have happened (though [the real cause was a bit different](https://sudonull.com/post/124038-Why-did-Windows-95-freeze-when-formatting-a-floppy-disk), it also came from the modules’ sharing a context). ![Synchronous communication freezes the system UI and format windows while a driver formats a floppy.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Intro/Floppy-Sync.png) The system implements the function it was made for – it formats floppies. However, while the low-level module is busy interacting with the hardware, all the modules above it have no chance to run because they have called the driver and are waiting for it to return. The modules are there, with the code separated into bounded contexts (the UI does not need to care about sectors and FATs) but all of them share non-functional qualities – latency in this case. Either the UI is responsive or the floppy driver runs a long-running action. We need the UI and the driver to execute independently. ## Asynchronous communication If the components cannot communicate directly (call each other and wait for the results returned) how should they interact? Through an intermediary where one of them leaves a message for another. Such an intermediary may be a message queue, a pub/sub channel, or even a data record in [[wiki/concepts/source/extension-metapatterns/shared-repository|shared memory]]. The sender posts its message and continues its routine tasks. The receiver checks for incoming messages whenever it has a free time slot. Behold multithreading in action! ![Asynchronous communication unblocks the system UI and format windows while a driver formats a floppy.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Intro/Floppy-Async.png) ## Distribution Once modules run independently, we can separate them into processes and even distribute the processes over multiple computers. That is required to address fault tolerance and high availability and solve conflicts around scaling or locality. Consider a web site. Most of them follow [*Three-Tier Architecture*](https://en.wikipedia.org/wiki/Multitier_architecture#Three-tier_architecture): - A *frontend* runs in users’ browsers. - A *backend* runs on the business owner’s servers. - A *database* usually runs on a single powerful server. This common division makes quite a lot of sense: ![Frontend, backend, and database layers differ in their scalability, security, and operational costs.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Intro/3-Tier.png) Websites are accessed by many users simultaneously. Any business owner wants to pay less for his servers, thus as much work as possible is offloaded to the users’ web browsers which provide unlimited resources for free (from the business owner’s viewpoint). Here we have a nearly perfect scalability – the business owner pays only for the traffic. Other parts of the software are business-critical and should be protected from hacking. Such ones are kept on private servers or in a cloud. This means that the business owner pays for the servers but still may scale their application by flooding it with money. The deepest layer – the database – is nontrivial to scale. Distributed databases are expensive, consume a lot of traffic, and still scale only to a limited extent. It often makes more sense to buy or rent top-tier hardware for a single database server than to switch over to a distributed database. This is a good example of how the physical distribution of a system solves the scalability, security, and cost conflict by choosing the best possible combination of forces for each module. Whatever is not secure scales for free. Whatever does not scale gets assigned expensive hardware. Whatever remains is in between. Another example comes from IoT – a fire alarm system. They tend to use 3 tiers as well: - *Sensors* (smoke or fire detectors) and *actuators* (fire suppression, sirens, etc.). - A *field gateway* – a kind of router the sensors and actuators are directly connected to. - A *control panel* – some place where operators drink their coffee. Sensors and actuators are cheap and energy-efficient but too dumb to act on their own – they do not react to events unless explicitly commanded to do so. The control panel is where all the magic happens, but it may be unreachable if the network is damaged or the wireless communication is jammed. Field gateways stand in between: they collect information from the sensors, aggregate it to save on traffic, communicate with the control panel, and even activate actuators if the control panel is unreachable. In this case a part of the business logic is installed into the dedicated devices which are located within the controlled building. Here reliability conflicts with accuracy: a human operator makes an accurate estimate of the threat and chooses an appropriate action, but it is not granted that we can always reach the operator. Thus to be reliable we add an inaccurate but trustworthy fallback reaction. ![Normal and autonomous operation of a field gateway of a fire alarm system.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Intro/Field%20Gateway.png) A similar pattern can be found with robotics, drones or even computer hardware (e.g. a HDD): dedicated peripheral controllers supervise their managed devices in real time while a more powerful but less interactive central processor drives the system as a whole. ## The goods and the price Let’s review what we found out. Modules make it easier to reason about the system, enable development by multiple teams in parallel, and resolve some conflicts between forces. For example, development speed against performance or release frequency against stability are solved by choosing a programming language and release management style on a per module basis. The cost is the loss of some options for performance optimization between modules and the extra cognitive load while debugging a module you are not familiar with. Asynchronous communication is a step forward from modules that solves more conflicts between forces. It addresses latency and multitasking. We pay for that with context switches and the need to copy and serialize data transferred in messages, making communication between participating modules slower. Debugging asynchronous communication becomes non-trivial as one cannot single-step in the debugger from the message sender into the message handler. Distribution builds upon asynchronous communication (as networks are asynchronous) and decouples participant components in such qualities as scalability, security and locality. It separates release cycles of the services involved and makes it possible for the system to recover from failures of some of its components. The price? Even slower and more complicated communication in the now distributed system (networks are quite laggy and unreliable) and extremely inconvenient debugging as you need to connect to multiple components over the network. We see that the more isolated our components become, the more their qualities are decoupled and the more flexible the resulting system grows. But this very same decoupling devastates the system’s performance and makes debugging into a nightmare. Any moral? There is one, even a few. - [Do not overisolate](https://martinfowler.com/bliki/MonolithFirst.html). Go asynchronous or distributed only if you are *force*d to. Especially if you are actively evolving your system. Especially in an unfamiliar domain. - Cohesive logic belongs together. If you split it among asynchronous or distributed components, it may become very hard to debug. - Components that communicate a lot should reside together. Distributing them may kill performance and even break the consistency of the data. --- title: "Foundations of software architecture" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Foundations of software architecture/Foundations of software architecture.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Foundations%20of%20software%20architecture/Foundations%20of%20software%20architecture source_license_note: "See namespace README; preserve attribution and source links." --- # Foundations of software architecture > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Foundations of software architecture/Foundations of software architecture.md`. This part defines some ideas which are used throughout the book: - [[wiki/concepts/source/foundations-of-software-architecture/modules-and-complexity|Complexity]] and its relation to modules, coupling and cohesion. - [[wiki/concepts/source/foundations-of-software-architecture/forces-asynchronicity-and-distribution|Forces]] (including non-functional requirements), their conflicts, and how those are resolved through asynchronous communication and distribution of system components. - Different [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|kinds of software systems]]: [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|control]], [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|interactive]], [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|streaming]], and [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|computational]]. - [[wiki/concepts/source/foundations-of-software-architecture/arranging-communication|Communication paradigms]]: [[wiki/concepts/source/foundations-of-software-architecture/orchestration|orchestration]], [[wiki/concepts/source/foundations-of-software-architecture/choreography|choreography]] and [[wiki/concepts/source/foundations-of-software-architecture/shared-data|shared data]]. Please feel free to skip (through) it as you probably know most of them quite well. ## Contents: - [[wiki/concepts/source/foundations-of-software-architecture/modules-and-complexity|Modules and complexity]] - [[wiki/concepts/source/foundations-of-software-architecture/forces-asynchronicity-and-distribution|Forces, asynchronicity, and distribution]] - [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|Four kinds of software]] - [[wiki/concepts/source/foundations-of-software-architecture/arranging-communication|Arranging communication]] - [[wiki/concepts/source/foundations-of-software-architecture/programming-and-architectural-paradigms|Programming and architectural paradigms]] - [[wiki/concepts/source/foundations-of-software-architecture/orchestration|Orchestration]] - [[wiki/concepts/source/foundations-of-software-architecture/choreography|Choreography]] - [[wiki/concepts/source/foundations-of-software-architecture/shared-data|Shared data]] - [[wiki/concepts/source/foundations-of-software-architecture/comparison-of-communication-styles|Comparison of communication styles]] --- title: "Four kinds of software" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Foundations of software architecture/Four kinds of software.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Foundations%20of%20software%20architecture/Four%20kinds%20of%20software source_license_note: "See namespace README; preserve attribution and source links." --- # Four kinds of software > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Foundations of software architecture/Four kinds of software.md`. Software products vary in their goals which, surprisingly, determine their structure and operation. The main features of software make a fuzzy continuous space rather than a strict set of well-defined classification options. Let’s examine two of its dimensions: ### Source of inputs Some systems receive their inputs from users via text commands or UI controls, often mediated by a network protocol. Such inputs are highly meaningful, structured and compact – processing a single user command often invokes a larger part of the program’s functionality. Another kind of system deals with binary data or signals that come from hardware. Those input sources have low informational payload – a digitized movie is orders of magnitude larger than a book written in a human language and still omits many details of the original text. Raw inputs often require context (the program’s state) to understand their meaning: the same sequence of bits may be treated as a part of a video, audio, executable file or archive – and the correct interpretation is known only from the command your program is running and the name and header of the file it is processing. ### Latency constraints Programs that control hardware or interface with users need to respond to their inputs in real time. Milliseconds of delay result in any kind of bad stuff that ranges from negative reviews and lost business opportunities to lost human lives (which are the same from the business PoV). That kind of software can never block its execution or run long calculations and has to keep all the data involved in memory. Other programs are not that time-constrained – they run a single task for a long time, accessing many files or consulting remote services in the process. They may use more powerful decision-making algorithms and all the data in the world – but they are slower to respond. Those dimensions make four corner cases that vary in architectural styles: ![Diagrams of control, interactive, streaming, and computational systems.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/4Kinds/4%20Kinds.png) ## Control (real-time, hardware input) ![A control system receives an event from a hardware component, processes it with a hardware driver, passes the result to a mediator, which calls another driver, which activates another hardware.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/4Kinds/Control%20-%20main.png) A *control* application supervises several hardware or software interfaces with the goal of keeping a certain system-wide *invariant*: - A fly-by-wire program receives readings from the airplane’s sensors and adjusts its [actuators](https://en.wikipedia.org/wiki/Actuator) to keep the vehicle on its course, which is the invariant. - When a telephony gateway receives an outgoing call request from one of the devices that it manages, it checks the dialed number, initiates an incoming call to a matching interface, and connects a voice channel between the devices as soon as the destination accepts the call. The invariant is that calls live in pairs: if a call comes in, it is to be rejected or a derived call should go out of the device. - A [[wiki/concepts/source/implementation-metapatterns/microkernel|*Container Orchestrator*]] checks the health of the services it manages and restarts any one which is slow to respond to its keep-alive request, making sure that all the services are online. As a control software must react quickly, it has no time to read from storage or obtain from other components the data it needs to make decisions. Thus it has to build and maintain in its memory a *model* of the system it controls: - An autopilot knows the last measured plane's coordinates, speed, angles and states of all its actuators. - A telephony application models both the phones and accounts it supervises and the calls present in the system. - A container manager remembers the state of every service it oversees, the time of the last health check and user request statistics (number of requests and processing time). When a program receives information from a component it controls, it updates its in-memory model with the new data and checks if the target invariant still holds: - A plane should remain on its course, otherwise its [angles](https://en.wikipedia.org/wiki/Aircraft_principal_axes) or thrust must be adjusted. - If there is a new incoming call, a VoIP gateway must create a corresponding outgoing call. - If a service is known to have not responded for a while, it must be killed and restarted. After the compensating action is initiated, the model is updated accordingly, and the software resumes processing events. After a while it should receive events that confirm that the invariant was restored: - The new sensor readings will show that the plane is back on its course. - The outgoing call will have been accepted and the gateway will connect the voice path between the call parties. - The container will have been restarted successfully and will be serving user requests. The flow of changes through the system is **M**-shaped: it starts as a low-level event, gets interpreted by a hardware driver or protocol stack, and goes up to an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]], which updates its model and decides if there should be any reaction, which would then go all the way down to the same or another low-level interface. ### Variants At the architectural level, control systems are [event-driven](https://en.wikipedia.org/wiki/Event-driven_programming) – their components communicate by messages. There are several architectures I am aware of: - [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] is the simplest and fastest implementation, usually tiny enough to run everything in a single [super loop](https://blog.mbedded.ninja/programming/design-patterns/how-to-write-super-loops-in-firmware/). - Plain ([[wiki/concepts/source/foundations-of-software-architecture/choreography|*choreographed*]]) [[wiki/concepts/source/basic-metapatterns/services|*Actors*]] fit systems of trivial logic but massive scale, like messenger backends. Each actor models the component (remote device or user) it interacts with, and there is no global model except for a registry of actors. - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Pedestal*]] is a better solution for complicated ([[wiki/concepts/source/foundations-of-software-architecture/modules-and-complexity|*cohesive*]]) systems that need centralized management. In it, the [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] (called *Control* or [*Mediator*](https://refactoring.guru/design-patterns/mediator) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\]) contains the whole system’s model and integration logic. Each hardware component is encapsulated with a [[wiki/concepts/source/extension-metapatterns/proxy|*driver*]] which hides its specifics from the business logic that resides in the hardware-agnostic, thus reusable and portable, *Orchestrator*. - A [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchical Orchestrator*]] can manage even more complex systems. The *Orchestrator* may run synchronously with polymorphic specialized components or asynchronously. In the last case each sub-orchestrator reacts independently based on its own model but also sends a notification to the high-level component which builds a global strategy and configures the smaller models of sub-orchestrators. ![Diagrams of control systems with the following architectures: monolithic, actors, Pedestal, hierarchical.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/4Kinds/Control%20-%20variants.png) ### Patterns The following patterns are prominent in control software: - [[wiki/concepts/source/basic-metapatterns/services|*Actors*]] – partitioning the domain into self-consistent asynchronous entities allows fine control over the order of execution of system activities, provided that we run a [preemptive scheduler](https://micrium.atlassian.net/wiki/spaces/osiiidoc/pages/131347/Preemptive+Scheduling) (from [POSIX real-time threads](https://man7.org/linux/man-pages/man7/sched.7.html) or [RTOS](https://en.wikipedia.org/wiki/Real-time_operating_system)) – we can assign top priority to reading data from communication interfaces (which would quickly overflow if the data is not retrieved), make reacting to events a bit less urgent, and still have leftovers of our CPU time for such long-running tasks as file access or strategic planning. - [[wiki/concepts/source/basic-metapatterns/monolith|*Proactor*]] – almost every component is single-threaded, reactive, and non-blocking, which makes the system very responsive. The downside is being unable to represent a multi-step scenario as a single function, which is usually unimportant as no predefined scenario ever survives event-driven reality unshattered. Following a planned path leads directly to your grave. Proceed stepwise, checking for dangers every millisecond, being ready to jump away from any approaching trouble. - [*Mediator*](https://refactoring.guru/design-patterns/mediator) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] (a kind of [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]]) – when you rely on information from and manage several devices or interfaces, you need a single entity that knows what is going on, makes informed decisions and dispatches commands to be executed. It integrates all the lower-level components into a coherent system. It is a *Mediator*. - [[wiki/concepts/source/basic-metapatterns/services|*Pedestal*]] (a kind of [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]]) – hardware components quickly become obsolete, and if you want your software to survive for a decade, you must be able to change them at will. And if you want to run, test, and debug your code on your desktop, you often need to [stub](https://martinfowler.com/articles/mocksArentStubs.html) or [mock the hardware](https://stackoverflow.com/questions/38745542/unit-testing-application-interface-to-hardware-to-mock-or-not). Therefore wrap each hardware component with a dedicated [[wiki/concepts/source/extension-metapatterns/proxy|*driver*]] that provides a generic interface to the upper layers of the system. This way you will be able to emulate or replace the hardware together with its driver because a driver conceals peculiarities and even the existence of the hardware it adapts. - [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchy*]] – if you manage a variety of interfaces that have the same role (telephony protocols, account providers, or payment systems) you want to make them polymorphic towards your main application logic, which is simpler if it does not need to address tens or hundreds of special cases. In other domains, like [IIoT](https://en.wikipedia.org/wiki/Industrial_internet_of_things), you may need to start reacting immediately with little precision and correct your actions after you have spent more time on better planning, which is achieved through a hierarchy of feedback loops. > Very complex hardware with tens or hundreds of components is usually managed by an [[wiki/concepts/source/implementation-metapatterns/microkernel|*Operating System*]] (a kind of [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]] architecture) with multiple control applications that have specific roles and govern distinct subsystems. An [extensively documented](https://www.autosar.org/fileadmin/standards/R22-11/CP/AUTOSAR_EXP_LayeredSoftwareArchitecture.pdf) real-world example is [[wiki/concepts/source/implementation-metapatterns/microkernel|*AUTOSAR*]] which is too vast a topic to discuss here. ### Implementation Even though we may (or may not) see [*State*](https://refactoring.guru/design-patterns/state) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] (aka *Objects for States* \[[wiki/concepts/source/appendices/books-referenced|[POSA4]]\]) close to hardware or protocol interfaces, the higher-level logic tends to be coded as a decision tree (with explicit conditions or polymorphic [*Strategies*](https://refactoring.guru/design-patterns/strategy) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\]) because it depends on multiple parameters which would make [too many combinations](https://en.wikipedia.org/wiki/Combinatorial_explosion#Computing) to write down as state classes. System components ([[wiki/concepts/source/basic-metapatterns/services|*actors*]]) have private in-memory data and communicate only by messages. They are usually single-threaded and non-blocking ([[wiki/concepts/source/basic-metapatterns/monolith|*Proactor*]]) – this way, the only locks in the system are those protecting the actors’ message queues and the global memory manager, which don’t contain anything complicated to block on for a noticeable time. And as nothing ever blocks, the whole system is extremely responsive. Messages may be dispatched through multilevel index arrays or [*Visitors*](https://refactoring.guru/design-patterns/visitor) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\]. [[wiki/concepts/source/foundations-of-software-architecture/shared-data|Message queues]] may be shared (a queue per thread priority) or private (a queue per component). With shared queues the destination of a message must either be resolvable from its type (when there is a single instance of every kind of system component) or stored in the message’s header. ## Interactive (soft real-time, user input) ![An interactive system workflow with an event coming from the presentation layer to the model and back.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/4Kinds/Interactive%20-%20main.png) An *interactive* software deals with users who expect it to provide immediate feedback to their actions. Examples include: - Desktop and mobile applications, from text editors to browsers. - Simple games, like chess or tetris. - UI of embedded devices, such as clocks or air conditioning systems. The [[wiki/concepts/source/basic-metapatterns/layers|user interface layer]] ([[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Separated Presentation*]]) receives input (mouse, keyboard / keypad, or touchscreen events), interprets it as a user action based on the current application’s *state* (pressing enter in a text editor may break a line of text in two, begin a new line, or even activate a menu item), forwards the action to the lower levels of the software and displays any feedback received, producing a **U**-shaped flow. ### Variants Interactive systems vary in a couple of ways: - The presentation layer may wait for the business logic to execute the user’s action, blocking further user input and screen updates (air conditioner controller), or it may asynchronously pass the command to the lower layer and continue processing new user input and showing progress of the already running tasks (many games) while the main program is busy with the command. ![With blocking interaction between the presentation and model layers the user interface is frozen for the duration of processing. With non-blocking it is mostly active.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/4Kinds/Interactive%20-%20variants%201.png) - There may be dedicated modules for processing user input and output ([[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*MVC* family]] of patterns) or both may pass through the same stack of components ([[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*MVP* family]]). The asymmetric approach deals with raw controller input, which is what most games need, while the bidirectional flow operates UI widgets provided by the host OS or GUI framework. ![Model-View-Controller features separate components for input and output. In Model-View-Presenter both layers above its model participate in both input and output.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/4Kinds/Interactive%20-%20variants%202.png) ### Patterns You will likely encounter: - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Separated Presentation*]] – the business logic is unaware of the implementation of the UI layer, though this may not be the case with some games that rely on *game development frameworks*. This pattern is usually implemented by: - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Model-View-Presenter*]] (MVP) – two input layers, namely: a *view* which receives input and shows output and a *presenter* which translates between the business logic, called *model*, and the view. - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Model-View-ViewModel*]] (MVVM) also has two layers, but the intermediate *ViewModel* [binds](https://en.wikipedia.org/wiki/Data_binding) to the view and bears its state. - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Model-View-Controller*]] (MVC) separates the input (*controller*) from the output (*view*). - [*State*](https://refactoring.guru/design-patterns/state) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\], subclassed by \[[wiki/concepts/source/appendices/books-referenced|[POSA4]]\] into *Objects for States*, *Methods for States*, and *Collections for States*, is prominent in games, though it may not always be implemented explicitly. - [*Flyweight*](https://refactoring.guru/design-patterns/flyweight), [*Command*](https://refactoring.guru/design-patterns/command), [*Observer*](https://refactoring.guru/design-patterns/observer) and many other \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] patterns originated with desktop software and [may often appear](https://gameprogrammingpatterns.com/contents.html) in games. ### Implementation The presentation layer may be called into by the desktop environment or it may run in a dedicated thread, for example, to play animations. The business logic is likely to rely on its own threads, at least for long-running actions. The presentation would usually receive (subscribe to) updates from the business logic. ## Streaming (continuous, raw data input) ![A streaming system is a pipeline of data processing steps.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/4Kinds/Streaming%20-%20main.png) A *streaming* system processes a long sequence of similar events or data packets, usually by transforming individual items in a predetermined way: - Each party in an audio call or video conference deals with incoming and outgoing media streams. For example, incoming audio stream processing involves: saving audio packets to a [jitter buffer](https://bloggeek.me/webrtcglossary/jitter-buffer/) to restore their order, [compensation for lost packets](https://en.wikipedia.org/wiki/Packet_loss_concealment), decoding, equalization, and playback. Outgoing audio passes through the following steps: [echo suppression or cancelation](https://en.wikipedia.org/wiki/Echo_suppression_and_cancellation), noise reduction, equalization, encoding, adding network headers, and sending packets to the network. - An image recognition system applies a [long sequence of transformations](https://keras.io/examples/vision/image_classification_from_scratch/#build-a-model) to every input image. - Hardware often works with streams. For example, a [CPU instruction pipeline](https://en.wikipedia.org/wiki/Instruction_pipelining) comprises at least: instruction fetching, instruction decoding and register fetching, execution, memory access, and register writeback. High-end processors may involve up to 20 stages. - Many UNIX command-line tools process streams of lines of text, allowing for complex text processing to be done by chaining pre-existing utilities \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\]. Streaming usually passes data through a chain of transformations ([[wiki/concepts/source/basic-metapatterns/pipeline|*pipeline*]]) which differ in functionality but stay at about the same level of abstractness – there are no managers or hierarchy. Such a **–**-shaped structure allows for each specialized component to process its own chunk of the stream in parallel to the other system components, which increases the system’s throughput but suffers from moderate to high latency. ### Variants As [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipelines*]] can exploit multiple CPU cores and specialized hardware, they are found everywhere from lowest-level firmware to large-scale distributed systems. \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\] classifies them into: - [[wiki/concepts/source/basic-metapatterns/pipeline|*Stream processing*]], where the pipeline is always alive waiting for more input to come. - [[wiki/concepts/source/basic-metapatterns/pipeline|*Batch processing*]], where the pipeline runs till it finishes processing an input file. ### Patterns - [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipes and Filters*]] is *the* stream processing pattern that describes pipelines: the system is built of *filters* (individual data processing steps) connected through *pipes* (data channels). - [[wiki/concepts/source/basic-metapatterns/pipeline|*Choreographed Event-Driven Architecture*]] (EDA) and [[wiki/concepts/source/basic-metapatterns/pipeline|*Data Mesh*]] are tree-like pipelines that process streams of domain events or data, respectively. \[[wiki/concepts/source/appendices/books-referenced|[EIP]]\] is full of patterns for the distributed processing of event streams. ### Implementation Every component is likely to run in its own thread or process and be unaware of other components – it only knows where to pull its input from and push its output into. One common challenge is to [slow down](https://medium.com/@beeindian04/back-pressure-in-data-pipeline-bdc25c6c1d79) a too fast data producer or scale data consumers when too much intermediate data accumulates in the channel between them. ## Computational (single run, user input) ![A computational system makes multiple calls to the underlying OS during a single run.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/4Kinds/Computational%20-%20main.png) Finally, there is a large group of applications created to process long-running commands: - A scientific calculation runs for days or weeks to provide insight into the physical reality or validate a new theory. - A compiler creates a platform-specific binary by processing text files with the program’s code. - An interpreter runs scripted actions on its user’s behalf. In each case the application starts with parsing (interpreting) its input, proceeds to execute it in a stepwise (and likely looped) manner, and finishes by outputting results of the run, making a **W**-shaped flow. ### Variants Some computational systems are single-use with a hard-coded task (calculation of Pi) while others can execute a variety of user scenarios (script interpreters). ### Patterns Long-running programs with user input are probably the most common, ancient, and well-studied kind of software, which also inspired many design patterns. Those of special significance are: - [[wiki/concepts/source/implementation-metapatterns/microkernel|*Interpreter*]] that supports very complex user commands. - [*Facade*](https://refactoring.guru/design-patterns/facade) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] or [[wiki/concepts/source/extension-metapatterns/orchestrator|*Process Manager*]] (kinds of [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]]) that executes a user command as a sequence of calls to lower-level components. ### Implementation There can be some kind of *parser* (complex for SQL or very simple for command-line parameters) that transforms user input into a [syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) or a set of flags, respectively. Then there is a kind of *main loop* which iteratively executes actions, pre-defined or encoded in the parsed tree, until an *exit condition* is met or the entire input has been processed. Any calls to external components (OS or libraries) are likely to be blocking as the computation does not need to react quickly to any additional input – actually, it does not read any input or change its behavior for the entire duration of its run. Parts of computations may be offloaded to [SIMD](https://en.wikipedia.org/wiki/Single_instruction,_multiple_data) or [GPU](https://en.wikipedia.org/wiki/Graphics_processing_unit) / [TPU](https://en.wikipedia.org/wiki/Tensor_Processing_Unit) because that greatly speeds up number crunching which is often characteristic of long-running calculations. ## Mixed cases Most real-life software is too complex to fit the classification outlined above. It tends to merge the paradigms either by mixing them to find a middle ground or by implementing two or three of them at once. Let’s inspect a few random examples to see how that works: ### Camera ![Internal components of a camera with interactive, control, and streaming communication highlighted.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/4Kinds/Camera.png) A digital camera incorporates subsystems of different kinds: - There is an interactive [[wiki/concepts/source/basic-metapatterns/layers|user interface]] that receives commands for other components and displays either the video stream from the matrix or the camera’s settings menu. - A control layer provides feedback loops to keep the camera focused on a selected object and preserve the overall brightness level and color balance when shooting in automatic mode. - An image processing pipeline applies noise reduction, rescaling, and color correction, then either passes the resulting frames to the UI to show them on the screen or proceeds with compressing the frame and storing it as a file. ### 3D action game ![A game framework receives data from hardware and sends an event to the business logic which updates multiple game objects. Finally, the framework sends updates to the hardware.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/4Kinds/3D%20action.png) Games with 3D graphics often bypass the host OS’ [desktop environment](https://en.wikipedia.org/wiki/Desktop_environment) and access the underlying hardware drivers to achieve fine control and improved performance. Such applications, though pretending to be interactive software driven by user input, strongly resemble control systems by polling hardware with fixed frequency (the game’s frame rate). ### SQL database ![Internals of a database with the following groups of components: session, parser, task, metadata manager, and tables.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/4Kinds/Database.png) SQL databases support several kinds of user commands: - [*Data definition*](https://en.wikipedia.org/wiki/Data_definition_language) and [*data control*](https://en.wikipedia.org/wiki/Data_control_language) *languages* (*DDL* / *DCL*) manage the database’s metadata: table definitions and user permissions, respectively. - [*Data manipulation*](https://en.wikipedia.org/wiki/Data_manipulation_language) and [*data query*](https://en.wikipedia.org/wiki/Data_query_language) *languages* (*DML* / *DQL*) write to and read from the tables. - [*Procedural language*](https://neon.tech/postgresql/postgresql-plpgsql/introduction-to-postgresql-stored-procedures) (*PL*) programs user-defined functions (*stored procedures*). Each kind of command is processed in a unique way: - When the parser recognizes a DDL or DCL request, it calls a corresponding method that reads or modifies the metadata. If a table is to be altered, the action will either lock the table for the duration of operation or require a complicated workaround to allow other sessions to access the table while it is being restructured. - DML or DQL input is compiled into a tree of elementary operations (reads, conditions, joins, etc.) which is then passed to the *query optimizer* to be rearranged into a linear sequence of operations that accounts for table sizes and index types. The execution of the resulting *pipeline* depends on its type: - As a *query* (DQL) does not change anything, it merely [reserves a virtual *snapshot*](https://en.wikipedia.org/wiki/Multiversion_concurrency_control) of the current state of the tables, runs the compiled pipeline on the snapshot (probably taking quite a while), streams the output to the client and, finally, releases the snapshot. - *Commands* (*DML*) modify data, thus they involve a few extra steps. A snapshot is allocated as well, however, now it is writable, storing all the changes to the database the command’s pipeline makes. Every change is also written to a [*Write-Ahead Log*](https://en.wikipedia.org/wiki/Write-ahead_logging) (*WAL*) [*buffer*](https://stackoverflow.com/questions/39879754/wal-buffers-fills-before-the-transaction-commits). When the pipeline or a wrapping transaction completes, the database engine ensures that the data changed in the snapshot is still unchanged in the main database. In case of a conflict the snapshot and WAL buffer are dropped, a new snapshot is allocated on top of the conflicting changes in the main storage, and the command is re-applied. As soon as there are no conflicts, the WAL buffer is flushed to the *WAL file*, which is the single source of truth for the database’s crash recovery. After that the changes from the snapshot are integrated into the main data storage, marking any updated data structures as *dirty*. The snapshot and WAL buffer are released, the command returns the number of rows it changed to the user, and another background thread lazily flushes the dirty data to the file system or network storage. *Snapshotting* ([MVCC](https://en.wikipedia.org/wiki/Multiversion_concurrency_control)) allows long-running queries ([OLAP](https://en.wikipedia.org/wiki/Online_analytical_processing)) to return consistent data (without reporting any concurrent changes from commands) and commands ([OLTP](https://en.wikipedia.org/wiki/Online_transaction_processing)) to commit with [no need to lock](https://en.wikipedia.org/wiki/Optimistic_concurrency_control) database records. - PL requests are identified by keywords and forwarded to a PL-dedicated parser which checks their syntax, compiles the user-defined functions into some kind of pseudocode and stores them for further use by queries or commands. Here we have: - Dynamically created *streaming pipelines* that represent DQL and DML requests. - Heavy reliance on parsers and compilers, like in *computational* software. - A long-running main application that deals with multiple user requests that often need to be executed quickly, like in *interactive* applications. \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\] is dedicated to the implementation of databases, which are indeed way more complex and varied than what I outlined above. ## Summary We can discern four kinds of systems that differ in their goals, architecture, and code: - *Control* – supervises hardware and must react with extremely low latency. It never blocks and relies on an in-memory model of the system it manages. - *Interactive* – deals with users and should handle their actions while providing feedback in near real time. Its user-facing layer tends to be separated from the main logic. - *Streaming* – processes boring sequences of blocks of data. Such systems are usually assembled from single-purpose components. - *Computational* – performs a long-running calculation. It parses user input to prepare its task, then goes deep into the engine for its execution and, finally, pops up the result. Complex real-world software usually involves two or three of these approaches. --- title: "Modules and complexity" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Foundations of software architecture/Modules and complexity.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Foundations%20of%20software%20architecture/Modules%20and%20complexity source_license_note: "See namespace README; preserve attribution and source links." --- # Modules and complexity > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Foundations of software architecture/Modules and complexity.md`. > This chapter is loosely based on [A Philosophy of Software Design](https://blog.pragmaticengineer.com/a-philosophy-of-software-design-review/) by John Ousterhout and [my article](https://medium.com/itnext/introduction-to-software-architecture-with-actors-part-1-89de6000e0d3). Any software system which we encounter is very likely to be too complex to comprehend all at once – the human mind is incapable of discerning a large number of entities and their relations \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]. It tends to simplify reality by building abstractions: as soon as we define the many shiny pieces of metal, glass and rubber as a ‘car’ we can identify ‘highways’, ‘parking spaces’ and ‘passengers’ – we live in a world of the abstractions which we create. In the same way the software we write is built of services, processes, files, classes, procedures – modules that conceal the swarm of bits and pieces which we are powerless against. Let’s reflect on that. ## Concepts and complexity Any system is comprised of *concepts* – notions defined in terms of other concepts. For example, if you are implementing a phonebook, you deal with *first* and *last names*, *numbers*, *sorting*, and *search*, which one must always keep in mind for any phonebook-related development task – just because requirements for the phonebook are described in terms of those concepts and their relations. In code high-level concepts are embodied as services, modules, or directories while lower-level concepts match to individual classes, API methods, or source files. Concepts are important because it is their quantity (or the number of the corresponding classes and methods) that defines the *complexity* of a system – the cognitive load which developers of the system face. If the programmers grasp the behavior of a component they work on in detail they tend to [become extremely productive](https://www.quora.com/What-are-some-habits-of-10x-programmers) and are often able to find [simple solutions for seemingly complex tasks](https://realmensch.org/2017/08/25/the-parable-of-the-two-programmers/). Otherwise the development is slow and requires extensive testing because the programmers are [unsure of how their changes affect the system’s behavior](https://news.ycombinator.com/item?id=18442941). ![Complexity represented as the number of interconnected nodes.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Intro/Modules-1.png) Figure 1: Complexity correlates with the number of entities. ## Modules, encapsulation and bounded context Let’s return to our example. As you implement the phonebook you find out that sorting and search are way more complex than you originally thought. Once you prepare to enter the international market you are in [deep trouble](https://en.wikipedia.org/wiki/Alphabetical_order#Language-specific_conventions). Some telephony providers send 7-digit numbers, others use 10 digits, still others – 13 digits (with either “\+” or “0” for the first character). German has “ß” which is identical to “ss” while Japanese uses two alphabets simultaneously. Once you start reading standards, implementing all the weird behavior and responding to user complaints you feel that your phonebook implementation is drowning in the unrelated logic of foreign alphabets’ special cases. You need *encapsulation*. Enter *modules*. A module wraps several concepts, effectively hiding them from external users, and exposes a simplified view of its contents \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]. Introducing modules splits a complex system into several, usually less complex, parts. ![A module hides a cluster of the original nodes but creates new interface nodes which add to the complexity of the modules that use them.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Intro/Modules-2.png) Figure 2: Dividing a system into modules, bounded contexts highlighted. This diagram has several important points to note: - Modules create new concepts for their *public APIs*. - The API entry points add to the complexity of *both* the owner module and its clients. - The total number of concepts in the system has increased (from 18 to 22) but the highest complexity in the system has dropped (from 18 to 15). Here we see how introducing modularity applies the [divide and conquer](https://en.wikipedia.org/wiki/Divide-and-conquer_algorithm) approach to lessen the cognitive load of working on any part of a system at the cost of a small increase in the total amount of work to be done. In our phonebook example, the peculiarities (including case sensitivity) of the locale-aware string comparison and alphabetical sorting of contact names would be better kept behind a simple string comparison interface in order to relieve the programmer of the phonebook engine of the complexity of supporting multiple languages. Modules represent *bounded contexts* \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\] – areas of the knowledge about a system that operate distinct sets of terms. In the case of phonebook the *collation* and *case sensitivity* do not matter for the phonebook engine – they are defined only in the context of language support. On the other hand, *matching a contact by number* is not defined in the language support module – that term exists only in the phonebook engine. It is the complexity of the current bounded context that a programmer struggles with. Apart from dividing the problem into simpler subproblems, modules open the path to a few extra benefits: - *Code reuse*. A well-written module that implements something generic may be used in multiple projects. - *Division of labor*. Once a system is split into modules and each module is assigned one or more programmers, development is efficiently parallelized. - *High-level concepts*. Some cases allow for merging several concepts of the original problem into higher-level aggregates, further reducing the complexity: ![Some of the interface nodes are grouped to lower the complexity.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Intro/Modules-3.png) Figure 3: Merged two API concepts in the green module. For example, the original definition of a phonebook contained *first name* and *last name*. Once we separate the language support into a dedicated module, we may find out that various locales differ in the way they represent contacts: some (USA) use ‘first name \+ last name’ while others (Japan) need ‘last name \+ first name’. If we want to abstract ourselves from that detail, we should use a new concept of *full name* which conjoins first and last names in a locale-specific way. Such a change actually simplifies some of the phonebook’s representation logic and code as it replaces two concepts with one. ## Coupling and cohesion We need to learn a couple of new concepts in order to use modules efficiently: *Coupling* is a measure of the number (density) of connections between modules relative to the modules’ sizes. *Cohesion* is a measure of the number (density) of connections inside a module relative to the module’s size. The rule of thumb is to aim for *low coupling and high cohesion*, meaning that each module should encapsulate a cluster of related (intensely interacting) concepts \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]. This is how we have split the system in figures 2 and 3. Now let’s see what happens if we violate the rules: ![Subdividing a complex module with many internal connections results in two complex modules because many new interface nodes are created.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Intro/Modules-4.png) Figure 4: The upper modules are tightly coupled. Splitting a cohesive module (a cluster of concepts that interact with each other) yields two strongly coupled modules. That’s what we wanted, except that each of the new modules is nearly as complex as the original one. Meaning, that we now face two hard tasks instead of one. Also, the system’s performance may be poor because communication between modules is rarely optimal, and we’ve got too much of that. ![Merging loosely coupled modules only marginally reduces the overall number of nodes in the system.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Intro/Modules-5.png) Figure 5: The lower module has low cohesion. What happens if we put several clusters of concepts into the same module? Nothing too evil if the clusters are small – the module acquires a higher complexity than each of its constituents, but lower overall than their sum. In practice, multiple unrelated functions are often gathered in a ‘utils’ or ‘tools’ file or directory to alleviate *operational complexity*. ## Development and operational complexity What we discussed above is *structural* or *development complexity* – the number of concepts and rules inside a bounded context. However, we also need to understand operations and components of the system as a whole, leading to *operational* or *integration complexity*: - Does this new requirement fit into an existing module or does it call for writing a dedicated one? - Which libraries with known security vulnerabilities do we use? - Is there any way to cut our cloud services cost? - 1% of requests time out. Would you please investigate that? - My team needs to implement this and that. Do we have something fit for reuse? - What the \*\*\*\* is [that global variable](https://news.ycombinator.com/item?id=18442941) about? - Do we really need this code in production? - I need to change the behavior of that shared component a little bit. Any objections? When there are hundreds or thousands of modules deployed nobody knows the answers. That’s similar to the case of one needing to do something in Linux: hundreds of tools are pre-installed and thousands more are available as packages, but the only real way forward is first searching the web for your needs, then trying two or three recipes from the results to see which one fits your setup. Unfortunately, Google does not index your company’s code. ## Composition of modules A module may encapsulate not only individual concepts, but even other modules. That is not surprising as an OOP class is a kind of module – it also has public methods and private members. Hiding a module inside another one removes it from the global scope, decreasing the operational complexity of the system – now it is not the system’s architect but the maintainer of the outer module who cares about the inner module. On one hand, that builds a manageable hierarchy in both the organization and the code. On the other hand, code reuse and many optimizations become nearly impossible as internal modules are hardly known organization-wide: ![When a module is hidden inside another module, there is no clear way to expose it to external clients.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Intro/Modules-6.png) Figure 6: Composition of modules prevents reuse. If the functionality of our internal module is needed by our clients, we have two bad options to choose from: ## Forwarding and duplication ![The interface of the internal module is duplicated in the interface of the wrapping module.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Intro/Modules-7.png) Figure 7: Forwarding the API of an internal module. We can add the API of a module which we encapsulate to our public API and forward its calls to the internal module. However, that increases the complexity and lowers the cohesion of our own module – now each client of our module is also exposed to the details of the methods of the module which we have encapsulated whether they are used or not. ![The internal module itself is duplicated outside of the module which wraps it.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Intro/Modules-8.png) Figure 8: Duplicating an internal module. Another bad option is to let the clients that need a module which we encapsulate duplicate it and own the copies as their own submodules. This relieves us of any shared responsibility, lets us modify and misuse our internals in any way we like, but violates [a couple](https://en.wikipedia.org/wiki/Rule_of_three_(computer_programming)) [of rules](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) of common sense. Both approaches, namely keeping all the modules in the global scope and encapsulating utility modules through composition, found their place in history \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\]. [*Service-Oriented Architecture*]() was based on the idea of reuse but fell prey to the complexity of its [[wiki/concepts/source/extension-metapatterns/orchestrator|*Enterprise Service Bus*]] which had to account for all the interactions (API methods) in the system. In response, the [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]] approach turned the tide in the opposite direction: its proponents disallowed sharing any resources or code between services to enforce their decoupling. ## Summary *Complexity* is the number of *concepts* and their relations which one must remember to work efficiently. A *module* hides some of the concepts from its users but creates new concepts (its *interface*). *Coupling* is the measure of dependencies between the modules, while *cohesion* is the same for the concepts inside a module. We prefer *low coupling and high cohesion* to group related things together. Having too many modules causes trouble for the system’s maintainers. A module may contain other modules. When a client wants to use a submodule, the wrapping module may extend its interface to forward client’s requests to the submodule or the client may deploy a copy of the submodule for its own use. Both approaches gave rise to prominent architectures. --- title: "Orchestration" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Foundations of software architecture/Arranging communication/Orchestration.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Foundations%20of%20software%20architecture/Arranging%20communication/Orchestration source_license_note: "See namespace README; preserve attribution and source links." --- # Orchestration > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Foundations of software architecture/Arranging communication/Orchestration.md`. The most straightforward way to integrate services is to add a coordinating layer, called [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] after the person that assigns parts in an orchestra, on top of them: ![After a monolith is subdivided into services, an orchestrator is added to communicate with the client and with each service.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Services%20to%20Orchestrator.png) The good thing is that your *Orchestrator* has explicit code for every use case it covers and every running scenario gets an associated thread, coroutine, or object so that you are able to attach to the *Orchestrator* and debug any use case step by step. Nor do you have to worry about keeping the state of the services consistent as they are passive with all the changes in the system being driven by the *Orchestrator*. Orchestration is the default approach for single-process (desktop) applications where it is faster to call into an orchestrated module and return than to send it a message. However, in distributed systems orchestration doubles the communication overhead (when compared to [[wiki/concepts/source/foundations-of-software-architecture/choreography|choreography]] or [[wiki/concepts/source/foundations-of-software-architecture/shared-data|shared data]]) as every method call into an orchestrated service uses two messages: request and confirmation. ## Roles In a backend which serves client requests an *Orchestrator* takes the role of [*Facade*](https://refactoring.guru/design-patterns/facade) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] – a module that provides and implements a high-level interface for a multicomponent system. It sends requests to the underlying services and waits for their confirmations – the mode of action that can be wrapped in an [*RPC*](https://en.wikipedia.org/wiki/Remote_procedure_call) (*remote procedure call*). The state of each scenario that the facade runs resides in the associated thread’s or coroutine’s call stack (for [[wiki/concepts/source/basic-metapatterns/monolith|*Reactor*]] or [[wiki/concepts/source/basic-metapatterns/monolith|*Half-Sync/Half-Async*]] implementations, respectively) or in a dedicated object (for [[wiki/concepts/source/basic-metapatterns/monolith|*Proactor*]]). ![A facade uses request/confirm pairs of messages to communicate with the services which it orchestrates.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Facade.png) A *Facade* also supports querying the services in parallel and collecting the data returned into a single message through the *Splitter* and *Aggregator* patterns of \[[wiki/concepts/source/appendices/books-referenced|[EIP]]\]. That reduces latency (and resource consumption as the whole task is completed faster) for [scatter-gather](https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/scatter-gather.html) requests when compared to sequential execution. ![A facade initiates communication with every service that it orchestrates simultaneously in a fan-out manner.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Facade%20-%20Parallel.png) Embedded and systems programming – the areas that deal with automating [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|*control*]] of hardware or distributed software – employ *Orchestrators* as [*Mediators*](https://refactoring.guru/design-patterns/mediator) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] – components that keep the state of the whole system (and, by implication, any hardware it may manage) consistent by enacting a system-wide reaction to any observable change in any of the system’s constituents. A mediator operates in non-blocking, fire-and-forget mode which is more characteristic of choreography, to be discussed [[wiki/concepts/source/foundations-of-software-architecture/choreography|below]]. This also means that you will not be able to debug a use case as a thread – because [there are no predefined scenarios in control software](https://medium.com/itnext/control-and-processing-software-9011fee8bc66)! ![A mediator receives an input from one component, processes it, and initiates actions in other components.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Mediator.png) Such a difference may be rooted in the direction of the control and information flow: in a backend it comes as a complex, high-level request while a control system reacts to a flood of low-level events. ## Dependencies By default an *Orchestrator* depends on each service which it manages – that means that a change in a service’s interface or contract – caused by fixing a bug, adding a feature, or optimizing performance – requires corresponding changes in the *Orchestrator*. That is acceptable as the *Orchestrator*’s client-facing, high-level logic tends to evolve much faster than the business rules of the lower layer of services, therefore the team behind the *Orchestrator*, unrestricted by other components depending on it, will likely release way more often than any other team. However, as the number of the managed services and the lengths of their APIs increase, so does the amount of information that the *Orchestrator*’s team must remember and the influx of changes which they must integrate in their code. For a large project the workload of supporting the *orchestration layer* may paralyze its development – that was a major reason behind the decline of [*Enterprise SOA*]() where [[wiki/concepts/source/extension-metapatterns/orchestrator|*ESB*]] used to orchestrate all the interactions in the system, including those between domain-level services and components of the utility layer. ![An orchestrator depends on every service which it uses.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Orchestrator%20-%20Dependencies.png) Another option, which appears in [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]] and develops in [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]] and [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]], stems from [*dependency inversion*](https://en.wikipedia.org/wiki/Dependency_inversion_principle): the *Orchestrator* defines an [*SPI*](https://en.wikipedia.org/wiki/Service_provider_interface) (*service provider interface*) for every service. That makes each service depend on the *Orchestrator* so that a single *Orchestrator*’s team does not need to follow updates of the multiple services’ APIs – instead it initiates the changes at its own pace. However, with that approach the design of an SPI requires coordination from the teams on both sides of it and the once settled interface becomes hard to change. The most famous example of modules that implement SPIs are OS drivers. ![In a Microkernel each managed service depends on a dedicated Service Provider Interface of the microkernel.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Microkernel%20-%20Dependencies.png) Furthermore, some domains develop that idea into a [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchy*]]: when services implement related concepts, they may match a single SPI, making the *Orchestrator* simpler (as there is no further need for its developers to remember multiple interfaces). That is the case with telecom or payment gateways and it may also be found with trees of product categories in online marketplaces. ![In a hierarchy each child component depends on the same Service Provider Interface of their parent component.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Hierarchy%20-%20Dependencies.png) All kinds of orchestration allow for an easy addition of new use cases which may even involve new services as that changes nothing in the existing code. However, removing or restructuring (splitting or merging) previously integrated services requires much work within the orchestrator, except for in a *Hierarchy* where all the services implement the same interface which means that the code in the *Orchestrator* does not depend (much) on any specific child. ![Adding a new use case to an orchestrated system changes only the orchestrator.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Orchestrator%20add%20a%20Use%20Case.png) ## Mutual orchestration In some systems there are several services that have their own kinds of clients (for example, employees of different departments). Each of the services tries hard to process its clients’ requests on its own but occasionally still needs help from other parts of the system. This creates a paradoxical case where several services orchestrate each other: ![A set of services which call each other while executing requests from their clients.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Mutual%20Orchestration%20-%201.png) As each of the services depends on the APIs of the others, any change to any interface or composition of such a system requires consent and collaboration from every team as it impacts the code of all the services. ![Each service depends on every other service which it calls.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Mutual%20Orchestration%20-%202.png) In real life [[wiki/concepts/source/fragmented-metapatterns/layered-services|services are likely to be layered]], with their upper layers acting as both internal and external *Orchestrators*. Layering isolates interdependencies to the relatively small [[wiki/concepts/source/basic-metapatterns/layers|application-level]] components and resolves, to an extent, the seemingly counterintuitive case of mutual orchestration as now there is an explicit, though fragmented, system-wide orchestration layer. ![In Layered Services only the application layers of the services call each other.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Mutual%20Orchestration%20-%203.png) ![In Layered Services only the application layers of the services are interdependent.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Mutual%20Orchestration%20-%204.png) ## Summary Orchestration represents [[wiki/concepts/source/basic-metapatterns/layers|use cases]] as a code, allowing for an orchestrated system to support many complex scenarios. Dealing with errors is as trivial as properly handling exceptions. This approach trades performance for clarity. --- title: "Programming and architectural paradigms" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Foundations of software architecture/Arranging communication/Programming and architectural paradigms.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Foundations%20of%20software%20architecture/Arranging%20communication/Programming%20and%20architectural%20paradigms source_license_note: "See namespace README; preserve attribution and source links." --- # Programming and architectural paradigms > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Foundations of software architecture/Arranging communication/Programming and architectural paradigms.md`. Sharing a database is the greatest sin when you architect [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]] yet [[wiki/concepts/source/extension-metapatterns/sandwich|*Space-Based Architecture*]] is built around shared data. How do these approaches coexist? Does *Microservice Architecture* make any sense if blatantly violating its principles still results in successful projects? Another programming paradox holds a clue. There was C. Then there came C\+\+ to kill C. Then we’ve got Rust to kill C\+\+. Now we have C, C\+\+, and Rust, all of them alive and kickin’. ## Technologies are specialized When a new technology emerges, it must show its superiority over existing mature methods. In most cases that is achieved by specialization. Is a car superior to a donkey? It depends. Probably yes, when there are good roads, plenty of gas, and spare parts. A car is narrowly specialized, thus some areas have successfully adopted cars, while others still rely on donkeys. The same holds true for programming languages and architectures. C is good when you work close to hardware and need complete control over whatever happens in the system. C\+\+ is great at partitioning business logic, but it lost the simplicity of its predecessor. Rust will likely shine in communication libraries, which are often targeted by hackers, though we have yet to see its wide adoption. Hence the usefulness (and choice) of a tool or programming language depends on the circumstances. Let’s turn our attention to your average code. It often mixes together: - *Object-oriented* programming that divides the application into a tree of loosely interacting pieces. - *Functional* programming, with the output of one function becoming the input to another, [method chaining](https://en.wikipedia.org/wiki/Method_chaining) included. - *Procedural* programming, where multiple functions access the same set of data, which also happens inside classes whose many methods operate their private data members. Each [programming paradigm](https://en.wikipedia.org/wiki/Programming_paradigm) fits its own kind of tasks. Moreover, the same three approaches reemerge at the system level: ## Object-oriented (centralized, shared nothing) paradigm – orchestration Almost every software project is too complex for a programmer to keep all the details of its requirements and implementation in their mind. Notwithstanding, those details must be written down and run as code. The good old way out of the trouble is called [*divide and conquer*](https://en.wikipedia.org/wiki/Divide-and-conquer_algorithm). The global task is divided into several subtasks, and each subtask is subdivided again and again – till the resulting pieces are either simple enough to solve directly or [[wiki/concepts/source/foundations-of-software-architecture/modules-and-complexity|too messy]] to allow for further subdivision. Essentially, we need to split our domain’s *control*, *logic*, and *data* into a single hierarchy of moderately sized components. We have heard a lot about keeping *logic and data* together: an object (or [[wiki/concepts/source/basic-metapatterns/services|actor]], or [[wiki/concepts/source/basic-metapatterns/services|module]], or [[wiki/concepts/source/basic-metapatterns/services|service]] – no matter what you call it) must own its data to assure its consistency and hide the complexity of the component’s internals from its users. If the encapsulation of an object's data is violated, the object’s code can neither trust nor ever restructure it. On the other hand, if the data is bound to the logic that deals with it, the entire thing becomes a useful black box which one does not need to look into to operate. Adding *control* to the blend is more subtle, but no less crucial than the encapsulation discussed above. If an object commands another thing to do something, it must receive the result of the delegated action to know how to proceed with its own task. Returning control after the action is conducted enables separation of high-level supervising (orchestration, integration) logic from low-level algorithms which it drives, adding depth to the structure. ![A diagram of an object-oriented system built through composition.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Paradigms%20-%20Object-oriented.png) The ability to address complex domains by reducing the whole to self-contained pieces makes object-oriented design ubiquitous. This paradigm, when applied to distributed systems, gives birth to [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]], [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrated Services*]], and [*Service-Oriented Architecture*](). ![Diagrams of: Microservices, Orchestrated Services, and Service-Oriented Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Paradigms%20-%20Object-oriented%20-%20Variants.png) ## Functional (decentralized, streaming) paradigm – choreography Sometimes you don’t need that level of fine-tuning for the behavior of the system you build – it operates as an [assembly line](https://en.wikipedia.org/wiki/Assembly_line) with high throughput and little variance: its logic is made of steps that resemble work stations along a [conveyor belt](https://en.wikipedia.org/wiki/Conveyor_belt) through which identically structured pieces of data flow. In that case there is very little to control: if an item is good, it goes further, otherwise it just falls off the line. Here the *control* resides in the graph of connections*,* the [[wiki/concepts/source/basic-metapatterns/layers|domain *logic*]] is subdivided, while the *data* is copied (or, more rarely, moved) between the components. ![A diagram of a pipeline with components implementing steps of data processing.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Paradigms%20-%20Functional.png) Functional or pipelined design is famous for its simplicity and high performance as the majority of processing steps can be scaled. However, its straightforward application lacks the depth needed for handling complex processes, which would translate into webs of relations between hundreds of functions present at the same level of design. It is also inefficient for choose-your-own-adventure-style ([[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|*control*]]) systems where too many too short conveyor belts would be required, negating the paradigm’s benefits. And it may not be the right tool for making small changes in large sets of data as you’ll likely need to copy the whole dataset between the constituent functions. In distributed systems the functional paradigm is disguised as [[wiki/concepts/source/basic-metapatterns/pipeline|*Choreographed Event-Driven Architecture*]], [[wiki/concepts/source/basic-metapatterns/pipeline|*Data Mesh*]], and various [[wiki/concepts/source/basic-metapatterns/pipeline|batch or stream]] processing \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\] [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipelines*]]. ![Diagrams of Event-Driven Architecture and Data Mesh.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Paradigms%20-%20Functional%20-%20Variants.png) ## Procedural (data-centric) paradigm – shared data The final approach is integration through data. There are cases where the domain data and business logic differ in structure – you cannot divide your project into objects because each of the many pieces of its logic needs to access several (seemingly unrelated) parts of its data. ![A diagram of a procedural system where logic and data make independent hierarchies.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Paradigms%20-%20Data-centric.png) In the data-centric paradigm *logic* and *data* are orthogonal. There are two ways to deal with the control: - In procedural programming, like in object-oriented paradigm, *control* is implemented inside the logic, making the logic layer hierarchical ([[wiki/concepts/source/foundations-of-software-architecture/orchestration|*orchestrated*]]), as on the diagram above. - Another, much less common, option relies on [*Observer*](https://refactoring.guru/design-patterns/observer) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] to provide data change notifications, resulting in decentralized ([[wiki/concepts/source/foundations-of-software-architecture/choreography|*choreographed*]]) application logic, as shown below. ![Components of a data-centric system rely on data change notifications.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Paradigms%20-%20Data-centric%20-%20Notifications.png) The data-centric approach works well for moderately-sized projects with a stable data model (like reservation of seats in trains or the game of chess). The best-known distributed data-centric architectures include [[wiki/concepts/source/extension-metapatterns/shared-repository|*Services with a Shared Database*]] and [[wiki/concepts/source/extension-metapatterns/sandwich|*Space-Based Architecture*]]. ![Diagrams for Services with a shared database and Space-Based Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Paradigms%20-%20Data-centric%20-%20Variants.png) ## Composite cases The three programming paradigms tend to collaborate: - An ordinary class is object-oriented on the outside but procedural inside: each of its methods can access any of its private data members. Moreover, a class method may chain function calls, applying the functional paradigm to two or three lines of its code. - [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Cell-Based Architecture*]] tends to use [[wiki/concepts/source/foundations-of-software-architecture/choreography|*choreography*]] (pub/sub) between [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cells*]] and [[wiki/concepts/source/foundations-of-software-architecture/orchestration|*orchestration*]] or communication via a [[wiki/concepts/source/foundations-of-software-architecture/shared-data|*shared database*]] inside them \[[wiki/concepts/source/appendices/books-referenced|[DEDS]]\]. - A system of [[wiki/concepts/source/basic-metapatterns/services|*Services*]] (or [[wiki/concepts/source/extension-metapatterns/sandwich|*Space-Based Architecture*]]) may be integrated through both [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] and [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Database*]] (or [[wiki/concepts/source/extension-metapatterns/orchestrator|*processing grid*]] and [[wiki/concepts/source/extension-metapatterns/shared-repository|*data grid*]], respectively), see [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]]. ## Reality is more complex We have reviewed a few cases directly supported by common programming languages. However, there is a wide variety of possible combinations of (at least) the following dimensions, each making a unique programming paradigm: - Synchronous (method calls) vs asynchronous (messaging), with closely related: - Imperative vs reactive. - Blocking vs non-blocking. - Centralized (orchestrated) vs decentralized (choreographed) flow. - Shared data (tuple space) vs [shared nothing](https://en.wikipedia.org/wiki/Shared-nothing_architecture) (messaging). - Commands (actors) vs notifications (agents). - One-to-one (channels) vs many-to-one (mailboxes) vs one-to-many (multicast) vs many-to-many (gossip) communication. Some of the combinations look impossible or impractical, others are narrowly specialized thus uncommon, while many more are commonplace. Discussing all of them would require insights from people who have used them in practice and would likely take a dedicated book. ## Summary We have deconstructed the most common programming paradigms into their driving forces and shown how those forces shape distributed architectures: - An object-oriented system relies on hierarchical decomposition of a complex domain, just like [*SOA*]() and [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrated (Micro-)Services*]] do. - Functional programming streams data through a sequence of transformations, which is the idea behind [[wiki/concepts/source/basic-metapatterns/pipeline|*Choreographed Event-Driven Architecture*]] and [[wiki/concepts/source/basic-metapatterns/pipeline|*Data Mesh*]]. - Procedural style lets any piece of logic access the entire project’s data, resembling [[wiki/concepts/source/extension-metapatterns/sandwich|*Space-Based Architecture*]] and [[wiki/concepts/source/extension-metapatterns/shared-repository|*Services with a Shared Database*]]. Now let’s examine each of these approaches in depth: --- title: "Shared data" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Foundations of software architecture/Arranging communication/Shared data.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Foundations%20of%20software%20architecture/Arranging%20communication/Shared%20data source_license_note: "See namespace README; preserve attribution and source links." --- # Shared data > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Foundations of software architecture/Arranging communication/Shared data.md`. The final approach is integration through shared data ([[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]]): ![After a monolith is subdivided into services, a shared database is used to integrate the services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Services%20to%20Shared%20Data.png) The shared data is a “blackboard” available for each service to read from and write to. It is passive (as controlled by the services) and does not contain any logic except for the data schema, which represents a part of the domain knowledge. That makes communication through shared data the antipode of [[wiki/concepts/source/foundations-of-software-architecture/orchestration|orchestration]], which also features a shared component, namely an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]], which is, however, active (controls services) and contains business logic, not data. Shared data can be used for storage, messaging, or both: ## Storage The most common case of shared data is persistent storage (usually a database, sometimes a file system) for a (sub)domain that comprises functionally independent services which operate on a common dataset. For example, a ticket purchase service and a ticket refund service share a database of ticket details. The ticket purchase service reads in the available seats and fills in ticket data for purchases. The ticket refund service should be able to find all tickets bought by a user and delete the user data from seats refunded. The only communication between the purchase and refund services is the shared database of tickets or seats, so that one of them sees the changes made by the other the next time it reads the data. ![Both purchase and refund services see and edit the entire system's data.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Purchase%20and%20Return.png) With this model the services don’t depend on each other – instead, they depend on the shared (domain) data format and the database technology. Thus, it is easy to add, modify, or remove services but hard to change the shared data structure or the database vendor. ![Each service depends only on the shared database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Shared%20Data%20-%20Dependencies.png) ![Adding a service to a system integrated through shared data does not require changes to other services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Shared%20Data%20add%20a%20Service.png) Services usually need to coordinate their actions. Commonly, services with a shared database rely on a messaging [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] for communication. Users of our ticketing system will want to be notified (through email, SMS or an instant message) when a free seat that they are interested in appears. We’re not going to complicate either of the existing services by integration with instant messengers, so we will create a new notification service, which must track each returned ticket to see if any user wants to buy it. This is easily implemented by the refund service publishing and the notification service subscribing to a ticket refund event, mixing in a bit of choreography into our data-centric backend. ![A diagram of a ticketing service whose components use direct messaging to intercommunicate.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Notification%20to%20Notification.png) Another case is found with data processing pipelines where an element may periodically read new files from a folder or new records from a database table to avoid implementing notifications. This increases latency and may cause a little CPU load when the system is idle, but is perfectly ok for long-running calculations. ![Stepwise processing of a batch of files.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Shared%20files.png) Finally, there is the rarely used option of an external [[wiki/concepts/source/extension-metapatterns/proxy|*Scheduler*]] which selects the services which should run based on the data available. This is known as [[wiki/concepts/source/extension-metapatterns/sandwich|*Blackboard System*]], and [[wiki/concepts/source/basic-metapatterns/monolith|something similar]] happens in 3D game engines. The *Scheduler* (which in this case serves as an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]]) is needed when CPU (or GPU or RAM) resources are much lower than what the services would consume if all of them ran in parallel, thus they must be given priorities, and the priorities change based on the context which is regularly estimated from the latest data. ![Components of the Blackboard Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Blackboard.png) > There is no clear distinction between [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]] and [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]], and between [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] and [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]]. Their [[wiki/concepts/source/introduction/system-topologies|topologies]] are identical, and functionality is often intermixed. Indeed, a database can be used for messaging, as we see below, and many communication frameworks store the history of messages; while the [[wiki/concepts/source/introduction/system-topologies|managing layer]] can both provide protocol support and implement use cases, as seen with [[wiki/concepts/source/extension-metapatterns/proxy|*API Gateways*]]. *Scheduler*, which is normally a *Proxy* with a simple [round-robin](https://micrium.atlassian.net/wiki/spaces/osiiidoc/pages/131360/Round-Robin+Scheduling) or [preemption](https://micrium.atlassian.net/wiki/spaces/osiiidoc/pages/131347/Preemptive+Scheduling) algorithm, in *Blackboard* is delegated complex strategic planning, which turns it into an *Orchestrator*. ## Messaging The other, not as obvious, use case for shared data is messaging, which is implemented by the sender writing to a (shared) queue (or log) while the recipient is waiting to read from it. Queues can be used for any kind of messages: request/confirm pairs, commands, or notifications. Each service may have a dedicated queue (either input for commands mode or output for notifications), a pair of queues (messages from the service’s output are duplicated by an underlying distributed [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] to input queues of their destinations), or there may be a queue per communication channel, or a single queue for the entire system (or one global-level queue per message priority) with each message carrying destination id (for commands) or topic (for notifications). ![Diagrams for: a queue per service, separate input and output queues, a queue per channel, and a single system queue.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Queues.png) The use of shared data for messaging turns our data store into a [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]]. The dependencies are identical to [[wiki/concepts/source/foundations-of-software-architecture/choreography|those in choreography]] – each service depends on the APIs of its destinations for commands or its sources for notifications. There should be a means for the recipient of a message to know about its arrival so that it starts processing the input. Usually a messaging *Middleware* implements a receive() method for the service to block on. However, very low latency applications, like [HFT](https://en.wikipedia.org/wiki/High-frequency_trading), may [busy-wait](https://en.wikipedia.org/wiki/Busy_waiting) by repeatedly re-reading the shared memory so that the service starts processing the incoming data immediately on its arrival, bypassing the OS scheduler. This is the fastest means of communication available in software. ## Full-featured Finally, some (usually distributed) data stores implement data change notifications. That allows for the services to communicate through the data store in near real-time, removing both the need for an additional *Middleware* and interdependencies for the services. Such a system follows the [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]] pattern of \[[wiki/concepts/source/appendices/books-referenced|[POSA4]]\] which was rectified as [[wiki/concepts/source/extension-metapatterns/shared-repository|*Space-Based Architecture*]]. In our example, the available seats notification service subscribes to changes in the seats data in the database – this way it does not need to be aware of the existence of other services at all. We can also move the email notifications logic of the ticket purchase service into a separate component which would track purchases in the database and send a printable version of each newly acquired ticket to the buyer’s email address which can be found in the ticket details in the database. ![A diagram of a ticketing service whose components rely on database notifications.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Notification%20inside%20the%20DB.png) ## Summary Communication through shared data is best suited for data-centric domains (for example, ticket purchase). It allows for the services to be unaware of each other’s existence, just as they are with orchestration, but the structure of the domain data becomes hard to change as it is referenced all over the code. Shared data may also be used to implement messaging. --- title: "Backends for Frontends (BFF)" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Fragmented metapatterns/Backends for Frontends (BFF).md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Fragmented%20metapatterns/Backends%20for%20Frontends%20%28BFF%29 source_license_note: "See namespace README; preserve attribution and source links." --- # Backends for Frontends (BFF) > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Fragmented metapatterns/Backends for Frontends (BFF).md`. ![A diagram for Services with Backends for Frontends, in abstractness-subdomain-sharding coordinates.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Main/Backends%20for%20Frontends.png) *Hire a local guide.* Dedicate a service for every kind of client. Known as: Backends for Frontends (BFF), [Layered Microservice Architecture](https://github.com/wso2/reference-architecture/blob/master/api-driven-microservice-architecture.md). Structure: A layer of integration services over a shared layer of core services. Type: Extension component, derived from [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] and/or [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]]. | *Benefits* | *Drawbacks* | | --- | --- | | Clients become independent in their protocols, workflows and, to an extent, qualities | No single place for cross-cutting concerns | | A specialized team and technology per client may be employed | More work for the DevOps team | | The multiple *Orchestrators* are smaller and more cohesive than a universal one would be | | References: The [original article](https://samnewman.io/patterns/architectural/bff/), a [smaller one](https://learn.microsoft.com/en-us/azure/architecture/patterns/backends-for-frontends) from Microsoft, and an [excerpt](https://microservices.io/patterns/apigateway.html) from \[[wiki/concepts/source/appendices/books-referenced|[MP]]\]. Here are the [reference diagrams](https://github.com/wso2/reference-architecture/blob/master/api-driven-microservice-architecture.md) from WSO2 (notice multiple *Microgateway* \+ *Integration Microservice* pairs). If some aspect(s) of serving our system’s clients strongly vary by client type (e.g. OLAP vs OLTP requests, user vs admin privileges, buyer vs seller vs customer support roles), it makes sense to use a dedicated component (the titular *Backend for Frontend* or *BFF*) per client type to encapsulate that variation. Protocol variations call for multiple [[wiki/concepts/source/extension-metapatterns/proxy|*Proxies*]], workflow variations – for several [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrators*]], both coming together – for [[wiki/concepts/source/extension-metapatterns/orchestrator|*API Gateways*]] or *Proxy \+ Orchestrator* pairs. It is even possible to vary the *BFF*’s programming language on a per client basis. The drawback is that once the clients get their dedicated *BFFs* it becomes hard to share a common functionality between them, unless you are willing to add yet another new utility [[wiki/concepts/source/basic-metapatterns/services|*service*]] (that will strongly smell of [*SOA*]()) or a [[wiki/concepts/source/basic-metapatterns/layers|*layer*]] that can be used by each of them. ### Performance As the multiple *Orchestrators* of *BFF* don’t intercommunicate, the pattern’s performance is identical to that of an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]]: it also slows down request processing in the general case but allows for several [[wiki/concepts/source/extension-metapatterns/orchestrator|specific optimizations]], including direct communication channels between the orchestrated [[wiki/concepts/source/basic-metapatterns/services|services]]. ### Dependencies Each *BFF* depends on all the services which it uses (usually every service in the system). The services themselves are likely to be independent, as is common in [[wiki/concepts/source/foundations-of-software-architecture/orchestration|*orchestrated* systems]]. ![Each Backend for Frontend depends on every service which it calls.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Dependencies/Backends%20for%20Frontends.png) ### Applicability *Backends for Frontends* are good for: - *Multiple client protocols.* Deploying a [[wiki/concepts/source/extension-metapatterns/proxy|*Gateway*]] per protocol hides the variation from the underlying system. - *Multiple UIs.* When you have one team per UI, each of them may [want to have](https://netflixtechblog.com/embracing-the-differences-inside-the-netflix-api-redesign-15fd8b3dc49d) an API which they feel comfortable with. - *Drastically different workflows.* Let each client-facing development team own a component and choose the best fitting technologies and practices. *Backends for Frontends* should be avoided when: - *The clients are mostly similar.* It is hard to share code and functionality between *BFF*s. If the clients have much in common, the shared aspects either find their place in a shared monolithic layer (e.g. multiple client protocols call for multiple [[wiki/concepts/source/extension-metapatterns/proxy|*Gateways*]] but a shared [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]]) or are duplicated. *BFF* may not be the best choice – use OOD (conditions, factories, strategies, and inheritance) instead to handle the clients’ differences within a single codebase. ### Relations ![Diagrams of Backends for Frontends over a monolith, layers, shards, and services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Relations/BFF.png) *Backends for Frontends*: - Extends [[wiki/concepts/source/basic-metapatterns/services|*Services*]] or rarely [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]], [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]], or [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]]. - Is derived from a client-facing extension pattern: [[wiki/concepts/source/extension-metapatterns/proxy|*Gateway*]], [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]], [[wiki/concepts/source/extension-metapatterns/orchestrator|*API Gateway*]], or [[wiki/concepts/source/extension-metapatterns/orchestrator|*Event Mediator*]]. ## Variants *Backends for Frontends* vary according to the kind of component that gets dedicated to each client: - A [*Proxy*](#proxies) per client when clients differ in protocols. - An [*Orchestrator*](#orchestrators) or [*Event Mediator*](#event-mediators) per client for different client roles and use cases. - A [*Proxy \+ Orchestrator* pair](#proxy--orchestrator-pairs) or an [*API Gateway*](#api-gateways) when clients differ in both protocols and roles. ### [[wiki/concepts/source/extension-metapatterns/proxy|Proxies]] ![Each gateway in the Backends for Frontends layer adapts its client's protocol and calls the services of the domain layer.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/BFF%20-%20Gateways.png) Dedicating a [[wiki/concepts/source/extension-metapatterns/proxy|*Gateway*]] per client is useful when the clients differ in the mode of access to the system (protocols / encryption / authorization) but not in workflows. ### [[wiki/concepts/source/extension-metapatterns/orchestrator|Orchestrators]] ![Each orchestrator in the Backends for Frontends layer calls the services of the domain layer.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/BFF%20-%20Orchestrators.png) An [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] per client makes sense if the clients use the system in completely unrelated ways, e.g. a shop’s customers have little to share with its administrators. ### [[wiki/concepts/source/extension-metapatterns/proxy|Proxy]] \+ [[wiki/concepts/source/extension-metapatterns/orchestrator|Orchestrator]] pairs ![In each pair in the Backends for Frontends layer the gateway adapts its client's protocol while the orchestrator calls the services of the domain layer.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/BFF%20-%20Gateways%20%2B%20Orchestrators.png) Clients vary in both access mode (protocol) and workflow. [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrators*]] or [[wiki/concepts/source/extension-metapatterns/proxy|*Proxies*]] may be reused if some kinds of clients share only the protocol or [[wiki/concepts/source/basic-metapatterns/layers|*application logic*]]. ### [[wiki/concepts/source/extension-metapatterns/orchestrator|API Gateways]] ![Each API Gateway in the Backends for Frontends layer both adapts its client's protocol and orchestrates the services of the domain layer.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/BFF%20-%20API%20gateways.png) Clients vary in access mode (protocol) and workflow and there is a third-party [[wiki/concepts/source/extension-metapatterns/orchestrator|*API Gateway*]] framework which seems to fit your requirements off the shelf. Multiple *API Gateways* match the literal meaning of *Backends for Frontends* – each UI team ([backend, mobile, desktop](https://www.thoughtworks.com/insights/blog/bff-soundcloud); or [end-device-specific](https://netflixtechblog.com/embracing-the-differences-inside-the-netflix-api-redesign-15fd8b3dc49d) teams) gets some code on the backend side to adapt the system’s API and protocols to its needs by building a new, probably higher-level specialized API with a convenient transport. ### [[wiki/concepts/source/extension-metapatterns/orchestrator|Event Mediators]] ![Each event mediator in the Backends for Frontends layer orchestrates the services of the domain layer.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/BFF%20-%20Event%20mediators.png) \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] mentions that multiple [[wiki/concepts/source/extension-metapatterns/orchestrator|*Event Mediators*]] may be deployed in [[wiki/concepts/source/basic-metapatterns/pipeline|*Event-Driven Architecture*]] to split the codebase and improve stability. ## Evolutions *BFF*-specific evolutions aim at sharing logic between the *BFF*s: - The *BFF*s can be merged into a single [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] if their functionality becomes mostly identical. - A shared *orchestration* [[wiki/concepts/source/basic-metapatterns/layers|*layer*]] with common functionality may be added for use by the *BFF*s. - A layer of *Integration Services* under the *BFF*s simplifies them by providing shared high-level APIs for the resulting [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cells*]]. - [[wiki/concepts/source/extension-metapatterns/proxy|*Sidecars*]] (of [[wiki/concepts/source/implementation-metapatterns/mesh|*Service Mesh*]]) are a way to share libraries among the *BFF*s. ![Backends for Frontends can be merged into an Orchestrator, can share code via sidecars, or put shared functionality into a dedicated orchestration layer or into Cell gateways.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/3/BFF.png) ## Summary *Backends for Frontends* assigns a [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]] and/or an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] per each kind of a system’s client to encapsulate client-specific use cases and protocols. The drawback is that there is no good way for sharing functionality between the *BFF*s. --- title: "Fragmented metapatterns" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Fragmented metapatterns/Fragmented metapatterns.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Fragmented%20metapatterns/Fragmented%20metapatterns source_license_note: "See namespace README; preserve attribution and source links." --- # Fragmented metapatterns > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Fragmented metapatterns/Fragmented metapatterns.md`. There are several [[wiki/concepts/source/introduction/system-topologies|topologies]] with no system-wide layers. Some of them incorporate two or three orthogonal domains which vary in abstractness to the extent that a service (limited to a subdomain) of one domain acts as a layer for another domain. ### [[wiki/concepts/source/fragmented-metapatterns/layered-services|Layered Services]] ![A diagram of Layered Services, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Layered%20Services.png) [[wiki/concepts/source/fragmented-metapatterns/layered-services|*Layered Services*]] is an umbrella metapattern which highlights implementation details of [[wiki/concepts/source/basic-metapatterns/services|*Services*]], [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]], or [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]]. *Includes*: Orchestrated Three-Layered Services, Choreographed Two-Layered Services, and Command Query Responsibility Segregation (CQRS). ### [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Polyglot Persistence]] ![A diagram of Services with Polyglot Persistence, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Polyglot%20Persistence.png) [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]] is about using multiple data stores which differ in roles or technologies. Each of the upper-level components may have access to any data store. Each data store is a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]]. *Includes*: specialized data stores, private and shared databases, data file, and Content Delivery Network (CDN); read-only replicas, Reporting Database, CQRS View Database, Memory Image, Query Service, search index, historical data, and Cache-Aside. ### [Backends for Frontends]() ![A diagram of Services with Backends for Frontends, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Backends%20for%20Frontends.png) [*Backends for Frontends*]() feature a service (*BFF*) for each kind of the system’s client. A *BFF* may be a [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]], [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]], or both. Each *BFF* communicates with all the components below it. The pattern looks like multiple *Proxies* or *Orchestrators* deployed in parallel. *Includes*: Layered Microservice Architecture. ### [Service-Oriented Architecture]() ![A diagram of Service-Oriented Architecture, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Service-Oriented%20Architecture.png) [*SOA*]() comprises three or four layers of services, with each layer making a domain. The upper layer contains [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrators*]] which are often client-specific, just like [*BFF*]()*s*. The second layer incorporates business rules and is divided into business subdomains. The lower layer(s) are libraries and utilities, grouped by functionality and technologies. Any component may use (orchestrate) anything below it. *Includes*: distributed monolith, enterprise SOA, and Domain-Oriented Microservice Architecture (DOMA). ### [[wiki/concepts/source/fragmented-metapatterns/hierarchy|Hierarchy]] ![A diagram of Hierarchy, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Hierarchy.png) Some domains allow for [[wiki/concepts/source/fragmented-metapatterns/hierarchy|hierarchical composition]] where the functionality is spread over a tree of components. *Includes*: Orchestrator of Orchestrators, Presentation-Abstraction-Control (PAC) and Hierarchical Model-View-Controller (HMVC), Bus of Buses, and the WSO2 version of Cell-Based (Microservice) Architecture (Services of Services). --- title: "Hierarchy" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Fragmented metapatterns/Hierarchy.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Fragmented%20metapatterns/Hierarchy source_license_note: "See namespace README; preserve attribution and source links." --- # Hierarchy > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Fragmented metapatterns/Hierarchy.md`. ![A diagram for Hierarchy, in abstractness-subdomain-sharding coordinates.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Main/Hierarchy.png) *Command and conquer.* Build a tree of responsibilities. Structure: A tree of components. Type: System topology or extension component. | *Benefits* | *Drawbacks* | | --- | --- | | Very good in decoupling logic | Global use cases may be hard to debug | | Supports multiple development teams and technologies | Poor latency for global use cases | | Components may vary in their qualities | Operational complexity | | Low-level components are easy to replace | Slow start of the project | | Limited fault tolerance | | References: None good I know of. Though not applicable to every domain, hierarchical decomposition is arguably the best way to distribute responsibilities between components. It limits the connections (thus the number of interfaces and contracts for the team to keep in mind) of each component to its parent and a few children, allowing for the building of complex (and even complicated) systems in a simple way. The hierarchical structure is very flexible as it features [multiple layers of indirection](https://en.wikipedia.org/wiki/Fundamental_theorem_of_software_engineering) (and often polymorphism), which makes addition, replacement, or [stubbing/mocking](https://stackoverflow.com/questions/3459287/whats-the-difference-between-a-mock-stub) of leaf components trivial. It is also quite fault-tolerant as individual subtrees operate independently. This architecture is not ubiquitous because few domains are truly hierarchical. Its high fragmentation results in increased latency and poor debugging experience. Moreover, component interfaces must be designed beforehand and are hard to change. ### Performance No kind of distributed hierarchy is latency-friendly as many use cases involve several network hops. The fewer layers of the hierarchy are involved in a task, the better its performance. ![Comparison of latency for decision-making at various levels of a hierarchy.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Performance/Hierarchy%20-%20speed.png) Maintaining high throughput usually requires deployment of multiple instances of the root component, which is not possible if it is stateful (as in [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|*control systems*]]) and the state cannot be split into [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]]. The following tricks may help unloading the root: - *Aggregation* ([[wiki/concepts/source/basic-metapatterns/layers|first met]] in [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]]): a node of a hierarchy collects reports from its children, aggregates them into a single package, and sends the aggregated data up to its parent. This greatly reduces traffic to the root in large [IIoT](https://en.wikipedia.org/wiki/Industrial_internet_of_things) networks. - *Delegation* (resembles [[wiki/concepts/source/basic-metapatterns/layers|strategy injection and batching]] for [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]]): a node should try to handle all the low-level details of communication with its children without consulting its parent node. For a [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|control system]] that means that its mid-level nodes should implement control loops for the majority of incoming events. For a [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|computational system]] that means that its mid-level nodes should expose coarse-grained interfaces to their parent(s) while translating each API method call into multiple calls to their child nodes. - *Direct communication channels* ([[wiki/concepts/source/extension-metapatterns/orchestrator|previously described]] for [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]]): if the low-level nodes need to exchange data, their communication should not always go through the higher-level nodes. Instead, they may negotiate a direct link (open a socket) that bypasses the root of the hierarchy. ![Aggregation of data in mid-level nodes; autonomous decision-making by mid-level nodes; direct communication between low-level nodes of a hierarchy.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Performance/Hierarchy%20-%20optimizations.png) ### Dependencies A parent node would usually define one (for polymorphic children) or more (otherwise) [*SPIs*](https://en.wikipedia.org/wiki/Service_provider_interface) for its child nodes to implement. The interfaces reside on the parent side because low-level nodes tend to be less stable (new types of them are often added and old ones replaced) therefore we don’t want our main business logic to depend on them. ![In Hierarchy a child component depends on an SPI of its parent component. If the children are polymorphic, their parent has a single SPI.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Dependencies/Hierarchy.png) ### Applicability *Hierarchy* fits with: - *Large and huge projects.* The natural division by both level of abstractness and subdomain allows for using smaller modules, ideally with intuitive interfaces. The APIs for each team to learn are limited to just a few which their component interacts with directly. - *Systems of hardware devices.* Real-world [IIoT](https://en.wikipedia.org/wiki/Industrial_internet_of_things) systems may use a hierarchy of controllers to benefit from [[wiki/concepts/source/foundations-of-software-architecture/forces-asynchronicity-and-distribution|autonomous decision-making and data aggregation]]. - *Customization*. The tree-like structure provides opportunities for easy customization. A medium-sized hierarchical system may integrate hundreds of leaf types. - *Survivability*. A distributed hierarchy retains limited functionality even if several of its nodes fail. *Hierarchy* fails with: - *Cohesive domains.* Horizontal interactions between nodes that belong to the same layer bloat interfaces as they have to pass through parent nodes. - *Quick start*. Finding (and verifying) a good hierarchical domain model may be hard if at all possible. Debugging an initial implementation will not be easy. - *Low latency*. System-wide scenarios involve many cross-component interactions which are slow in distributed systems. ### Relations ![Diagrams of Orchestrator of Orchestrators, Middleware of Middlewares, and Services of Services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Relations/Hierarchy.png) *Hierarchy*: - Can be applied to [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]]*,* [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] or [[wiki/concepts/source/basic-metapatterns/services|*Services*]]. ## Variants by structure (may vary per node) *Hierarchy* comes in various shapes as it is more of a design approach than a ready-to-use pattern: ### Polymorphic children All the child nodes managed by a given parent node expose the same interface and contract. This tends to simplify the implementation of the parent and resembles the inheritance of OOD. Example: a fire alarm system may treat all of its fire sensors as identical devices, even though the real hardware comes from many manufacturers. ### Functionally distinct children The managing node is aware of several kinds of children that vary in their APIs and contracts, just like with the composition in OOD. Example: an intrusion alarm logic may need to discern between cat-affected IR sensors and mostly cat-proof glass break detectors. ## Variants by direction A *Hierarchy* may have its root [at the top](#top-down-hierarchy-orchestrator-of-orchestrators-presentation-abstraction-control-pac-hierarchical-model-view-controller-hmvc) (client side), [at the bottom](#bottom-up-hierarchy-bus-of-buses-network-of-networks-hierarchical-middleware) (deep infrastructure), or [no root at all](#in-depth-hierarchy-cell-based-microservice-architecture-wso2-version-segmented-microservice-architecture-services-of-services-clusters-of-services) (recursive decomposition): ### Top-Down Hierarchy: [[wiki/concepts/source/extension-metapatterns/orchestrator|Orchestrator]] of Orchestrators, Presentation-Abstraction-Control (PAC), Hierarchical [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Model-View-Controller]] (HMVC) ![A single component calls two components in the layer below it, each of which calls two or three lower-level leaf components.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/Hierarchy%20-%20Top-down.png) In the most common case *Hierarchy* is applied to business logic to build a layered system which grows from a single generic high-level root into a swarm of specialized low-level pieces. The most obvious applications are protocol parsers, decision trees, [IIoT](https://en.wikipedia.org/wiki/Industrial_internet_of_things) (e.g. a fire alarm system of a building), and [modern automotive](https://semiengineering.com/managing-todays-advanced-vehicle-networks-design-challenges/) networks. A marketplace that allows for customized search and marketing algorithms within each category of its goods may also be powered by a hierarchy of category-specific services. [*Presentation-Abstraction-Control*](https://en.wikipedia.org/wiki/Presentation%E2%80%93abstraction%E2%80%93control) (*PAC*) \[[wiki/concepts/source/appendices/books-referenced|[POSA1]], [[wiki/concepts/source/appendices/books-referenced|POSA4]]\] applies *Top-Down Hierarchy* to a user-facing application, providing each of the resulting [[wiki/concepts/source/basic-metapatterns/layers|*layered*]] nodes with its own widget (*presentation*) on the UI screen (which is the *presentation* of the root node). *Controls* are responsible for inter-node communication and [[wiki/concepts/source/basic-metapatterns/layers|integration logic]], while [[wiki/concepts/source/basic-metapatterns/layers|domain logic]] and data reside in *abstractions*. [*Hierarchical Model-View-Controller*](https://herbertograca.com/2017/08/17/mvc-and-its-variants/#hierarchical-model-view-controller) (*HMVC*) is similar, but its *views* access *models* directly, like in [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*MVC*]], and every model synchronizes with the global data. This pattern [was used](https://web.archive.org/web/20060319064042/http://www.javaworld.com/javaworld/jw-09-2000/jw-0908-letters.html) in [rich clients](https://en.wikipedia.org/wiki/Rich_client). ![Both Presentation-Abstraction-Control and Hierarchical Model-View-Controller are top-down hierarchies with three-component nodes, which share a database in the second pattern.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/PAC.png) ### Bottom-Up Hierarchy: [[wiki/concepts/source/extension-metapatterns/middleware|Bus]] of Buses, Network of Networks, Hierarchical Middleware ![An integration middleware interconnects the middlewares of two systems.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/Hierarchy%20-%20Bottom-up.png) Other cases require building a common base for intercommunication between several networks which vary in their protocols (and often their hardware). The root of such a *Hierarchy* is a [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] generic and powerful enough to cover the needs of all the specialized networks which it interconnects. Example: [Automotive networks](https://www.mdpi.com/1424-8220/21/23/7917), integration of corporate networks, [the Internet](https://en.wikipedia.org/wiki/Internet_service_provider). ### In-Depth Hierarchy: [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Cell]]-Based (Microservice) Architecture (WSO2 version), Segmented Microservice Architecture, [[wiki/concepts/source/basic-metapatterns/services|Services]] of Services, Clusters of Services ![Cells of different kinds communicate with each other through cell gateways.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/Cell-Based%20Architecture.png) When several [[wiki/concepts/source/basic-metapatterns/services|*services*]] in a system grow large, in some cases it is possible to divide each of them into *subservices*. Each group of the resulting subservices (known as a [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cell*]], [*Domain*](https://www.uber.com/blog/microservice-architecture/) or *Cluster* \[[wiki/concepts/source/appendices/books-referenced|[DEDS]]\]) usually implements a *bounded context* \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]. It is hidden behind its own [[wiki/concepts/source/extension-metapatterns/proxy|*Cell Gateway*]] and may even use its own [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]]. Subservices of a *Cell* may [[wiki/concepts/source/extension-metapatterns/shared-repository|*share a database*]] and may be deployed as a single unit. This keeps the system’s integration complexity (the length of its APIs and the number of deployable units) reasonable while still scaling development among many teams or individuals, each owning a service. If each instance of a *Cell* owns a [[wiki/concepts/source/basic-metapatterns/shards|*shard*]] of its database, the system [becomes more stable](https://docs.aws.amazon.com/wellarchitected/latest/reducing-scope-of-impact-with-cell-based-architecture/what-is-a-cell-based-architecture.html) as there is no single point of failure (except for the [[wiki/concepts/source/extension-metapatterns/proxy|*Load Balancer*]] called *Cell Router*). Another benefit is that *Cells* can be deployed to regional data centers to improve locality for users of the system. However, that will likely cause data synchronization traffic between the data centers. The [*Cell-Based Architecture*](https://github.com/wso2/reference-architecture/blob/master/reference-architecture-cell-based.md) ([*Segmented Microservice Architecture*](https://github.com/wso2/reference-architecture/blob/master/api-driven-microservice-architecture.md)) may be seen as a combination of an *Orchestrator of Orchestrators* and a *Bus of Buses* where the subservices are leaf nodes of both *hierarchies* while the [[wiki/concepts/source/extension-metapatterns/proxy|*API Gateways*]] of the *Cells* are their internal nodes. Uber [compacted](https://www.uber.com/blog/microservice-architecture/) 2200 [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]] into 70 *Cells* arranged in a [*SOA*]()-style topology called [*Domain-Oriented Microservice Architecture*](). ## Evolutions - The upper component of a *Top-Down Hierarchy* can be split into [*Backends for Frontends*](). ![The upper layer of a top-down hierarchy is subdivided into Backends for Frontends.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/3/Hierarchy%20-%201.png) ## Summary *Hierarchy* fits a project of any size as it evenly distributes complexity among the system’s many components. However, it is not without drawbacks in performance, debuggability, and operational complexity. Moreover, very few domains allow for seamless application of this architecture. --- title: "Layered Services" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Fragmented metapatterns/Layered Services.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Fragmented%20metapatterns/Layered%20Services source_license_note: "See namespace README; preserve attribution and source links." --- # Layered Services > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Fragmented metapatterns/Layered Services.md`. ![A diagram for Layered Services, in abstractness-subdomain-sharding coordinates.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Main/Layered%20Services.png) *Cut the cake.* Divide each service into layers. Structure: Subdomain services divided into layers. Type: Implementation of [[wiki/concepts/source/basic-metapatterns/services|*Services*]], [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]], or [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]]. *Layered Services* is an umbrella architecture for common implementations of systems of [[wiki/concepts/source/basic-metapatterns/services|*Services*]]. It does not introduce any special features as the layers are completely encapsulated by the service which they belong to. Still, as the services may communicate at different layers, there are a couple of things to learn by exploring the subject matter. ### Performance *Layered Services* are similar to [[wiki/concepts/source/basic-metapatterns/services|*Services*]] performance-wise: use cases that involve a single service are the fastest, those that need to synchronize states of multiple services are the slowest. Remarkable features of *Layered Services* include: - Independent scaling of the layers inside the services. It is common to have multiple [[wiki/concepts/source/basic-metapatterns/shards|instances]] (with the number varying from service to service and changing dynamically under load) of the layers that contain business logic while the corresponding [[wiki/concepts/source/basic-metapatterns/layers|data layers]] (databases) are limited to a single instance. ![The use of scaled stateless services and load balancers in Layered Services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Performance/Layered%20Services%20-%20sharding.png) - The option to establish additional communication channels between the lower layers in order to drive [*CQRS*](#command-query-responsibility-segregation-cqrs) databases ([[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|read/write replicas]] of the same database) or [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*CQRS Views*]] (cached subsets of data from other services) \[[wiki/concepts/source/appendices/books-referenced|[MP]]\]. ![Data streams in Three-Layered Services: from data layer to data layer, from domain layer to data layer, and between two domain-level components.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Performance/Layered%20Services%20-%20channels.png) ## Variants *Layered Services* vary in the number of [[wiki/concepts/source/basic-metapatterns/layers|layers]] and in the layer through which the [[wiki/concepts/source/basic-metapatterns/services|services]] communicate: - In [*Orchestrated Three-Layered Services*](#orchestrated-three-layered-services) the application layer of any service may call the same layer in other services. - In [*Choreographed Two-Layered Services*](#choreographed-two-layered-services) the domain layers of services are assembled into a *Pipeline*. - [*Command Query Responsibility Segregation*](#command-query-responsibility-segregation-cqrs) streams changes from a transactional to an analytical database. ## Orchestrated Three-Layered Services ![In three-layered services the application layer of every service calls the domain layer of its service and the application layers of other services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/Three-Layered%20Services.png) Likely the most common backend architecture has [[wiki/concepts/source/basic-metapatterns/layers|three layers]]: [[wiki/concepts/source/basic-metapatterns/layers|*application*]], [[wiki/concepts/source/basic-metapatterns/layers|*domain*]], and *infrastructure* \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]. The application layer [[wiki/concepts/source/foundations-of-software-architecture/orchestration|*orchestrates*]] the domain layer. If such an architecture is divided into [[wiki/concepts/source/basic-metapatterns/services|services]], each of them receives a part of every layer, including application, which means that now there are as many *Orchestrators* as services. Each *Orchestrator* implements the API of its service by integrating (calling or messaging into) the domain layer of its service and the APIs of other services, which makes all the *Orchestrators* interdependent: ### Dependencies The upper ([[wiki/concepts/source/basic-metapatterns/layers|*application*]]) layer of each service orchestrates both its middle (*domain*) layer and the upper layers of other services, resulting in [[wiki/concepts/source/foundations-of-software-architecture/orchestration|mutual orchestration and interdependencies]]. ![In Layered Services only the application layers of the services are interdependent.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Communication/Mutual%20Orchestration%20-%204.png) The good thing is that the majority of the code belongs to the domain layer which depends only on its service’s database. The bad thing is that changes in the application of one service may affect the application layers of all of the other services. ### Relations *Three-layered services*: - Implement [[wiki/concepts/source/basic-metapatterns/services|*Services*]]. - Are derived from [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] and [[wiki/concepts/source/basic-metapatterns/services|*Services*]]. - Have multiple [[wiki/concepts/source/extension-metapatterns/orchestrator|*Integration (sub)Services*]] ([[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrators*]]). ### Evolutions *Orchestrated Layered Services* may become coupled, which is resolved either by merging their layers: - A part of or the whole [[wiki/concepts/source/basic-metapatterns/layers|*application layer*]] can be merged into a shared [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]]. - Some or all the [[wiki/concepts/source/basic-metapatterns/layers|*databases*]] can be united into a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Database*]] or shared as [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]]. - Both the *application* and *data* layers can be merged into a [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]]*.* ![Diagrams for Three-Layered Services with partially merged application layer, partially merged databases and shared databases, and a Sandwich.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/3/Three-Layered%20Services%20-%201.png) or by building derived datasets: - A [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*CQRS View*]] inside a service aggregates any events from other services which its host is interested in. - A dedicated [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Query Service*]] captures the whole system’s state by subscribing to events from all the services. ![Diagrams for Three-Layered Services employing CQRS views and a Query Service.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/3/Three-Layered%20Services%20-%202.png) If a service becomes too large: - Its middle layer can be split, resulting in a [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]] or [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cell*]]. ![The domain layer of a large three-layered service is split into sub-subdomain components, resulting in a Sandwich Cell.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/3/Three-Layered%20Services%20-%203.png) ## Choreographed Two-Layered Services ![The domain-level components of two-layered services participate in multiple pipelines and access their service's databases.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/Two-Layered%20Services.png) If there is no [[wiki/concepts/source/foundations-of-software-architecture/orchestration|*orchestration*]], there is no role for the [[wiki/concepts/source/basic-metapatterns/layers|*application* layer]]. [[wiki/concepts/source/foundations-of-software-architecture/choreography|*Choreographed*]] systems are made up of services that implement individual steps of request processing. The sequence of actions (*integration logic*) which three-layered systems put in the [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrators*]] now moves to the graph of *event channels* between the services. This means that with choreography the high-level part of the business logic (use cases) exists outside of the code for the system’s constituent services. ### Dependencies Dependencies are identical to those of a [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]] or [[wiki/concepts/source/foundations-of-software-architecture/choreography|*choreographed*]] [[wiki/concepts/source/basic-metapatterns/services|*Services*]] except that each service also depends on its database. ### Relations *Two-layered services*: - Implement [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]]. - Are derived from [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] and [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]]. ### Evolutions If *Choreographed Layered Services* become coupled: - The *business logic* of two or more services can be merged together, resulting in [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]]. - Some databases can be united into a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Database*]] or shared as [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]]. ![Diagrams for Two-Layered Services with partially merged domain layer, partially merged databases, and shared databases.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/3/Two-Layered%20Services%20-%201.png) [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*CQRS Views*]] or a [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Query Service*]] are also an option: ![Diagrams for Two-Layered Services employing CQRS views and a Query Service.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/3/Two-Layered%20Services%20-%202.png) An overgrown service can be: - Split in two ![The domain layer of a large two-layered service is split in half.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/3/Two-Layered%20Services%20-%203.png) ## [[wiki/concepts/source/extension-metapatterns/sandwich|Command Query Responsibility Segregation]] (CQRS) ![Write requests from a client go to the write backend and OLTP database which feeds OLAP databases. Read requests go to the scaled read backend and the scaled OLAP database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/CQRS.png) *Command Query Responsibility Segregation* (*CQRS*) \[[wiki/concepts/source/appendices/books-referenced|[MP]], [[wiki/concepts/source/appendices/books-referenced|LDDD]]\] is, essentially, the division of a [[wiki/concepts/source/basic-metapatterns/layers|layered]] application or a service into two (rarely more) [[wiki/concepts/source/basic-metapatterns/services|services]], one of which is responsible for write access (handling *commands*) to the domain data while the other(s) deal with read access (*queries*), thus [[wiki/concepts/source/analytics/pipelines-in-architectural-patterns|creating]] a data [[wiki/concepts/source/basic-metapatterns/pipeline|*pipeline*]] (see the diagram below). Such an architecture makes sense when the write and read operations don’t rely on a common vision (*model*) of the domain, for example, writes are individual changes ([*OLTP*](https://en.wikipedia.org/wiki/Online_transaction_processing)) that require cross-checks and validation of input while reads show aggregated data ([*OLAP*](https://en.wikipedia.org/wiki/Online_analytical_processing)) and may take long time to complete (meaning that the [[wiki/concepts/source/foundations-of-software-architecture/forces-asynchronicity-and-distribution|*forces*]] for the read and write paths differ). If there is nothing to share in the code, why not separate the implementations? ![In CQRS data streams from the client to the write backend, then to the OLTP database, to the OLAP database, to the read backend and, finally, returns to the client.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/CQRS%20-%20pipeline%20view.png) This separation brings in the pros and cons of [[wiki/concepts/source/basic-metapatterns/services|*Services*]]: commands and queries may differ in technologies (including schemas or even kinds of the databases) and forces, and even be developed by separate teams, at the expense of [consistency](https://en.wikipedia.org/wiki/Eventual_consistency) (database replication delay) and increasing the system’s complexity. In addition, for read-heavy applications the read database(s) is easy to scale. *CQRS* has the following variations: - The database may be shared, commands and queries may use dedicated databases, or the read service may maintain a [*Memory Image*](https://martinfowler.com/bliki/MemoryImage.html) / [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Materialized View*]] fed by events from the write service (as in other kinds of *Layered Services*). - Data [[wiki/concepts/source/basic-metapatterns/shards|replication]] may be implemented as a [[wiki/concepts/source/basic-metapatterns/pipeline|*pipeline*]] between the databases (based on nightly snapshots or [log-based replication](https://www.dremio.com/wiki/log-based-replication/)) or a [direct event feed](https://martinfowler.com/bliki/EagerReadDerivation.html) from the OLTP code to the OLAP database. ![In CQRS the OLAP databases receive data from the OLTP database or from events originating in the write backend. Alternatively, the read and write backends may share a database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/CQRS%20-%20subtypes.png) It is noteworthy that while ordinary *Layered Services* usually communicate through their upper-level components that drive the use cases, a *CQRS* system is held together by spreading data changes through its lowest layer. Examples: Martin Fowler has a [short article](https://martinfowler.com/bliki/CQRS.html) and Microsoft a [longer one](https://learn.microsoft.com/en-us/azure/architecture/patterns/cqrs). ### Dependencies Each backend depends on its database (its technology and schema). The OLTP to OLAP data replication requires an additional dependency that comes from the way the replication is implemented: ![In CQRS each service depends on its database while the OLAP database depends on the source of its event feed.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Dependencies/CQRS.png) ### Relations *CQRS*: - Implements [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] (a whole *system* or a single [[wiki/concepts/source/basic-metapatterns/services|*service*]]). - Is derived from both [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] and [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]]. - Is a development of [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]]. ### Evolutions - You will usually need a [[wiki/concepts/source/extension-metapatterns/proxy|*Reverse Proxy*]] or an [[wiki/concepts/source/extension-metapatterns/proxy|*API Gateway*]] to segregate commands from queries. - If the commands and queries become intermixed, the business logic can be merged together but the databases are left separate, resulting in [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]]. - Both read and write backends can be split into [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] or [[wiki/concepts/source/basic-metapatterns/services|*Services*]] (yielding [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cells*]]). - Applying [[wiki/concepts/source/extension-metapatterns/shared-repository|*Space-Based Architecture*]] may further improve performance. - Multiple schemas or even kinds of OLAP databases can be used simultaneously ([[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]]). ![Diagrams of CQRS behind an API Gateway, with a single backend, with multiple OLAP databases, with layered backends, Cells for backends, and Data Grid for a database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/3/CQRS.png) ## Summary *Layered Services* is an umbrella pattern that conjoins: - *Three-Layered Services* where each service [[wiki/concepts/source/foundations-of-software-architecture/orchestration|*orchestrates*]] other services. - *Two-Layered Services* that form a [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]]. - *CQRS* that separates the read and write request processing paths. --- title: "Polyglot Persistence" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Fragmented metapatterns/Polyglot Persistence.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Fragmented%20metapatterns/Polyglot%20Persistence source_license_note: "See namespace README; preserve attribution and source links." --- # Polyglot Persistence > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Fragmented metapatterns/Polyglot Persistence.md`. ![A diagram for Services with Polyglot Persistence, in abstractness-subdomain-sharding coordinates.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Main/Polyglot%20Persistence.png) *Unbind your data.* Use multiple specialized data stores. Known as: Polyglot Persistence. Structure: A layer of data services used by higher-level components. Type: Extension component, derived from [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]]. | *Benefits* | *Drawbacks* | | --- | --- | | Performance is fine-tuned for various data types and use cases | The peculiarities of each data store need to be learned | | Less load on each data store | Muсh more work for the DevOps team | | The data stores may satisfy conflicting forces | More points of failure in the system | | | Consistency is hard or slow to achieve | References: The [original](https://martinfowler.com/bliki/PolyglotPersistence.html) and closely related [CQRS](https://martinfowler.com/bliki/CQRS.html) articles from Martin Fowler, chapter 7 of \[[wiki/concepts/source/appendices/books-referenced|[MP]]\], chapter 11 of \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\] and much information dispersed all over the Web. You can choose a dedicated technology for each kind of data or pattern of data access in your system. That improves performance (as each data store engine is optimized for a few use cases), distributes load between the data stores, and may solve conflicts between forces (like when you need both low latency and large storage). However, you may need to hire several experts to get the best use of and to support the multiple data stores, especially if those are full-featured databases. Moreover, having your data spread over multiple data stores makes it the application’s responsibility to keep the data in sync (by implementing some kind of distributed transactions or making sure that the clients don’t get stale data). > A *database* is a complex service which provides both data storage and such high-level functionality as transactions and analytical queries. A [*data store*](https://en.wikipedia.org/wiki/Data_store) is anything where data can be placed and retrieved: a shared memory, file system, cloud storage, or a database. Though this chapter mostly deals with databases, several patterns which it discusses pertain to non-database storage. ### Performance *Polyglot Persistence* aims at improving performance through the following means: - Optimize for [specific data use cases](#specialized-databases). It is impossible for a single database to be good at everything. - Redirect read traffic to [read-only database *replicas*](#read-only-replicas). The write-enabled *leader* database then processes only the write requests. - [*Cache* any frequently used data](#database-cache-cache-aside) in a fast in-memory data store to let the majority of client requests be served without hitting the slower persistent storage. - Build a [*view* of the states of other services](#reporting-database-cqrs-view-database-event-sourced-view-source-aligned-native-data-product-quantum-dpq-of-data-mesh) in the system to avoid querying them. - Maintain an external [*index*](#external-search-index) or [*Memory Image*](#memory-image-materialized-view) for use with tasks that don’t need the history of changes. - [Purge old data](#historical-data-data-archiving) to a slower storage. - Store read-only sequential [data as files](#data-file-content-delivery-network-cdn), often close to the end users who download them. Beware that the read-write separation introduces a replication lag \[[wiki/concepts/source/appendices/books-referenced|[MP]]\] which is a headache when both data consistency and responsiveness are important for the system’s clients. ### Dependencies In general, each service depends on all of the data stores which it uses. There may also be an additional dependency between the data stores if they share a dataset (one or more data stores are derived). ![The business logic depends on every database. A derived database depends on its data source.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Dependencies/PolyglotPersistence.png) ### Applicability *Polyglot Persistence* helps: - *High load and low latency projects.* [*Specialized Databases*](#specialized-databases) shine when given fitting tasks. [*Caching*](#database-cache-cache-aside) and [*Read-Only Replicas*](#read-only-replicas) take the load off the main database. [*External Search Indices*](#external-search-index) may occasionally save the day as well. - [*Event sourcing*](https://martinfowler.com/eaaDev/EventSourcing.html) *and* [*event collaboration*](https://martinfowler.com/eaaDev/EventCollaboration.html)*.* A [*Memory Image*](#memory-image-materialized-view) maintains the current state of an event-sourced component. A [*CQRS View*](#reporting-database-cqrs-view-database-event-sourced-view-source-aligned-native-data-product-quantum-dpq-of-data-mesh) aggregates domain events to provide its host service with whatever data from other subdomains it may need to use. - *Conflicting forces*. An instance of a stateless service inherits many of the qualities of the data store which it accesses for any given request it is processing. When there are several data stores, the qualities (e.g. latency) of a service instance may vary from request to request, depending on which data store is involved. *Polyglot Persistence* may harm: - *Small projects.* Properly setting up and maintaining multiple databases is not that easy. - *High availability*. Each data store which your system uses will tend to fail in its own crazy way. - *User experience*. For systems with read-write database separation the replication lag between the databases will make you [choose](https://medium.com/@Ian_carson/replication-lag-82c736081e32) between reading changes from the *leader* (write database), adding synchronization code to your application to wait for the read database to be updated, and risking returning outdated results to the users. ### Relations ![Polyglot Persistence for Monolith, Layers, Shards, and Services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Relations/Polyglot%20Persistence.png) *Polyglot Persistence*: - Extends [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]], [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]], [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]], or [[wiki/concepts/source/basic-metapatterns/services|*Services*]]. - Is derived from [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] (the persistence layer) or [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]]. - Variants with derived databases have an aspect of [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]] and are closely related to [[wiki/concepts/source/fragmented-metapatterns/layered-services|*CQRS*]]. ## Examples with independent storage Many cases of *Polyglot Persistence* use multiple data stores just because there is no single technology that matches all the application’s needs. The data stores used are filled with different subsets of the system’s data: - Each service may have its own private data while [another dataset is shared](#private-and-shared-databases). - [Specialized databases](#specialized-databases) that differ in storage and analytical technologies may be used. - Many kinds of data can be [stored as files](#data-file-content-delivery-network-cdn). ### Private and Shared Databases ![A subset of data shared between shards and between services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/PP%20-%20Private%20and%20Shared.png) If several services or shards become coupled through a subset of the system’s data, that subset can be put into a separate database which is accessible to all the participants. All the other data remains private to the [[wiki/concepts/source/basic-metapatterns/shards|shards]] or [[wiki/concepts/source/basic-metapatterns/services|services]]. ### Specialized Databases ![A service which uses both SQL and NoSQL databases.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/PP%20-%20Specialized.png) Databases [vary in their optimal use cases](https://www.jamesserra.com/archive/2015/07/what-is-polyglot-persistence/). You can employ several different databases to achieve the best performance for each kind of data that you persist. ### Data File, Content Delivery Network (CDN) ![A backend reads page templates while a frontend reads content from a content delivery network.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/PP%20-%20File%20Storage.png) Some data is happy to stay in files. Web frameworks load web page templates from OS files and store images and videos in a *Content Delivery Network* (*CDN*) which replicates the data all over the world so that each user downloads the content from the nearest server (which is faster and cheaper). ## Examples with derived storage In other cases there is a single writable data store (called *system of record* \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\]) which is the main *source of truth* from which the other data stores are derived. The primary reason to use several data stores is to [relieve the main database of read requests](#read-only-replicas) and maybe support some additional qualities: special kinds of queries, aggregation for [*materialized*](#memory-image-materialized-view) and [*CQRS views*](#reporting-database-cqrs-view-database-event-sourced-view-source-aligned-native-data-product-quantum-dpq-of-data-mesh), full text search for [*text indices*](#external-search-index), huge dataset size for [*historical data*](#historical-data-data-archiving), or high performance for an [*in-memory cache*](#database-cache-cache-aside). The updates to the derived data stores may come from: - the main database as [*Change Data Capture*](https://www.dremio.com/wiki/change-data-capture/) (*CDC*) \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\] (a log of changes), - the application after it changes the main data store (see caching strategies [below](#database-cache-cache-aside)), - another service as an *event stream* \[[wiki/concepts/source/appendices/books-referenced|[DDIA]], [[wiki/concepts/source/appendices/books-referenced|MP]]\], - a dedicated *indexer* that periodically crawls the main data store or web site. ![A derived database is fed data from the main database, from an indexer which scans the main database, from application events, or from events originating with another service.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/PP%20-%20Derived%20Storage.png) ### Read-Only [[wiki/concepts/source/basic-metapatterns/shards|Replicas]] ![An instance of a backend writes to a leader database which streams updates to database replicas. Other backend instances read from the replicas.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/Read-only%20Replica.png) Multiple instances of the database are deployed and one of them is the *leader* \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\] instance which processes all writes to the system’s data. The changes are then replicated to the other instances (via [*Change Data Capture*](https://www.dremio.com/wiki/change-data-capture/) (*CDC*)) which are used for read requests. Distributing workload over multiple instances increases maximum read throughput which the system is capable of, as the database is usually the system’s bottleneck. Having several running [[wiki/concepts/source/basic-metapatterns/shards|*replicas*]] greatly improves reliability and allows for nearly instant recovery of database failures as any replica may quickly be promoted to the leader role to serve write traffic. ### Database [[wiki/concepts/source/extension-metapatterns/proxy|Cache]], Cache-Aside ![A cache hit only reads from the cache; a cache miss reads from the cache, reads from the database, and writes to the cache; a write writes to the database and to the cache.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/Cache-Aside.png) Database queries are resource-heavy while databases scale only to a limited extent. That means that a highly loaded system benefits from bypassing its main database in as many queries as possible, which is usually achieved by storing recent queries and their results in an in-memory data store ([*Cache-Aside*](https://www.enjoyalgorithms.com/blog/cache-aside-caching-strategy)). Each incoming query is first looked for in the fast cache, and if it is found then you are lucky to get the result immediately without having to consult the main database. Keeping the cache consistent with the main database is the hard part. There are quite a few strategies (some of them treat the [[wiki/concepts/source/extension-metapatterns/proxy|cache as a *Proxy*]] for the database): [write-through](https://www.enjoyalgorithms.com/blog/write-through-caching-strategy), [write-behind](https://www.enjoyalgorithms.com/blog/write-behind-caching-pattern), [write-around](https://www.enjoyalgorithms.com/blog/write-around-caching-pattern) and [refresh-ahead](https://www.enjoyalgorithms.com/blog/refresh-ahead-caching-pattern). ### Memory Image, Materialized View ![At startup a service reads from an event store and writes to a memory image. At runtime it reads from the memory image and updates both the memory image and event store.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/Memory%20Image.png) [*Event sourcing*](https://martinfowler.com/eaaDev/EventSourcing.html) (of [[wiki/concepts/source/basic-metapatterns/pipeline|*Event-Driven Architecture*]] or [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]]) is all about changes. A service persists only *changes* to its data instead of its *current* data. As a result, the service needs to aggregate its history into a [*Memory Image*](https://martinfowler.com/bliki/MemoryImage.html) (*Materialized View* \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\]) by loading a snapshot and replaying any further events to rebuild its current state (which other architectural styles store in databases) to start operating. ### Reporting Database, CQRS View Database, Event-Sourced View, Source-Aligned (Native) Data Product Quantum (DPQ) of [[wiki/concepts/source/basic-metapatterns/pipeline|Data Mesh]] ![A service reads from a CQRS view which aggregates updates streamed by another service. An analyst queries a reporting database which aggregates a stream of events from the main database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/Reporting%20DB%20and%20CQRS%20View.png) It is common wisdom that a database is good for either *OLTP* (transactions) or *OLAP* (queries). Here we have two databases: one optimized for commands (write traffic protected with transactions) and another one for complex analytical queries. The databases differ at least in their schemas (the OLAP schema is optimized for queries) and often vary in type (e.g. SQL vs NoSQL). A [*Reporting Database*](https://martinfowler.com/bliki/ReportingDatabase.html) (or *Source-Aligned (Native) Data Product Quantum* of [[wiki/concepts/source/basic-metapatterns/pipeline|*Data Mesh*]] \[[wiki/concepts/source/appendices/books-referenced|[SAHP]]\]) derives its data from a write-enabled database in the same subsystem (service) while a *CQRS View* \[[wiki/concepts/source/appendices/books-referenced|[MP]]\] or *Event-Sourced View* \[[wiki/concepts/source/appendices/books-referenced|[DEDS]]\] is fed a stream of events from another service from which it filters the data relevant to its owner. This way a *CQRS View* lets its host service query (its replica of) the data that originally belonged to other services. ### Query Service, [[wiki/concepts/source/extension-metapatterns/orchestrator|Front Controller]], Data Warehouse, Data Lake, Aggregate Data Product Quantum (DPQ) of [[wiki/concepts/source/basic-metapatterns/pipeline|Data Mesh]] ![Several services both stream updates and query data from a shared query service.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/Query%20Service.png) A *Query Service* \[[wiki/concepts/source/appendices/books-referenced|[MP]]\] (or *Aggregate Data Product Quantum* of [[wiki/concepts/source/basic-metapatterns/pipeline|*Data Mesh*]] \[[wiki/concepts/source/appendices/books-referenced|[SAHP]]\]) subscribes to events from several full-featured services and aggregates them into its data store, making it a [*CQRS View*](#reporting-database-cqrs-view-database-event-sourced-view-source-aligned-native-data-product-quantum-dpq-of-data-mesh) of several services or even the whole system. If any other service or a data analyst needs to process data which belongs to multiple services, it retrieves it from the *Query Service* which has already joined the data streams and represents the join in a convenient way. A [[wiki/concepts/source/extension-metapatterns/orchestrator|*Front Controller*]] \[[wiki/concepts/source/appendices/books-referenced|[SAHP]] [[wiki/concepts/source/analytics/ambiguous-patterns|but not]] [[wiki/concepts/source/appendices/books-referenced|PEAA]]\] is a *Query Service* embedded in the first (user-facing) service of a [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]]. It collects status updates from the downstream components of the *Pipeline* to track the state of every request being processed by the *Pipeline*. *Data Warehouse* \[[wiki/concepts/source/appendices/books-referenced|[SAHP]]\] and *Data Lake* \[[wiki/concepts/source/appendices/books-referenced|[SAHP]]\] are *analytical* data stores that connect directly to and import all the data from the *operational* (main) databases of all the system’s services. A *Data Warehouse* transforms the imported data into its own unified schema while a *Data Lake* stores the imported data in its original format(s). ### External Search Index ![An external index receives events from the main datastore. A service queries the external index to find ids of specific records which it later reads from the main database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/Search%20Index.png) Some domains require a kind of search which is not naturally supported by ordinary database engines. Full text search, especially [NLP](https://en.wikipedia.org/wiki/Natural_language_processing)-enabled, is one such case. Geospatial data may be another. If you are comfortable with your main data store(s), you can set up an *External Search Index* by deploying a product dedicated to the special kind of search that you need and feeding it updates from your main data store. Alternatively, you may just need a way to quickly search through text documents or videos stored in a file system or in a cloud, which requires some kind of index. ### Historical Data, Data Archiving ![A service works with an operational database. An archiver reads from the operational database and writes to an archive. An analyst reads from both the archive and operational database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/Historical%20Data.png) It is common to store the history of sales in a database. However, once a month or two has passed, it is very unlikely that the historical records will ever be edited. And though they are queried on very rare occasions, like audits, they still slow down your database. Some businesses offload any data older than a couple of months to a cheaper [*archive storage*](https://www.datacore.com/glossary/what-is-data-archiving/) which does not allow for changing the data and has limited query capabilities. That helps keep the main datasets small and fast. ## Evolutions *Polyglot Persistence* with derived storage can often be made subject to *CQRS*: - The service that uses the read and write databases is [[wiki/concepts/source/fragmented-metapatterns/layered-services|split into separate read and write services]]. ![The backend layer that uses OLAP and OLTP databases is subdivided into command and query backends, resulting in full-featured Command-Query Responsibility Segregation.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/3/Polyglor%20Persistence%20-%201.png) ## Summary *Polyglot Persistence* employs several specialized data stores to improve performance, often at the cost of eventual data consistency or implementing transactions in the application. --- title: "Service Oriented Architecture (SOA)" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Fragmented metapatterns/Service-Oriented Architecture (SOA).md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Fragmented%20metapatterns/Service-Oriented%20Architecture%20%28SOA%29 source_license_note: "See namespace README; preserve attribution and source links." --- # Service Oriented Architecture (SOA) > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Fragmented metapatterns/Service-Oriented Architecture (SOA).md`. ![A diagram for Service-Oriented Architecture, in abstractness-subdomain-sharding coordinates.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Main/Service-Oriented%20Architecture.png) *The whole is equal to the sum of the parts.* Distributed [Object-Oriented Design](https://en.wikipedia.org/wiki/Object-oriented_design). Known as: Service-Oriented Architecture (SOA), [Segmented Architecture](https://github.com/wso2/reference-architecture/blob/master/reference-architecture-layered-segmented.md). Structure: Usually three layers of services where each service can access any other service in its own or lower layers. Type: System topology, derived from [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]]. | *Benefits* | *Drawbacks* | | --- | --- | | Supports huge codebases | Very hard to debug | | Multiple development teams and technologies | Hard to test as there are many dependencies | | Forces may vary between the components | Very poor latency | | Deployment to dedicated hardware | Very high DevOps complexity | | Fine-grained scaling | The teams are highly interdependent | References: \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] has a chapter on Orchestration-Driven (Enterprise) Service-Oriented Architecture. \[[wiki/concepts/source/appendices/books-referenced|[MP]]\] mentions Distributed Monolith. There is also much (though somewhat conflicting) content over the Web. *Service-Oriented Architecture* looks like the [application of modular or object-oriented design](https://www.uber.com/en-UA/blog/microservice-architecture/) followed by distribution of the resulting components over a network. The system usually contains three (rarely four) [[wiki/concepts/source/basic-metapatterns/layers|*layers*]] of [[wiki/concepts/source/basic-metapatterns/services|*services*]] where every service has access to all the services below it (and sometimes some within its own layer). The services stay small, but as their number grows it becomes hard for programmers to keep in mind all the API methods and contracts available for use by their component. Another issue originates from the idea of reusable components – multiple applications, written for different clients with varied workflows, require the same service to behave in (subtly) different ways, either causing its API to bloat or else impairing its usability (which means that a new customized duplicate service will likely be added to the system). On top of that, request processing is slow because there is much interservice communication. Teams are interdependent as any use case involves many services, each owned by a dedicated team. Testability is poor because there are too many moving (and being independently updated!) parts. The OOP’s foundational idea of reuse [failed in practice](https://softwareengineering.stackexchange.com/questions/7618/does-oop-fulfill-the-promise-of-code-reuse-what-alternatives-are-there-to-achie), but its child architecture, namely *SOA*, still survives in historical environments. Even though *SOA* fell from grace and is rarely seen in modern projects, it may soon be resurrected by low-code and no-code frameworks for serverless systems (e.g. [[wiki/concepts/source/basic-metapatterns/services|*Nanoservices*]]) – it has everything ready: code reuse, granular deployment, and elastic scaling. ### Performance *SOA* is remarkable for its poor latency which results from extensive communication between its distributed components. There is hardly any way to help that as processing a request usually involves many services from all the layers. Nevertheless, this pattern allows for good throughput as its stateless components can be scaled individually, leaving the system’s scalability to be limited only by its databases, [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] … and funding. ### Dependencies Each service of each layer depends on everything it uses. As a result, development of a low-level (utility) component may be paralyzed because too many services already use it, thus no changes are welcome. Hence, the team writes a new version of their utility as a new service, which defeats the very idea of component reuse which *SOA* was based on. ![Tasks depend on entities. Entities depend on utilities and libraries. The many dependencies make it hard to change almost any component.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Dependencies/Service-Oriented%20Architecture.png) ### Applicability *Service-Oriented Architecture* is useful in: - *Huge projects.* Many teams can be employed, each handling a moderate amount of code. Yet, dependencies between the teams and the combined length of the APIs in the system may [stall the development](https://goomics.net/374) anyway. - *A system of specialized hardware devices.* If there is a lot of different hardware interacting in complex ways, the system may naturally fit the description of *SOA*. Don’t fight this kind of [Conway’s law](https://en.wikipedia.org/wiki/Conway%27s_law). *Service-Oriented Architecture* hurts: - *Fast-paced projects.* Any feature requires coordination of multiple teams, which is hard to achieve in practice. - *Latency-sensitive domains*. Over-distribution means too much messaging which causes too high latency. - *High availability systems*. Components may fail. The failure of a lower-level component is going to stall a large part of the system because every low-level component is used by many higher-level services. - *Safety-critical systems with frequent updates*. *SOA* is hard to test comprehensively. Either all the components must be certified with a strict standard and an exhaustive test suite or any single component update requires re-testing the entire system. ### Relations *Service-Oriented Architecture*: - Is a stack of [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] each of which is divided into [[wiki/concepts/source/basic-metapatterns/services|*Services*]]. - Is often extended with an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Enterprise Service Bus*]] (a kind of [[wiki/concepts/source/extension-metapatterns/orchestrator|*orchestrating*]] [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]]) and one or more [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Databases*]]. ## Examples This architecture was hyped at the time when enterprises were expanding by acquiring smaller companies and conjoining their IT systems. The resulting merged systems were still heterogeneous and the development experience unpleasant, which inclined popular opinion towards the then novel notion of [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]]. As nearly everybody has turned from merging existing systems to failing to apply *Microservices* in practice, the chance to find a pure greenfield *SOA* project in the wild is quite low. Known examples of this topology include: - [*Distributed Monolith*](#distributed-monolith) with synchronous interactions. - [*Enterprise SOA*](#enterprise-soa) that features a notorious *Enterprise Service Bus*. - [*Domain-Oriented Microservice Architecture*](#domain-oriented-microservice-architecture-doma) built of *Cells*. - [*Automotive SOA*](#misapplied-automotive-soa) which may actually be a *Microkernel*. - *SOA*-topology [*Nanoservices*](#nanoservices). ### Distributed Monolith ![There are three segmented layers: tasks, services and infrastructure. Each component of a layer accesses multiple components in layers below it.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/Distributed%20Monolith.png) If a [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] gets too complex and resource-hungry, the most simple & stupid way out of the trouble is to deploy each of its component modules to a separate, dedicated hardware. The resulting services still communicate synchronously and are subject to domino effect on failure. Such an architecture may be seen as a (hopefully) intermediate [[wiki/concepts/source/introduction/system-topologies|topology]] in transition to more independent and stable event-driven [[wiki/concepts/source/basic-metapatterns/services|*Services*]] (or [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cells*]]). ### Enterprise SOA ![An Enterprise Service Bus interconnects multilayered segmented subsystems each using its own protocol.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/Enterprise%20SOA.png) Multiple systems of [[wiki/concepts/source/basic-metapatterns/services|*Services*]], each featuring an [[wiki/concepts/source/extension-metapatterns/orchestrator|*API Gateway*]] and sometimes a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Database*]], are integrated, resulting in new cross-connections. Much of the orchestration logic is removed from the *API Gateways* and reimplemented in an [[wiki/concepts/source/extension-metapatterns/orchestrator|*orchestrating*]] [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] called [[wiki/concepts/source/extension-metapatterns/orchestrator|*Enterprise Service Bus*]] (*ESB*). This option allows for fast and only moderately intrusive integration (as no changes to the services, which implement the mass of the domain logic, are required), but the single [[wiki/concepts/source/foundations-of-software-architecture/orchestration|orchestrating]] component (*ESB*) often becomes the bottleneck for future development of the system due to its size and complexity. It is likely that if the orchestration were encapsulated in the individual *API Gateways*, the system would be easier to deal with (making what is now [marketed by WSO2](https://github.com/wso2/reference-architecture/blob/master/reference-architecture-cell-based.md) as [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Cell-Based Architecture*]]). The layers of *SOA* are: - *Business Process* (*Task*) – the definitions of [[wiki/concepts/source/basic-metapatterns/layers|use cases]] for a single business department, similar to the *API Gateways* layer of [*BFF*](). - *Services* (*Enterprise*, *Entity*) – the implementation of the [[wiki/concepts/source/basic-metapatterns/layers|business logic of a subdomain]], to be used by the *tasks*. - *Components* (*Application* & *Infrastructure*, *Utility*) – external libraries and in-house utilities that are designed for shared use by the *services* layer. ### Domain-Oriented Microservice Architecture (DOMA) ![There are five layers: segmented gateways, segmented presentation, segmented product made of cells, segmented business made of cells, and monolithic infrastructure.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/DOMA.png) A huge business may build a *SOA* of [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cells*]] (called *Domains*) instead of plain [[wiki/concepts/source/basic-metapatterns/services|*Services*]]. That greatly simplifies: - administration (by reducing the number of components at the system level – Uber [packed](https://www.uber.com/blog/microservice-architecture/) 2200 [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]] into 70 *Domains*), - refactoring of individual subsystems (which are isolated behind [[wiki/concepts/source/extension-metapatterns/proxy|*Cell Gateways*]]), - development of business logic (as programmers need to learn much fewer interfaces of components they rely on). Uber’s *DOMA* also [makes heavy use](https://www.uber.com/blog/microservice-architecture/) of [[wiki/concepts/source/implementation-metapatterns/plugins|*Ambassador Plugins*]] which programmers from a client service team develop for the services which they rely on. That allows for cross-*Domain* customization (injection of business logic from another *Cell*) of a service’s behavior without making slow and possibly failing interservice calls. ### (misapplied) [[wiki/concepts/source/implementation-metapatterns/microkernel|Automotive SOA]] ![User applications run on top of a system-wide Virtual Functional Bus which communicates to various services that run on different chips in the system.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/3/SOA%20-%20AUTOSAR.png) *Automotive* architectures were inspired by *SOA*, but the old [[wiki/concepts/source/implementation-metapatterns/microkernel|*AUTOSAR Classic*]] looks more like [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]] (which indeed is similar to a 2-layered *SOA* with an [[wiki/concepts/source/extension-metapatterns/orchestrator|*ESB*]]) while the newer system diagrams resemble a [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchy*]]. Therefore, they are addressed in the corresponding chapters. ### Nanoservices It seems that some proponents of [[wiki/concepts/source/basic-metapatterns/services|*Nanoservices*]] [take them](https://medium.com/@ido.vapner/unlocking-the-power-of-nano-services-a-new-era-in-microservices-architecture-22647ea36f22) for a novel version of *SOA* – with the old good promise of reusable components. However, as that promise was failing miserably ever since the ancient days of OOP, it is of no surprise that [in practice](https://increment.com/software-architecture/the-rise-of-nanoservices/) *Nanoservices* are used instead to build [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipelines*]] with little to no reuse. ## Evolutions *SOA* suffers from excessive reuse and fragmentation. To fix that, first and foremost, each service of the *components* ([[wiki/concepts/source/basic-metapatterns/layers|*utility*]]) layer should be duplicated: - Into every *service* that uses it, giving the developers who write the business logic full control over all the code which they use. Now they have several projects to support on their own (instead of asking other teams to make changes to their components). ![The shared components are replicated into services which use them.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/3/SOA%20-%201.png) - Or into [[wiki/concepts/source/extension-metapatterns/proxy|*Sidecars*]] if you employ a [[wiki/concepts/source/implementation-metapatterns/mesh|*Service Mesh*]], resulting in much fewer network hops (thus lower latency) in request processing, but retaining the inter-team dependencies. ![The shared components are replicated into sidecars.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/3/SOA%20-%202.png) That removes the largest and most obvious part of the fragmentation, making the [[wiki/concepts/source/extension-metapatterns/orchestrator|*ESB*]] (if you use one) [[wiki/concepts/source/foundations-of-software-architecture/orchestration|*orchestrate*]] only the *entity* layer. Afterwards you may deal with the remaining orchestration. The idea is to move the orchestration logic from the *ESB* to an explicit *layer* of [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrators*]]: - Either a monolithic *Orchestrator* over all the services. - Or [*Backends for Frontends*]() with an *Orchestrator* per client type (department of an enterprise) if each client uses most of the services. - Or go for [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cells*]] with an *Orchestrator* per subdomain if your clients are subdomain-bound. - Or a combination of the above. ![Diagrams for Services with an orchestrator, Backends for Frontends, and Cell-Based Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Evolutions/3/SOA%20-%203.png) Still another step is unbundling the [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]], which supports multiple protocols via [[wiki/concepts/source/extension-metapatterns/proxy|*Adapters*]]: - If you have a [[wiki/concepts/source/implementation-metapatterns/mesh|*Service Mesh*]], the *Adapters* may be added to [[wiki/concepts/source/extension-metapatterns/proxy|*Sidecars*]]. - Otherwise there is an option of a [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*hierarchical Middleware*]] (*Bus of Buses*) if closely interacting components share protocols. Still, these evolutions of [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] may not bring any real benefit except for removing the [[wiki/concepts/source/extension-metapatterns/orchestrator|*ESB*]] altogether, which may not be that bad after all when it is not misused. In any case, many of the evolutions will likely be very expensive, thus it makes sense to conduct some of them gradually via a kind of [strangler fig approach](https://martinfowler.com/bliki/StranglerFigApplication.html). Or let the architecture live and die as it is. ## Summary *Service-Oriented Architecture* divides each of the *integration*, *domain*, and *utility* layers into shared services. The extensive fragmentation and reuse degrade performance and speed of development. Nevertheless, huge projects are known to survive with this architecture. --- title: "Hexagonal Architecture" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Implementation metapatterns/Hexagonal Architecture.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Implementation%20metapatterns/Hexagonal%20Architecture source_license_note: "See namespace README; preserve attribution and source links." --- # Hexagonal Architecture > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Implementation metapatterns/Hexagonal Architecture.md`. ![A diagram for Hexagonal Architecture, in abstractness-subdomain-sharding coordinates.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Main/Hexagonal%20Architecture.png) *Trust no one.* Protect your code from external dependencies. Known as: Hexagonal Architecture, or originally as Ports and Adapters. Structure: Monolithic or subdivided business logic extended with a set of (adapter, component) pairs that encapsulate external dependencies. Type: Implementation. | *Benefits* | *Drawbacks* | | --- | --- | | Isolates business logic from external dependencies which thus become expendable | Suboptimal performance | | The programmers of business logic don’t need to learn any external technologies | The vendor-independent interfaces must be designed before the start of development | | Facilitates the use of stubs/mocks for testing and development | | | Allows for qualities to vary between the external components and the business logic | | References: [Herberto Graça’s chronicles](https://herbertograca.com/2017/07/03/the-software-architecture-chronicles/) is the main overview of patterns in this chapter. For *Hexagonal Architecture* there is [the original article](https://alistair.cockburn.us/hexagonal-architecture/) and a brief summary of its layered variant in \[[wiki/concepts/source/appendices/books-referenced|[LDDD]]\]. Most of the *Separated Presentation* patterns are featured on Wikipedia and there are collections of them from [Martin Fowler](https://martinfowler.com/eaaDev/uiArchs.html), [Anthony Ferrara](https://blog.ircmaxell.com/2014/11/alternatives-to-mvc.html), and [Derek Greer](https://lostechies.com/derekgreer/2007/08/25/interactive-application-architecture/). *Cells* originate with [WSO2](https://github.com/wso2/reference-architecture/blob/master/reference-architecture-cell-based.md) and [Uber](https://www.uber.com/en-UA/blog/microservice-architecture/). *Hexagonal Architecture* (see the [original article](https://alistair.cockburn.us/hexagonal-architecture/) for the explanation of its name) is a variation of [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]] that aims for self-sufficiency of a system’s business logic *core*. It hides external components such as libraries, services, or data stores, which the business logic would normally depend upon, behind [[wiki/concepts/source/extension-metapatterns/proxy|*adapters*]] \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] that translate from the external component’s interface to an interface ([*API*](#upper-half-separated-presentation-open-host-service) or [*SPI*](#lower-half-pedestal-abstraction-layer-anticorruption-layer)) defined in terms of the business logic. This indirection not only makes the bulk of the system’s code easy to port, test, and run in isolation, but also allows for replacing external components and for changing the interfaces or even the internal structure of the business logic late in the [[wiki/concepts/source/analytics/architecture-and-product-life-cycle|project’s life cycle]]. Though various kinds of *Hexagonal Architecture* are de facto industry standards, they are not without drawbacks: the [extra layer of indirection](https://en.wikipedia.org/wiki/Fundamental_theorem_of_software_engineering) requires careful planning with insights into how the system will evolve and which kinds of external components will need to be integrated or which platforms the system will need to run on. It also adds boilerplate code and prevents aggressive performance optimization that would rely on peculiarities of the currently integrated external component. ### Performance *Hexagonal Architecture* is a strange beast performance-wise. The generic interfaces between the *core* and *adapters* stand in the way of whole-system optimization and may add a context switch. Still, at the same time, each *adapter* concentrates all the vendor-specific code for its external dependency, which makes the *adapter* a perfect single place for aggressive optimization by an expert or consultant who is proficient with the adapted third-party software but does not have time to learn the details of your business logic. Thus, some opportunities for optimization are lost while others emerge. Occasionally, the system may benefit from direct communication between the *adapters*. However, that requires several of them to be compatible or polymorphic, in which case your *Hexagonal Architecture* may in fact be a kind of a shallow [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchy*]]. Examples include a service which uses several databases which are kept in sync through [*Change Data Capture*](https://www.dremio.com/wiki/change-data-capture/) (*CDC*) or a telephony gateway that interconnects various kinds of voice devices. ![A data stream between adapters of Hexagonal Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Performance/Hexagonal%20Architecture.png) ### Dependencies Each [[wiki/concepts/source/extension-metapatterns/proxy|*adapter*]] breaks the dependency between the *core* that contains business logic and an adapted component. This makes all the system’s components mutually independent – and easily interchangeable and evolvable – except for the *adapters* themselves, which are small enough to be rewritten as need arises. ![In Hexagonal Architecture each adapter depends on the core and the component or protocol it adapts.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Dependencies/Hexagonal%20Architecture.png) Still, there is a hidden pitfall of designing a [*leaky abstraction*](https://en.wikipedia.org/wiki/Leaky_abstraction) – an interface that looks generic but whose contract matches that of the component it encapsulates, making it much harder than expected to change the external component’s vendor. ### Applicability *Hexagonal Architecture* benefits: - *Medium-sized or larger components.* The programmers don’t need to learn details of external technologies and may concentrate on the business logic instead. The code of the *core* becomes smaller as all the details of managing the external components are moved into their *adapters*. - *Cross-platform development.* The core is naturally cross-platform as it does not depend on any (platform-specific) libraries or frameworks. - *Long-lived products*. Technologies come and go, your product remains. Always be ready to change the technologies it uses. - *Unfamiliar domain.* You don’t know how much load you’ll need your database to support. You don’t know if the library which you have selected is stable enough for your needs. Be prepared to replace vendors even after the public release of your product. - *Automated testing.* [Stubs and mocks](https://stackoverflow.com/questions/3459287/whats-the-difference-between-a-mock-stub) are great for reducing load on test servers. And stubs for the [SPI](#lower-half-pedestal-abstraction-layer-anticorruption-layer)s which you wrote yourself are as easy as a pie. - *Zero bug tolerance.* SPIs allow for event replay. If your business logic is deterministic, you can [reproduce your user’s bugs in your office](http://ithare.com/chapter-vc-modular-architecture-client-side-on-debugging-distributed-systems-deterministic-logic-and-finite-state-machines/). *Hexagonal Architecture* is not good for: - *Small components.* If there is little business logic, there is not much to protect, while the overhead of defining [SPI](#lower-half-pedestal-abstraction-layer-anticorruption-layer)s and writing *adapters* for them is high compared to the total development time. - *Write-and-forget projects.* You don’t want to waste your time on long-term survivability of your code. - *Quick start*. You need to show the results right now. No time for good architecture. - *Low latency*. The *adapters* slow down communication. This is somewhat alleviated by creating [direct communication channels](#performance) between the *adapters* to bypass the *core*. ### Relations ![Diagrams of Hexagonal Architecture with a monolithic core, with a layered core, and Cell.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Relations/Hexagonal%20Architecture.png) *Hexagonal Architecture*: - Is a kind of [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]]. - May be a shallow [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchy*]]. - Implements [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] or [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]]. - Extends [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]], [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]], or [[wiki/concepts/source/basic-metapatterns/services|*Services*]] with one or two layers of [[wiki/concepts/source/extension-metapatterns/proxy|*Adapters*]]. - The [*MVC* family of patterns](#model-view-controller-mvc-action-domain-responder-adr-resource-method-representation-rmr-model-2-mvc2-game-development-engine) is also [[wiki/concepts/source/analytics/pipelines-in-architectural-patterns|derived]] from [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]]. ## Variants by placement of adapters There is a variation in a distributed or asynchronous *Hexagonal Architecture* regarding the deployment of [[wiki/concepts/source/extension-metapatterns/proxy|*adapters*]], which may reside with the components they adapt or stay adjacent to the *core*: ### Adapters on the external component side ![Adapters co-located with external components translate a single message from the core into multiple calls to the adapted components.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Hexagonal%20-%20Adapters%20with%20Components.png) If your team owns the component adapted, the *adapter* may be placed next to it. That usually makes sense because a single domain message (designed in the terms of your business logic) tends to unroll into a series of calls to an external component. The fewer messages you send, the faster your system is. ### Adapters on the core side ![Adapters for external services and a shared database are co-located with the core.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Hexagonal%20-%20Adapters%20with%20the%20Core.png) Sometimes you need to adapt an external service which you don’t control. In that case the only real option is to place its *adapter* together with your *core* logic. For a monolithic *core* ([*Ports and Adapters*](#ports-and-adapters-hexagonal-architecture)) the *adapters* may run in the core’s process as ordinary modules. A distributed *core* ([*Cell*](#cell-cluster-domain)) requires the *adapters* to be stand-alone components, which however can be co-located and co-deployed with the *core* as [[wiki/concepts/source/extension-metapatterns/proxy|*Sidecars*]]. ## Variants by encapsulation In most cases a component or subsystem is involved in two kinds of communication: - Input/output from clients, users, or network devices which we [[wiki/concepts/source/introduction/metapatterns|draw on the upper side of diagrams]]. These events initiate the application’s use cases thus they are called [*primary* or *driving*](https://alistair.cockburn.us/hexagonal-architecture). - The activities and calls initiated by the component itself while executing a use case. They usually target the infrastructure or external services, are called [*secondary* or *driven*](https://alistair.cockburn.us/hexagonal-architecture), and are drawn on the lower side. ![The system's core is isolated with adapters for the following communication: data stream, REST, and SSH inputs; database and library access; publish/subscribe output stream.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Hexagonal%20-%20Driving%20and%20Driven.png) > Client output *adapters*, such as the *view* in [*MVC*](#model-view-controller-mvc-action-domain-responder-adr-resource-method-representation-rmr-model-2-mvc2-game-development-engine) or *responder* in [*ADR*](#model-view-controller-mvc-action-domain-responder-adr-resource-method-representation-rmr-model-2-mvc2-game-development-engine), are treated as *primary* even though they are called by the business logic. The reason is that such an *adapter* is a part of the client feedback loop which *drives* the system’s behavior. We can protect a component from its environment by inserting *adapters* into its *primary*, *secondary*, or both communication pathways: ### Upper half: [[wiki/concepts/source/extension-metapatterns/proxy|Separated Presentation]], [[wiki/concepts/source/extension-metapatterns/proxy|Open Host Service]] ![A component provides an API which is called by a GUI adapter for a system's user, a REST adapter for a web application, and a gRPC adapter for a software client.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Hexagonal%20-%20Driving.png) Isolation on the input ([*primary*, *driving*](https://alistair.cockburn.us/hexagonal-architecture)) side is achieved by designing an *Application Programming Interface* (*API*) that provides access to your component’s use cases which your software exists to implement and writing *adapters* that translate your *API* into something convenient for every kind of communication used by your clients. For example, you may need a desktop GUI, mobile GUI, CLI, web application (REST), and customer (JSON or gRPC) *adapters* all of which are built on top of your component’s API. Input side isolation allows for your system to be used in different environments and roles and enables automated testing of its business logic without hard-to-support GUI tests. > Though it may seem that a system’s business logic cannot depend on its [[wiki/concepts/source/basic-metapatterns/layers|*interface*]] (GUI or web API) because it is the *interface* that calls the business logic, in fact the business logic must prepare the data it returns in the format declared by the interface (for a web protocol) or output the data queried to a widget (for a GUI). Therefore, making the business logic independent requires an [[wiki/concepts/source/extension-metapatterns/proxy|*Adapter*]] to translate between the domain terms native to the business logic and the data format used by a web protocol or GUI framework which calls the business logic. [*Separated Presentation*](https://martinfowler.com/eaaDev/SeparatedPresentation.html) protects business logic (*model*) from a dependency on the [[wiki/concepts/source/extension-metapatterns/proxy|*Presentation Layer*]] \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\] (interactions with the system’s user via a window, command line, or web page). There is a [great variety](https://blog.ircmaxell.com/2014/11/alternatives-to-mvc.html) of such patterns, commonly known as *Model-View-Controller* (*MVC*) alternatives, which make three structurally distinct groups: - Bidirectional flow – the *view* (user-facing component) both receives input and produces output, and there is often an extra *adapter* between it and the main system, resulting in [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]]. These patterns make the [*Model-View-Presenter* (*MVP*) family](#model-view-presenter-mvp-model-view-adapter-mva-model-view-viewmodel-mvvm-model-1-mvc1-document-view). - Unidirectional flow – the *controller* receives input while the *view* only produces output, [[wiki/concepts/source/analytics/pipelines-in-architectural-patterns|forming]] a kind of [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]]. Such patterns are [associated with *Model-View-Controller* (*MVC*)](#model-view-controller-mvc-action-domain-responder-adr-resource-method-representation-rmr-model-2-mvc2-game-development-engine). - Hierarchical patterns with multiple *models*, [[wiki/concepts/source/fragmented-metapatterns/hierarchy|discussed]] in the [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchy*]] chapter. *Open Host Service* \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\] is an enterprise pattern about providing functionality for use by other components (hence the name) through publishing a stable interface, known as a *Published Language* \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]. As the subject service should be able to change, and as its clients may differ in their needs, requiring multiple *Published Languages*, an *Open Host Service* usually involves an *adapter* for every *Published Language* it provides – just like *Separated Presentation* does for supported *UI*s. > As is common with patterns, there is a slight difference between what the client-facing side comprises in *Separated Presentation* and *Open Host Service*, with the last pattern including adapters for *Publish-Subscribe* interfaces. Neither of these two patterns provides indirection for input streams of data or events. ### Lower half: [[wiki/concepts/source/basic-metapatterns/services|Pedestal]], [[wiki/concepts/source/extension-metapatterns/proxy|Abstraction Layer, Anticorruption Layer]] ![The core uses adapters to call a database, a library, and an external service.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Hexagonal%20-%20Driven.png) In many cases you need to protect your component from its dependencies. If the bulk of your code depends on a certain external service provider or component, you face a major risk, called [*vendor lock-in*](https://en.wikipedia.org/wiki/Vendor_lock-in), of that provider going out of business, being [acquired and killed by a larger company](https://killedbygoogle.com/), or greatly increasing the price of its services. Therefore you better define a *Service Provider Interface* (*SPI*) in terms of your business logic and write an *adapter* between it and the external provider’s *API*. This way you will be able to change the external provider by merely rewriting the *adapter*. It will also be easy to upgrade to new versions of the provider’s API. The set of *adapters* to the services used by a component is called *Anticorruption Layer* \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\] because it protects the component from any external changes. There is a similar situation in embedded and systems programming where software faces hardware. A hardware component is normally in production only for a few years before being replaced by a newer, and often incompatible, chip. You have to make sure that your business logic can run on any hardware that provides the functions it needs – therefore you write hardware-specific [[wiki/concepts/source/extension-metapatterns/proxy|*drivers*]] which adapt the target hardware’s interface to the expectations of your business logic. The *drivers* make an [[wiki/concepts/source/extension-metapatterns/proxy|*Hardware Abstraction Layer*]] while the entire architecture is called a [*Pedestal*](#pedestal). Another common case is platform independence – you may need to run your code under different operating systems or cloud orchestrators, which requires an [[wiki/concepts/source/extension-metapatterns/proxy|*OS Abstraction Layer*]] (*OSAL*) or a [[wiki/concepts/source/extension-metapatterns/proxy|*Platform Abstraction Layer*]] (*PAL*). And you may even use a [[wiki/concepts/source/extension-metapatterns/proxy|*Database Abstraction Layer*]] (*DAL*) to feel more secure. > Embedded software and operating systems [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|don’t run use cases]] – they react to events which come from hardware. This fact breaks the *Hexagonal Architecture*’s model of [*driving* and *driven* adapters](#variants-by-encapsulation) – any hardware component can *drive* a part of the system’s behavior but none of them initiates use cases. The extra benefit of the isolation from infrastructure and external services by using [*secondary* or *driven*](https://alistair.cockburn.us/hexagonal-architecture) *adapters* is the ability to run against [*test doubles*](https://martinfowler.com/bliki/TestDouble.html) – local [*stubs* or *mocks*](https://stackoverflow.com/questions/3459287/whats-the-difference-between-a-mock-stub) that replace an external dependency, saving testing time and money. They also allow for implementing business logic before the external services which it will use are chosen, and even replacing a service provider late in the [[wiki/concepts/source/analytics/architecture-and-product-life-cycle|project’s life cycle]]. > *Stubs* and *mocks* are [*test doubles*](https://martinfowler.com/bliki/TestDouble.html) – simplistic replacements for real-world components. They are used to run the business logic in isolation – without the need to deploy any heavyweight libraries or services which the logic may depend upon. A *stub* supports a single usage scenario in a single test case while a *mock* is more generic – its behavior is programmed on a per test basis. ### Full encapsulation: [Ports and Adapters](#ports-and-adapters-hexagonal-architecture) ![A system with its core fully isolated by adapters from both inputs and outputs. The core depends only on its own interfaces.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Hexagonal%20-%20Full%20Isolation.png) Complete isolation of business logic – when both *driving* and *driven* communication is mediated by *adapters* – not only reaps the benefits of both approaches described above but also simplifies the main codebase because the entire business logic is written in its own terms, called *ubiquitous language* \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]. It also lowers the cognitive complexity which its programmers face because they don’t need to learn any external technology, and [allows for event replay to reproduce bugs](http://ithare.com/chapter-vc-modular-architecture-client-side-on-debugging-distributed-systems-deterministic-logic-and-finite-state-machines/) (*any reproducible bug is a dead bug*) if the business logic is deterministic. The pattern for full isolation of business logic is known as [*Ports and Adapters*](https://herbertograca.com/2017/09/14/ports-adapters-architecture/) which is the original [*Hexagonal Architecture*](https://alistair.cockburn.us/hexagonal-architecture). ## Examples Several patterns apply the principles of *Hexagonal Architecture* to different extents: - The patterns of the [*MVP family*](#model-view-presenter-mvp-model-view-adapter-mva-model-view-viewmodel-mvvm-model-1-mvc1-document-view) add one or two layers of indirection between a GUI or web framework and the business logic. - Those of the [*MVC family*](#model-view-controller-mvc-action-domain-responder-adr-resource-method-representation-rmr-model-2-mvc2-game-development-engine) use separate *adapters* for input and output flows. - [*Pedestal*](#pedestal) wraps each hardware component with a *driver*. - [*Ports and Adapters*](#ports-and-adapters-hexagonal-architecture) fully isolate the system’s business logic from any dependencies. - There are a few [related architectures with layered *cores*](#ddd-style-hexagonal-architecture-onion-architecture-clean-architecture). - [*Cell*](#cell-cluster-domain) isolates a group of services from the rest of the system. ### Model-View-Presenter (MVP), Model-View-Adapter (MVA), Model-View-ViewModel (MVVM), Model 1 (MVC1), Document-View ![The control flow of Model-View-Presenter is a loop that starts with an OS GUI, is handled by the view, passes to the presenter, then down to the model, and all the way back to the OS.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/MVP.png) *MVP*-style patterns pass user input and output through one or more [[wiki/concepts/source/extension-metapatterns/proxy|*Presentation Layers*]]. Each pattern includes: - *View* – the interface exposed to users. In *Hexagonal Architecture*’s terms it is an *adapter* for the OS GUI framework. - An optional intermediate layer that translates between the *view* and *model*, beneficial for when the internal representation of the domain in the *model* diverges from the way it is presented to users by the *view*. It is this component which differentiates the patterns, both in name and function. - *Model* – the whole system’s business logic and infrastructure, now independent from the method of presentation (CLI, UI, or web). *Document-View* \[[wiki/concepts/source/appendices/books-referenced|[POSA1]]\] and [*Model 1*](https://stackoverflow.com/questions/796508/what-is-the-actual-difference-between-mvc-and-mvc-model2) (*MVC1*) skip the intermediate layer and connect the *view* directly to the *model* (*document*). These are the simplest [*Separated Presentation*](https://martinfowler.com/eaaDev/SeparatedPresentation.html) patterns for UI and web applications, respectively. In a [*Model-View-Presenter*](https://herbertograca.com/2017/08/17/mvc-and-its-variants/#model-view-presenter) (*MVP*), the *presenter* ([*Supervising Controller*](https://martinfowler.com/eaaDev/SupervisingPresenter.html)) receives input from the *view*, interprets it as a call to one of the *model*’s methods, retrieves the call’s results, and shows them in the *view*, which is often completely dumb ([*Passive View*](https://martinfowler.com/eaaDev/PassiveScreen.html)). A complex system may feature multiple *view-presenter* pairs, one per UI screen. A [*Model-View-Adapter*](https://blog.ircmaxell.com/2014/11/alternatives-to-mvc.html#MVA-Model-View-Adapter) (*MVA*) is quite similar to *MVP*, but it chooses the *adapter* on a per session basis while reusing the *view*. For example, an unauthorized user, a normal user, and an admin would access the *model* through different *adapters* which would show them only the data and actions available under their permissions. A [*Model-View-ViewModel*](https://herbertograca.com/2017/08/17/mvc-and-its-variants/#model-view-view_model) (*MVVM*) uses a stateful intermediary (*ViewModel* or [*Presentation Model*](https://martinfowler.com/eaaDev/PresentationModel.html)) which resembles a [[wiki/concepts/source/extension-metapatterns/proxy|*Response Cache*]], [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Materialized View*]], [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Reporting Database*]], or the *Read Model* of [[wiki/concepts/source/fragmented-metapatterns/layered-services|*CQRS*]] – it stores all the data shown in the *view* in a form which is convenient for the *view* to [bind to](https://en.wikipedia.org/wiki/Data_binding). Changes in the *view* are propagated to the *ViewModel* which translates them into requests to the underlying application (the true *model*). Changes in the *model* (both independent and resulting from user actions) are propagated to the *ViewModel* and, eventually, to the *view*. All those patterns exploit modern OS or GUI frameworks’ widgets which handle and process mouse and keyboard input, thus [removing](https://mvc.givan.se/papers/Twisting_the_Triad.pdf) the need for a separate (input) *controller* (see [below](#model-view-controller-mvc-action-domain-responder-adr-resource-method-representation-rmr-model-2-mvc2-game-development-engine)). ![Diagrams of MVP with a view-presenter pair for each screen, MVA with different adapters for different kinds of users, MVVM with data in ViewModel, and simple Document-View and Model 1.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/MVP%20-%20subtypes.png) ### Model-View-Controller (MVC), Action-Domain-Responder (ADR), Resource-Method-Representation (RMR), Model 2 (MVC2), Game Development Engine ![The control flow in Model-View-Controller starts with mouse events handled by the controller which calls the model which calls the view which updates the display.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/MVC.png) When your presentation’s input and output are of different natures (raw mouse movement vs 3D graphics in UI, HTTP requests vs HTML pages in websites), it makes sense to separate the [[wiki/concepts/source/extension-metapatterns/proxy|*Presentation Layer*]] into dedicated components for input and output. *Model-View-Controller* (*MVC*) \[[wiki/concepts/source/appendices/books-referenced|[POSA1]], [[wiki/concepts/source/appendices/books-referenced|POSA4]]\] allows for cross-platform development of hand-crafted UI applications (which was necessary before universal UI frameworks emerged) by abstracting the system’s *model* (its main logic and data, the *core* of *Hexagonal Architecture*) from its [[wiki/concepts/source/basic-metapatterns/layers|user interface]] comprised of platform-specific *controller* (input) and *view* (output): - The *controller* translates raw input into calls to the business-centric model’s API. It may also hide or lock widgets in the *view* when required by the input or if the *model*’s state changes. - The *model* is the bulk of UI-agnostic code which executes the *controller*’s requests and notifies the *view* and, optionally, *controller* when its data changes. - Upon receiving a notification, the *view* reads, transforms, and presents to the user the subset of the *model*’s data which it covers. Each widget on the screen [may have](https://martinfowler.com/eaaDev/uiArchs.html#ModelViewController) its own *model-view* pair. The absence of an intermediate layer between the *view* and *model* makes the *view* heavyweight as it has to translate the *model*'s data format into something presentable to users – the flaw addressed by the 3-layered [*MVP* patterns discussed above](#model-view-presenter-mvp-model-view-adapter-mva-model-view-viewmodel-mvvm-model-1-mvc1-document-view). Both [*Action-Domain-Responder*](https://github.com/pmjones/adr#action-domain-responder) (*ADR*) and [*Resource-Method-Representation*](https://herbertograca.com/2018/08/31/resource-method-representation/) (*RMR*) are web layer patterns. An *action* (*method*) receives a request, calls into a *domain* (*resource*) to make changes and retrieve data, and brings the results to a *responder* (*representation*) which prepares the return message or web page. *ADR* is technology-agnostic while *RMR* is HTTP-centric. [*Model 2*](https://stackoverflow.com/questions/796508/what-is-the-actual-difference-between-mvc-and-mvc-model2) (*MVC2*) is a similar pattern from the Java world with integration logic [implemented](https://github.com/pmjones/adr/blob/master/MVC-MODEL-2.md) in the *controller*. A [*game development engine*](https://slideplayer.com/slide/12426213/) creates a higher-level abstraction over input from mouse / keyboard / joystick and output to sound card and GPU while more powerful engines may also [model physics](https://discussions.unity.com/t/unity3d-architecture/565787) and character interactions. This role is quite similar to what the original *MVC* did, with a couple of differences: - Games often have to deal with the low-level and very chatty interfaces of hardware components, thus the input and output are at the bottom side of the system diagram. - The framework itself makes a cohesive layer, becoming a kind of [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]]. Another difference is that while *MVC* provides for changing target platforms by rewriting its minor components (the *view* and *controller*), you are very unlikely to change your game framework – instead, it is the framework itself that makes all the platforms look identical to your code. ![Diagrams of MVC with a dedicated view-controller pair for each widget, ADR and RMR where the action calls the responder, Model 2 with an orchestrating controller, and a game development engine.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/MVC%20-%20subtypes.png) ### [[wiki/concepts/source/basic-metapatterns/services|Pedestal]] ![A Pedestal has a control layer mediating hardware drivers which wrap each hardware component. An operating system adds a kernel between the drivers and an application that uses them.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Pedestal.png) [*Pedestal*](https://alistair.cockburn.us/hexagonal-architecture) is named after the shape of its diagram and describes a system that operates multiple hardware components. On one hand, *software* takes years to write and stabilize. On the other hand, there are many versions and even vendors for each kind of *hardware* component, and they change over years. Therefore, the bulk of the *software* that contains the system logic (called [[wiki/concepts/source/extension-metapatterns/orchestrator|*control*]]) is decoupled from the *hardware* which it manages through the use of *hardware*-specific [[wiki/concepts/source/extension-metapatterns/proxy|*drivers*]]. As a result, changes in *hardware* require updates only to the matching *drivers*, the business logic remaining intact. A similar pattern is in use with [[wiki/concepts/source/implementation-metapatterns/microkernel|*OS kernels*]], adjusted for the fact that the business logic now resides in user applications, the *kernel* acting as an [extra layer of indirection](https://en.wikipedia.org/wiki/Fundamental_theorem_of_software_engineering). ### [Ports and Adapters](#full-encapsulation-ports-and-adapters), Hexagonal Architecture ![Adapters of the Hexagonal Architecture translate between the interfaces of its core and those of the adapted external components.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Monolithic%20Hexagonal.png) [*Ports and Adapters*](https://alistair.cockburn.us/hexagonal-architecture/) – the original *Hexagonal Architecture* – provides full isolation for its business logic (*core*) by injecting *adapters* in all its [communication pathways](#variants-by-encapsulation). As a result, the *core* depends only on the interfaces, called *ports* (hence the name of the pattern), which it defines, with the benefits discussed [earlier in this chapter](#full-encapsulation-ports-and-adapters). Just like [*MVC*](#model-view-controller-mvc-action-domain-responder-adr-resource-method-representation-rmr-model-2-mvc2-game-development-engine) it is based on, *Ports and Adapters* does not care about the contents or structure of its *core* – it is all about isolating the *core* from its environment. The *core* may have [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] or [[wiki/concepts/source/basic-metapatterns/services|*Modules*]], or even [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]] inside, but this pattern has nothing to say about them. ### DDD-Style Hexagonal Architecture, Onion Architecture, Clean Architecture ![Control flows for changing an entity in puristic Domain-Driven Design, pragmatic Domain-Driven Design, and Onion Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Layered%20Hexagonal.png) As *Hexagonal Architecture* built upon the [*Domain-Driven Design*](https://en.wikipedia.org/wiki/Domain-driven_design)’s (*DDD*) idea of isolating business logic with [[wiki/concepts/source/extension-metapatterns/proxy|*Adapters*]] (see the [[wiki/concepts/source/extension-metapatterns/proxy|*Anticorruption Layer* and *Open Host Service*]] patterns), it was quickly integrated back into DDD \[[wiki/concepts/source/appendices/books-referenced|[LDDD]]\]. However, as *Ports and Adapters* appeared later than the original DDD book, namely \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\], there is no universal agreement on how this architecture should work: - The cleanest way is for the [[wiki/concepts/source/basic-metapatterns/layers|*domain*]] layer to have nothing to do with the database – with this approach the [[wiki/concepts/source/basic-metapatterns/layers|*application*]] asks the [[wiki/concepts/source/extension-metapatterns/proxy|*repository*]] (the database *adapter*) to create *aggregates* (domain objects), then executes its business actions on the aggregates, and tells the repository to save the changed aggregates back to the database. - Others say that in practice the logic inside an aggregate may have to read additional information from the database or even depend on the results of persisting parts of the aggregate. Thus it is the aggregate, not the *application*, which should save its changes, and the logic of accessing the database leaks into the domain layer. - [*Onion Architecture*](https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/) (see the [original article](https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/) for the meaning of the name) – one of early developments of *Hexagonal Architecture* and DDD – always splits the domain layer into a *domain model* and *domain services*. The *domain model* layer contains classes with business data and business logic, which are loaded and saved by the mostly stateless *domain services* layer just above it. And the upper *application services* layer drives use cases by calling into both domain services and the *domain model*. - There is also [*Clean Architecture*](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) which seems to generalize the approaches above without delving into practical details – thus the way it saves its aggregates remains a mystery. ### Cell, Cluster, Domain ![Several intercommunicating subservices are wrapped with a cell gateway that receives client requests, adapters for outgoing communication, and a plugin.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Cell.png) A [*Cell*](https://github.com/wso2/reference-architecture/blob/master/reference-architecture-cell-based.md) (WSO2 name), *Cluster* \[[wiki/concepts/source/appendices/books-referenced|[DEDS]]\], or [*Domain*](https://www.uber.com/en-UA/blog/microservice-architecture/) (Uber’s name) is an encapsulated cluster of [[wiki/concepts/source/basic-metapatterns/services|*services*]] which implements a subdomain and is usually deployed as a single unit. It is modeled after a living cell whose contents are isolated with a membrane. [As a rule of thumb](https://learn.microsoft.com/en-us/azure/architecture/patterns/choreography) \[[wiki/concepts/source/appendices/books-referenced|[DEDS]]\], the communication inside a *Cell* is synchronous, allowing for complex [[wiki/concepts/source/foundations-of-software-architecture/orchestration|*orchestrated*]] use cases that involve the tightly coupled *Cell* components. Contrariwise, *Cells* are usually loosely coupled among themselves and the communications between them tend to be asynchronous ([[wiki/concepts/source/foundations-of-software-architecture/choreography|*choreography*]]). A *Cell* may naturally emerge when a [[wiki/concepts/source/basic-metapatterns/services|*subdomain service*]] becomes too large for comfortable development, which usually means that at least its [[wiki/concepts/source/basic-metapatterns/layers|*domain* layer]] (already limited to a single subdomain) is to be subdivided into sub-subdomain components. If other layers remain intact, this leads to a [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]], otherwise the result is a subsystem of [[wiki/concepts/source/basic-metapatterns/services|*Services*]] or a [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]]. ![Diagrams for: a Cell with a Sandwich, a Cell with services, and a Cell with a pipeline.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Cell%20-%20Basic%20-%20Subtypes.png) Another way a *Cell* can arise is when architects have overcommitted themselves to [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]], gradually [introducing hundreds of them](https://www.uber.com/en-UA/blog/microservice-architecture/) and turning their project into a [*Microservice Hell*](https://www.reddit.com/r/programming/comments/10xvltx/microservice_hell/). Now they need to group their services to: - Have a clear high-level picture of what is going on in the system. - Cut accidental dependencies between their services and the teams behind them. - Improve latency by co-locating the services that interact intensely. ![A layered system transforms into a Sandwich-based Cell. A group of stand-alone services is aggregated into a cell behind a Cell gateway.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Cell%20-%20Basic%20-%20Evolutions.png) In the simplest implementation, the *Cell*’s contents are hidden from its clients by a [[wiki/concepts/source/extension-metapatterns/proxy|*Cell Gateway*]] which acts as an [[wiki/concepts/source/extension-metapatterns/proxy|*Open Host Service*]], allowing for anything inside the *Cell* to be changed at will with no effect on the outside world. > In practice, there are three kinds of outgoing traffic: responses to incoming client requests, pub/sub notifications, and requests to external services. In a *Cell*, the *responses* are sure to pass through or be generated by the *Cell Gateway*. Indeed, a response usually reuses the request’s transport, therefore if a request arrives at the *Gateway*, the corresponding response should also start there. *Notifications* are harder to pinpoint: on one hand, they are a part of the *Cell*’s API which the *Gateway* takes care of. However, that means that the *Cell*’s internals must be aware of the *Gateway*’s existence to use it for their notifications, thus creating a dependency that violates the [[wiki/concepts/source/basic-metapatterns/layers|normal order for a *layered system*]]. Finally, *outgoing requests* bypass the *Cell Gateway* whose role is limited to the *Cell*’s API. Better developed *Cells* employ [[wiki/concepts/source/extension-metapatterns/proxy|*Adapters*]] for outgoing requests to build an [[wiki/concepts/source/extension-metapatterns/proxy|*Anticorruption Layer*]] that protects the *Cell*’s contents from changes in its environment. Please note that, unlike in [*Ports and Adapters*](#ports-and-adapters-hexagonal-architecture), there is no *Adapter* for the *Cell*’s database(s) as they are inside the *Cell*’s perimeter. Another improvement, popularized by Uber’s [*Domains*](https://www.uber.com/en-UA/blog/microservice-architecture/), is the use of [[wiki/concepts/source/implementation-metapatterns/plugins|*Ambassador Plugins*]] which run pieces of business logic that belong to other *Cells* inside a host *Cell*. That both avoids slow intercell calls and boosts the system’s fault tolerance as each *Cell* can now operate independently. ![Injecting a part of a Cell's business logic into another Cell as an ambassador plugin improves performance by avoiding expensive intercell calls.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Cell%20-%20Full-Featured%20-%20Plugins.png) > [In Uber](https://www.uber.com/en-UA/blog/microservice-architecture/), the service responsible for a driver’s status accepts *Plugins* from other services, such as safety checks or compliance, which can block the driver from appearing in the system and responding to ride requests. *Cells* facilitate recursive decomposition by subdomain. They are the building blocks for the following patterns: - [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Cell-Based Architecture*]] which is [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*hierarchical*]] [[wiki/concepts/source/basic-metapatterns/services|*Services*]]. - [*Domain-Oriented Microservice Architecture*]() which is a [*SOA*]() boosted by [[wiki/concepts/source/implementation-metapatterns/plugins|*Ambassador Plugins*]]. ## Summary *Hexagonal Architecture* isolates the component’s business logic from its external dependencies by inserting *adapters* between them. It protects from *vendor lock-in* and allows for late changes of third-party components but requires all the APIs and SPIs to be designed before programming can start and often hinders performance optimizations. --- title: "Implementation metapatterns" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Implementation metapatterns/Implementation metapatterns.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Implementation%20metapatterns/Implementation%20metapatterns source_license_note: "See namespace README; preserve attribution and source links." --- # Implementation metapatterns > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Implementation metapatterns/Implementation metapatterns.md`. A few architectures focus on implementation of components: ### [[wiki/concepts/source/implementation-metapatterns/plugins|Plugins]] ![A diagram of Plugins Architecture, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Plugins.png) The [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]] family of patterns is about separating a system’s main logic from the customizable details of its behavior. That allows for the same codebase to be used for multiple flavors or customers. *Includes*: Plug-In Architecture, Addons, Strategy, Hooks. ### [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Hexagonal Architecture]] ![A diagram of Hexagonal Architecture, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Hexagonal%20Architecture.png) [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] is a specialization of [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]] where every external dependency is isolated behind an [[wiki/concepts/source/extension-metapatterns/proxy|*Adapter*]], making it easy to update or replace third-party components. *Includes*: Ports and Adapters, Onion Architecture, and Clean Architecture; Model-View-Presenter (MVP), Model-View-ViewModel (MVVM), Model-View-Controller (MVC), and Action-Domain-Responder (ADR); Pedestal and Cell (Cluster or Domain). ### [[wiki/concepts/source/implementation-metapatterns/microkernel|Microkernel]] ![A diagram of Microkernel, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Microkernel.png) [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]] is another derivation of [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]], featuring a rudimentary *core* component which mediates between resource *consumers* (*applications*) and resource *providers*. The *microkernel* is a [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] to the *applications* and an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] to the *providers*. *Includes*: operating system, software framework, virtualizer, distributed runtime, interpreter, configuration file, Saga Engine, and AUTOSAR Classic Platform. ### [[wiki/concepts/source/implementation-metapatterns/mesh|Mesh]] ![A diagram of Services over a mesh, with explanations.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Contents/Mesh.png) A [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]] consists of intercommunicating shards, each of which may host an application. The shards coalesce into a fault-tolerant distributed [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]]. *Includes*: peer-to-peer networks, Leaf-Spine Architecture, Actors, Service Mesh, and Space-Based Architecture. --- title: "Mesh" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Implementation metapatterns/Mesh.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Implementation%20metapatterns/Mesh source_license_note: "See namespace README; preserve attribution and source links." --- # Mesh > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Implementation metapatterns/Mesh.md`. ![A diagram for Services over a mesh, in abstractness-subdomain-sharding coordinates.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Main/Mesh.png) *Hive mind.* Go decentralized. Known as: Mesh, Grid. Structure: A system of interconnected [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]] which usually make a [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]]. Type: Implementation. | *Benefits* | *Drawbacks* | | --- | --- | | No single point of failure | Overhead in administration and security | | The system is able to self-heal | Performance is likely to suffer | | Great scalability | The *Mesh* engine is very hard to debug | | Available off the shelf | Unreliable communication must be accounted for in the code | References: [Wikipedia](https://en.wikipedia.org/wiki/Network_topology#Classification) and \[[wiki/concepts/source/appendices/books-referenced|[DDIA]]\] on topology and protocols. \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] on *Service Mesh* and *Space-Based Architecture*. A [long](https://buoyant.io/service-mesh-manifesto) and [short](https://www.oracle.com/cloud/cloud-native/service-mesh/what-is-a-service-mesh/) article on *Service Mesh*. If a system is required to survive faults, all of its components must be both [[wiki/concepts/source/basic-metapatterns/shards|*sharded*]] and interconnected, which is the definition of a *Mesh* – a network of interacting instances (*nodes*). In most cases the lower layer of a *shard* implements connectivity while the business logic resides in its upper layer(s). Whilst the connectivity component tends to be identical in every node of a system, the upper components may be identical – forming [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]], or different – forming [[wiki/concepts/source/basic-metapatterns/services|*Services*]]. Most *Meshes* support adding and removing parts of their networks dynamically, which allows for scaling up, scaling down, and fault recovery. That is achieved through a flexible network topology, which has the chance of missing or duplicating requests, which may lead to a single action being executed by two instances of a service in parallel or by the same instance twice. Moreover, *Mesh*-mediated communication is likely to be slower than direct one. ### Performance In most (all?) implementations the user *application* is colocated with a *node* of the *Mesh*, thus communicating through the *Mesh* does not add an extra network hop (which would strongly degrade performance). However, that holds true only when the *Mesh node* knows the destination of the message it should send – when it has already established a communication channel towards it. Finding a new destination may not always be easy and would often require consulting registries, and sometimes waiting for the network topology to stabilize, which may involve timeouts (like the ones you could have experienced with torrents). On the other hand, no other architecture is known to seamlessly support huge networks. ### Dependencies *Mesh*, being a *sharded Middleware*, inherits dependencies from both of its parent metapatterns: - As with [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]], the services that run over a *Mesh* depend both on the *Mesh’s API* and on each other (or on a shared message format, aka [[wiki/concepts/source/extension-metapatterns/shared-repository|*Stamp Coupling*]], or a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]] if they [[wiki/concepts/source/foundations-of-software-architecture/shared-data|use one for communication]]). - As with [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]], the nodes of the *Mesh* should communicate through a backward- and forward-compatible protocol as there will likely be periods of time when multiple versions of the *Mesh* nodes coexist. ### Applicability *Mesh* is perfect for: - *Dynamic scaling.* Instances of services may be quickly added or removed. - *High availability.* A *Mesh* is very hard to disable or kill because it both creates new instances of failed services and finds routes around failed connections. *Mesh* fails in: - *Low latency domains.* Spreading information through a *Mesh* is slow and sometimes unreliable. - *Security-critical systems.* A public *Mesh* exposes a high attack surface while the scalability of private deployments is limited by the installed hardware. - *Quick and dirty programming*. The possible message duplication may cause evil bugs if you ignore the risks. ### Relations *Mesh*: - Misuses [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]]. - Uses [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]]. - Is the base for running multiple instances of a [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]], [[wiki/concepts/source/basic-metapatterns/layers|*layers*]], or [[wiki/concepts/source/basic-metapatterns/services|*services*]]. - Implements a distributed [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]], [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]], or [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]]. ## Variants *Meshes* are known to vary: ### By structure The connections in a *Mesh* can be: - *Structured* or *pre-defined* – the *Mesh* is pre-designed and hard-wired. This kind of topology provides redundancy but not scalability. - *Unstructured* or *ad-hoc* – *nodes* can be added or removed at runtime, restructuring the *Mesh*. ### By connectivity Each *node* is: - Connected to all other nodes – a *fully connected Mesh*. Such *Meshes* are limited in size because the number of interconnections grows as a square of the number of nodes. Notwithstanding, they offer the best communication speed and delivery guarantees. - Connected to some other nodes. There are many possible [*topologies*](https://en.wikipedia.org/wiki/Network_topology#Classification) with the choice for any given task better left to experts. ### By the number of mesh layers The connected *nodes* of a *Mesh* may be: - Identical (one-layer *Mesh*). A node behaves according to its site in the network. - Specialized (multi-layer *Mesh*). Some nodes implement *trunk* (route messages and control the topology) while others are *leaves* (run user applications). ## Examples The diversity of *Meshes* can be seen in the following examples: - [*Peer-to-Peer Networks*](#peer-to-peer-networks) for sharing files, CPU time, or Internet access. - [*Leaf-Spine Architecture*](#leaf-spine-architecture-spine-leaf-architecture) found in datacenters. - [*Actors*](#actors) – class-like software entities that communicate through messaging. - [*Service Mesh*](#service-mesh) which hosts *Microservices*. - [*Space-Based Architecture*](#space-based-architecture) which co-locates service instances and nodes of a distributed in-memory data store. ### Peer-to-Peer Networks ![Each application is connected to a node of a mesh. The nodes find each other's addresses in a registry and then communicate directly.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/P2P.png) [*Peer to Peer*](https://en.wikipedia.org/wiki/Peer-to-peer) (*P2P*) networks are intended for massive resource sharing over unstable connections. The *resource* in question may be data (torrents, blockchain, [P2PTV](https://en.wikipedia.org/wiki/P2PTV)), CPU time ([volunteer computing](https://en.wikipedia.org/wiki/Volunteer_computing), [distributed compilation](https://en.wikipedia.org/wiki/Distcc)), or Internet access ([Tor](https://en.wikipedia.org/wiki/Tor_(network)), [I2P](https://en.wikipedia.org/wiki/I2P)). In most cases it is shared over an *unstructured* (as participants join and leave) *2-layer* (there are dedicated servers that register and coordinate users) network which is [*overlaid*](https://en.wikipedia.org/wiki/Overlay_network) on top of the Internet. All the leaf nodes run identical narrowly specialized software (i.e. either file sharing or blockchain, but not both at once) which provides the clients with access to resources of other nodes, making a kind of distributed [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] or [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]]. Examples: torrent, onion routing (Tor), blockchain. ### Leaf-Spine Architecture, Spine-Leaf Architecture ![Each server of a datacenter is connected to a leaf node. Each leaf communicates with every spine node.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Leaf-Spine.png) This [datacenter network architecture](https://www.geeksforgeeks.org/spine-leaf-architecture/) is a rare example of a *structured fully connected Mesh*. It consists of client-facing (*leaf*) and internal (*spine*) network switches. Each *leaf* is connected to every *spine*, allowing for very high bandwidth (by distributing the traffic over multiple routes) that is almost insensitive to failures of individual hardware as there are always many parallel connections. ### [[wiki/concepts/source/basic-metapatterns/shards|Actors]] ![Each actor reads from its message queue and writes to other actors' message queues.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Actors.png) A system of *Actors* may be classified as a *fully connected Mesh* with the actors’ message queues being the nodes of the *Mesh*. Any actor can post messages to the queue of any other actor which it knows about, as all the actors share a virtual namespace or physical address space. ### [[wiki/concepts/source/extension-metapatterns/middleware|Service Mesh]] ![A service mesh comprises services each of which is connected to a sidecar connected to a mesh node. The mesh nodes communicate with a monitoring and control infrastructure.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Service%20Mesh.png) A [*Service Mesh*](https://buoyant.io/service-mesh-manifesto) \[[wiki/concepts/source/appendices/books-referenced|[FSA]], [[wiki/concepts/source/appendices/books-referenced|MP]]\] is a distributed [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] for running [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]]. It is a 2-layer *Mesh* which contains one or a few management nodes (*control plane*) and many user nodes (*data plane*). Each data plane node colocates: - A *mesh engine node* that deals with connectivity, - One or more [[wiki/concepts/source/extension-metapatterns/proxy|*Sidecars*]] ([[wiki/concepts/source/extension-metapatterns/proxy|*Proxies*]] where the support of *cross-cutting concerns* – the identical code in use by every service, e.g. logging or encryption – resides), - A user *application* (*microservice*) that differs from node to node. The control plane (re-)starts, updates, scales, and collects statistics from the nodes of the data plane. *Service Mesh* addresses some of the weaknesses of naive [[wiki/concepts/source/basic-metapatterns/services|*Services*]]: it provides tools for centralized management and allows for virtual [[wiki/concepts/source/analytics/sharing-functionality-or-data-among-services|sharing]] (through creating physical copies) of libraries to be accessed by all the service instances. It also takes care of scaling and load balancing. Ready-to-use *Service Mesh* frameworks are popular with the [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]] architecture. ### [[wiki/concepts/source/extension-metapatterns/sandwich|Space-Based Architecture]] ![Each processing unit is connected to a node of a data grid. The nodes directly exchange data updates and store them into a persistent database via a writer. There is also a reader to initiate nodes.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Space-Based%20Architecture.png) [*Space-Based Architecture*](https://en.wikipedia.org/wiki/Space-based_architecture) \[[wiki/concepts/source/appendices/books-referenced|[SAP]], [[wiki/concepts/source/appendices/books-referenced|FSA]]\] is a kind of [*Service Mesh*](#service-mesh) with an integrated [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]] (a [*tuple space*](https://en.wikipedia.org/wiki/Tuple_space) – shared dictionary – called [[wiki/concepts/source/extension-metapatterns/shared-repository|*Data Grid*]]) and an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] (called *Processing Grid*). The user services are called *Processing Units*. They may be identical (making [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]]) or different (resulting in [[wiki/concepts/source/basic-metapatterns/services|*Services*]]). This architecture is used for: - Highly scalable systems with relatively small datasets, in which case the entire database contents are replicated in the memory of each node. This works around the throughput and latency limits of an ordinary database. - Huge datasets, with each node owning a part of the total data. This hacks around the storage capacity and latency limits of a database, which may even be kept out of the loop, leaving the *Mesh* as the only data storage. There are multiple instances of the same data in *Processing Units*. Any change to the data in one unit must propagate to other units. That can be done in several ways: - Asynchronously, causing conflicts if the same data is changed elsewhere at the same time. - Synchronously, waiting for the propagation results and conflict resolution – a kind of distributed transaction which has poor latency. - The unit takes write ownership of the data before the write. That is not good for latency as well, but it may be a good choice for an evenly distributed load if the *Mesh* engine provides temporary locality of requests, i.e. it forwards requests that touch the same data to the same node. The choice of the strategy depends on your domain. The in-memory data in the nodes is usually loaded from a *Persistent Database* on initialization of the system, and any change to the data is replicated asynchronously back to the *Persistent Database*, which serves as a means of fault recovery in the unlikely case the entire *Mesh* goes down. ## Summary *Mesh* is a layer of intercommunicating instances of an infrastructure component that makes a foundation for running custom services in a distributed environment. This architecture is famous for its scalability and fault tolerance but is too complex to implement in-house, and may incur performance, administration and development overhead. --- title: "Microkernel" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Implementation metapatterns/Microkernel.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Implementation%20metapatterns/Microkernel source_license_note: "See namespace README; preserve attribution and source links." --- # Microkernel > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Implementation metapatterns/Microkernel.md`. ![A diagram for Microkernel, in abstractness-subdomain-sharding coordinates.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Main/Microkernel.png) *Communism.* Share resources among consumers. Known as: Microkernel (Architecture) \[[wiki/concepts/source/appendices/books-referenced|[POSA1]], [[wiki/concepts/source/appendices/books-referenced|POSA4]] but [[wiki/concepts/source/analytics/ambiguous-patterns|not]] [[wiki/concepts/source/appendices/books-referenced|SAP]] and [[wiki/concepts/source/appendices/books-referenced|FSA]]\]. Structure: A layer of [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrators*]] over a [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] over a layer of [[wiki/concepts/source/basic-metapatterns/services|*Services*]]. Type: System topology, implementation. | *Benefits* | *Drawbacks* | | --- | --- | | The system’s complexity is evenly distributed among the components | The API and SPIs are very hard to change | | The resource providers are easy to replace | Performance is suboptimal | | The components can have independent qualities | Latency is often unpredictable | | A resource provider can be implemented and tested in isolation | | | Each application is sandboxed by the microkernel | | | The system is platform-independent | | References: the Microkernel pattern in \[[wiki/concepts/source/appendices/books-referenced|[POSA1]]\]. While vanilla [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]] and [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] keep the business logic in their monolithic *core* components, *Microkernel* treats the core as a thin [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] (called *microkernel*) that connects user-facing applications (*external services*) to resource providers (*internal services*). The *resource* in question can be anything ranging from CPU time or RAM to business functions. The external services communicate with the microkernel through its *API* while the internal services implement the microkernel's *service provider interfaces* (*SPIs*) (usually there is one kind of internal service and an SPI per resource type). On one hand, the pattern is very specific and feels esoteric. On the other – it is indispensable in many domains, with many more real-life occurrences than would be expected. *Microkernel* finds its place where there are a variety of applications that need to use multiple shared resources, with each resource being independent of others and requiring complex management. ### Performance The *microkernel*, being an extra layer of indirection, degrades performance. The actual extent varies from a few percent for *OSes* and *virtualizers* to an order of magnitude for *scripts*. There is also a more grievous aspect of performance degradation, namely that latency becomes unpredictable as soon as the system runs short of one of the shared resources: memory, disk space, CPU time, or even storage for deleted objects. That is why [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|real-time systems]] rely on extremely minimalistic [real-time OS](https://en.wikipedia.org/wiki/Real-time_operating_system)es or even run on bare metal. It is common to see system components communicate directly via shared memory or sockets bypassing the *microkernel* to alleviate the performance penalty which it introduces. ### Dependencies The *applications* depend on the *API* of the *microkernel* while the *providers* depend on its *SPIs*. On one hand, that isolates the applications and providers from each other, letting them develop independently. On the other hand, the microkernel’s API and SPIs should be very stable to support older versions of the components which the microkernel integrates. ![Applications depend on the API of the microkernel. Providers depend on its SPIs.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Dependencies/Microkernel.png) ### Applicability *Microkernel* is applicable in: - *Systems programming.* You manage system resources and services which will be used by untrusted client applications. Hide the real resources behind a trusted proxy layer. Be ready to change the hardware platform without affecting the existing client code. - *Frameworks that integrate several subdomains.* The microkernel component coordinates multiple specialized libraries. Its API is a [*Facade*](https://refactoring.guru/design-patterns/facade) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] for the functionality which it organizes. - *Scripting or* [*DSL*](https://en.wikipedia.org/wiki/Domain-specific_language)*s.* The microkernel is an [*Interpreter*](#interpreter-script-domain-specific-language-dsl) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] which lets your clients’ code manage the underlying system. *Microkernel* does not fit: - *Coupled domains.* Any degree of coupling between the resource providers complicates the microkernel and its SPIs, and is likely to degrade performance, which, however, may be salvaged by introducing direct communication channels between the providers. ### Relations ![Microkernel as a middleware and as an orchestrator; applications of Microkernel Architecture as Backends for Frontends.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Relations/Microkernel.png) *Microkernel*: - Is a specialization of [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]]. - Is related to [*Backends for Frontends*](), which is a layer of [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrators*]] over a layer of [[wiki/concepts/source/basic-metapatterns/services|*Services*]]; *Microkernel* adds a [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] in between. - Is a kind of 2-layered [*SOA*]() with an [[wiki/concepts/source/extension-metapatterns/orchestrator|*ESB*]]. - The *microkernel* layer serves as (implements) a [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] for the upper (*external*) [[wiki/concepts/source/basic-metapatterns/services|*Services*]] and often makes an [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] for the lower (*internal*) *Services*. - May be implemented by a [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]]. ## Examples *Microkernel* can appear in many forms: - An [*Operating System*](#operating-system) shares hardware resources among many applications. - A [*Software Framework*](#software-framework-pluggable-component-framework) provides a unified interface to access several libraries. - A [*Virtualizer*](#virtualizer-hypervisor-container-orchestrator-distributed-runtime) maintains a generic interface to run guest software on any host OS and hardware. - An [*Interpreter*](#interpreter-script-domain-specific-language-dsl) executes client scripts in a sandbox. - A [*Configurator*](#configurator-configuration-file) sets up a system in accordance with a configuration file. - A [*Saga Engine*](#saga-engine) runs distributed transactions. - An [*AUTOSAR Classic Platform*](#autosar-classic-platform) provides automotive applications with access to any chip on board. ### Operating System ![Each application communicates with its runtime interfacing the shared operating system kernel which communicates with device drivers that adapt hardware components.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/OS.png) The original inspiration for *Microkernel*, namely *operating systems*, provides an almost perfect example of the pattern, even though their kernels are not that “micro-” (unless you are running [MINIX](https://en.wikipedia.org/wiki/Minix_3#Architecture) or [QNX](https://en.wikipedia.org/wiki/QNX#Technology)). [[wiki/concepts/source/basic-metapatterns/services|Device *drivers*]] (*internal services*) encapsulate available hardware resources (see [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Pedestal*]]) and make them accessible to user-space *applications* (*external services*) via an OS *kernel*. *Drivers* for a given kind of subsystem (e.g. network adapter or disk drive) are polymorphic towards the kernel and match the hardware installed. ### Software Framework, Pluggable Component Framework ![A framework is a facade between a user application and several lower-level components.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Framework.png) In a *Software Framework* or *Pluggable Component Framework* \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\] the *microkernel* (*abstract core* \[[wiki/concepts/source/appendices/books-referenced|[DDD]]\]) is a [[wiki/concepts/source/extension-metapatterns/orchestrator|*Facade*]] \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] that integrates a set of libraries and exposes a user-friendly high-level interface. [PAM](https://docs.oracle.com/cd/E23824_01/html/819-2145/pam-01.html) looks like a reasonably good example. ### Virtualizer, Hypervisor, Container Orchestrator, [[wiki/concepts/source/basic-metapatterns/services|Distributed Runtime]] ![A virtualizer stands between user applications and several instances of an operating system each running on a separate computer.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Virtualizer.png) *Hypervisors* (Xen), PaaS and [FaaS](https://shivangsnewsletter.com/p/why-doesnt-cloudflare-use-containers), *container orchestrators* (Kubernetes) \[[wiki/concepts/source/appendices/books-referenced|[DDS]]\], and distributed *actor frameworks* (Akka, Erlang/Elixir/OTP) use resources of the underlying computer(s) to run guest applications. A hypervisor virtualizes the resources of a single computer while a distributed runtime manages those of multiple servers – in the last case there are several instances of the same kind of an *internal server* which abstracts a host system. ### Interpreter, Script, Domain-Specific Language (DSL) ![Each script runs over its instance of an interprester. All the interpreters share a set of libraries.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Interpreter.png) User-provided *scripts* are run by an *Interpreter* \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] which also allows them to access a set of installed libraries. The *Interpreter* is a microkernel, and the syntax of the script or [*DSL*](https://en.wikipedia.org/wiki/Domain-specific_language) which it interprets is the microkernel’s API. ### Configurator, Configuration File ![A configurator runs at a system's startup, reads a configuration file, and sets up a system of services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Config%20file.png) *Configuration files* may be regarded as short-lived [*scripts*](#interpreter-script-domain-specific-language-dsl) that configure the underlying modules at the start of the system. The parser of the configuration file is a transient *microkernel*. ### [[wiki/concepts/source/extension-metapatterns/orchestrator|Saga]] Engine ![A saga engine calls several services while executing multiple short-lived sagas.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Saga%20engine.png) A [[wiki/concepts/source/extension-metapatterns/orchestrator|*Saga*]] \[[wiki/concepts/source/appendices/books-referenced|[MP]]\] orchestrates distributed transactions. It may be written in a [*DSL*](https://en.wikipedia.org/wiki/Domain-specific_language) which requires a compiler or [interpreter](#interpreter-script-domain-specific-language-dsl), which is a *microkernel*, to execute. ### AUTOSAR Classic Platform ![AUTOSAR Classic defines three segmented layers: applications, runtime environment with a shared Virtual Functional Bus, and basic software with generic and hardware-specific services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/AUTOSAR%20classic.png) The [notorious](https://www.reddit.com/r/embedded/comments/leq366/comment/gmiq6d0/) [automotive standard](https://www.autosar.org/fileadmin/standards/R20-11/CP/AUTOSAR_EXP_VFB.pdf), though inspired by [*SOA*](), is structured as a distributed / virtualized *Microkernel*. The application layer comprises a network of *software components* spread out over hundreds of chips which are, for some secret reason, called *electronic control units* (*ECU*s). Both communication paths between the software components and much of the system’s code are static (auto-generated at compilation time). A software component may access hardware of its ECU via standard interfaces. The *microkernel* shows up as *Virtual Functional Bus* (*VFB*) which, as a *distributed* [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]], provides communication between the *applications* by virtualizing multiple *Runtime Environments* (*RTEs*) – the local [system interfaces](https://www.autosar.org/fileadmin/standards/R22-11/CP/AUTOSAR_EXP_LayeredSoftwareArchitecture.pdf). ## Summary *Microkernel* is a ubiquitous approach to sharing resources among consumers, where both resource providers and consumers may be written by external companies. --- title: "Plugins" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Implementation metapatterns/Plugins.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Implementation%20metapatterns/Plugins source_license_note: "See namespace README; preserve attribution and source links." --- # Plugins > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Implementation metapatterns/Plugins.md`. ![A diagram for Plugins Architecture, in abstractness-subdomain-sharding coordinates.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Main/Plugins.png) *Overspecialize, and you breed in weakness.* Customize the system through attachable modules. Known as: Plug-In Architecture \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\], [Extension Architecture](https://www.uber.com/en-UA/blog/microservice-architecture/), (misapplied) Microkernel (Architecture) \[[wiki/concepts/source/appendices/books-referenced|[POSA1]], [[wiki/concepts/source/appendices/books-referenced|POSA4]], [[wiki/concepts/source/appendices/books-referenced|SAP]], [[wiki/concepts/source/appendices/books-referenced|FSA]]\], Plugin \[[wiki/concepts/source/appendices/books-referenced|[PEAA]]\], [Strategy](https://refactoring.guru/design-patterns/strategy) \[[wiki/concepts/source/appendices/books-referenced|[GoF]], [[wiki/concepts/source/appendices/books-referenced|POSA4]]\], Reflection \[[wiki/concepts/source/appendices/books-referenced|[POSA1]], [[wiki/concepts/source/appendices/books-referenced|POSA4]]\], Aspects, Hooks. Structure: A [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] or subsystem extended with one or more modules that customize its behavior. Type: Implementation, extension components. | *Benefits* | *Drawbacks* | | --- | --- | | Some aspects are easy to customize | Testability is poor (too many combinations) | | The customized system is relatively light-weight | Performance is suboptimal | | Platform-specific optimizations are supported | Designing good SPIs for plugins is hard | | The custom pieces may be written in a different programming language or DSL | | References: \[[wiki/concepts/source/appendices/books-referenced|[SAP]]\] and \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] [[wiki/concepts/source/analytics/ambiguous-patterns|mistakenly]] call this pattern *Microkernel* and dedicate chapters to it. Most systems require some extent of customizability that may range from the basic codec selection by a video player to the screens full of tools and wizards unlocked once you upgrade your subscription plan. This is achieved by keeping the *core* functionality separate from its extensions, which are developed by either your team or external enthusiasts to fine-tune the behavior of the system. The cost of flexibility is paid in complexity of design – the need to predict which aspects should be customizable and which *SPI*s ([*Service Provider Interfaces*](https://en.wikipedia.org/wiki/Service_provider_interface)) are good for both known and still unknown uses by the extensions. If the communication between the core and *plugins* is heavy, performance may suffer. ### Performance Using plugins usually degrades performance. The effect may be negligible for in-process plugins (such as strategies or codecs) but it becomes noticeable if inter-process communication or networking is involved. The only case for a plugin to improve performance of a system that I can think of is when a part of the client’s business logic moves to a lower layer of a system or to another service forming an [*Ambassador Plugin*](#ambassador-plugin-logic-extension). That is similar to the [[wiki/concepts/source/basic-metapatterns/layers|strategy injection optimization for *Layers*]]. Real-world examples include: - The use of *stored procedures* in databases, - HFT rules and price tables [uploaded](https://www.youtube.com/watch?v=sX2nF1fW7kI) to a network card or FPGA, - Customization of a supplier [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cell*]] for varying needs of its client *Cells* in [*Domain-Oriented Microservice Architecture*](). ![Business logic injection in Layers and Services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Performance/Plugins-injection.png) ### Dependencies Each *plugin* depends on the *core*’s *API* (for *Addons*) or *SPI* (for *Plugins*) for the functionality it extends. That makes the APIs and SPIs nearly impossible to modify, only to extend, as there tend to be many plugins in the field, some of them out of active development, that rely on any given method of the already published interfaces. ![Each plugin depends on an interface of the core.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Dependencies/Plugins.png) ### Applicability *Plugins* are good for: - *Product lines.* The shared *core* functionality and some *plugins* are reused by many flavors of the product. Per-client customizations are largely built using existing plugins. - *Frameworks.* A framework is a functional core to be customized by its users. When shipped with a set of plugins it becomes ready-to-use. - *Platform-specific customizations. Plugins* allow both for native look and feel (e.g. desktop vs mobile vs console) and for the use of platform-specific hardware. *Plugins* do not perform well in: - *Highly optimized applications.* Any generic code tends to be inefficient. A generic *SPI* that serves a family of plugins is unlikely to be optimized for the use by any one of them. ### Relations ![A monolith with plugins; layers with plugins; a Cell with a plugin.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Relations/Plugins.png) *Plugins*: - Implement [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]], [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]], or [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cell*]]. - Extend [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]], [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]], or [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cell*]] with one or two layers of services. ## Variants *Plugins* are omnipresent if we take [*Strategy*](https://refactoring.guru/design-patterns/strategy) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] for a kind of plugin. They vary: ### By the direction of communication A plugin may: - Provide input to the core (as UI screens and [CLI](https://en.wikipedia.org/wiki/Command-line_interface) connections do). - Receive output from the core (e.g. collection of statistics). - Participate in both input and output (like health check instrumentation). - Take the role of [[wiki/concepts/source/extension-metapatterns/orchestrator|*Controller*]] (*Orchestrator*) – the plugin processes events from the core and decides on the core’s further behavior. - Be a data processor – the plugin implements a part of a [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|data processing]] algorithm which is run by the core. This enables platform-specific optimizations, like SIMD or DSP. ### By linkage Plugins may be *built in* or selected dynamically: - Every *flavor* (e.g. free, lite, pro, premium) of a product line incorporates a single set of plugins (configuration) built into the application. Web services often use this kind of customization for [[wiki/concepts/source/basic-metapatterns/shards|*Multitenancy*]]. - Other systems choose and initiate their plugins on startup according to their configuration files or licenses. - Still others support attaching and detaching plugins dynamically at runtime. ### By granularity Plugins come in different sizes: - Small functions or classes are built into the core. They seem to implement the [*Strategy*](https://refactoring.guru/design-patterns/strategy) \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] / *Plugins* \[[wiki/concepts/source/appendices/books-referenced|[PEAA]]\] design patterns. - [*Aspects*](https://en.wikipedia.org/wiki/Aspect_(computer_programming)), such as logging and memory management, pervade a system and are accessed from many places in its code. *Reflection* \[[wiki/concepts/source/appendices/books-referenced|[POSA1]], [[wiki/concepts/source/appendices/books-referenced|POSA4]]\] probably belongs there. - Modules are plugged in as separate system components. This kind of *Plugins* matches the topic of this book and is further developed by [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] and [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]]. ### By the number of instances A plugin may be: - *Mandatory* (1 instance), like a piece of algorithm used by the core for a calculation. - *Optional* (0 or 1 instance), like a smart coloring scheme for a text editor. - *Subscriptional* (0 or more instances), like log output which may go to a console, the system log, a log file, socket, or to all of them simultaneously. ### By execution mode Plugins may be: - Linked as a binary code called from within the core. - Written in a [*domain-specific language*](https://en.wikipedia.org/wiki/Domain-specific_language) (*DSL*) and [[wiki/concepts/source/implementation-metapatterns/microkernel|interpreted]] by the core. - Remote, available as a stand-alone web service or even hardware component which the core needs to access through a dedicated protocol. ## Examples *Plugins* can take different roles and places in the system. Though their classification and naming has never been well established, we can discern the following kinds of *plugins*, which are non-exclusive, meaning that a single application may support some or all of them at once: - A [true *Plugin*](#true-plugin-or-plug-in) provides custom low-level functionality. - An [*Ambassador Plugin*](#ambassador-plugin-logic-extension) injects a part of a given service’s business logic into another service. - An [*Extension*](#extension) modifies its target’s workflow (use cases). - An [*Addin*](#addin-or-add-in) is a deeply integrated optional part of a system’s core logic. - An [*Addon*](#inexact-addon-or-add-on) is an addition that wraps an application to improve user experience. ### True Plugin (or Plug-in) ![Several low-level plugins are called by a use case running in a system.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Plugins.png) A true *Plugin* is registered with and called by the system’s *core* to provide (as an algorithm) or extend (as a custom step) a specific low-level functionality. It is usually made by a third party. Customizable software often exposes multiple interfaces for different kinds of *Plugins*, some of which are *optional* (0 or 1 instance) or *subscriptional* (multiple instances) as explained [earlier](#by-the-number-of-instances). Examples: codecs in a video player, country-specific tax calculation rules in accounting software, or filters in a traffic sniffer. ### [[wiki/concepts/source/extension-metapatterns/proxy|Ambassador]] Plugin, Logic Extension ![An ambassador plugin is a part of one service hosted inside another service. When called, it may consult its origin service or make independent decisions.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Ambassador%20Plugin.png) A service may accept *Plugins* that [act on behalf of peer services](https://www.uber.com/en-UA/blog/microservice-architecture/) (are are their [[wiki/concepts/source/extension-metapatterns/proxy|*Ambassadors*]]) and are implemented by their teams. That [[wiki/concepts/source/analytics/dependency-inversion-in-architectural-patterns|inverts dependencies]]: whoever wants to affect the behavior of your service uses your SPI to inject their code into your service which apart from that remains self-contained. As a result, whatever used to be a system-wide workflow becomes limited to a single subdomain. Though an *Ambassador Plugin* (aka Uber’s [*Logic Extension*](https://www.uber.com/en-UA/blog/microservice-architecture/)) may call the service on whose behalf it acts, it is preferable performance-wise for it to make independent decisions without cross-service calls (it works as a smart [[wiki/concepts/source/extension-metapatterns/proxy|*Cache*]] that provides decisions instead of data). For data-driven decisions the *Ambassador* may need to opaquely receive and store data ([*Data Extension*](https://www.uber.com/en-UA/blog/microservice-architecture/)) in its host’s data store. *Ambassador Plugins* are widely used in Uber’s [*Domain-Oriented Microservice Architecture*]() to decouple its [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cells*]]. ### Extension ![An extension is called as a high-level part of a system's use case.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Extension.png) An *Extension* changes use cases by modifying the application’s business logic, but it is still called by the application’s *core*. An *Extension* may install a group of low-level *Plugins* which add to the system’s low-level functionality whatever tools the *Extension* relies on. Examples: IDE customization. ### Addin (or Add-in) ![An addin is hosted inside a system and implements a part of its control flow.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Addin.png) An *Add-in* is deeply integrated with the *core* – the system has been originally designed to provide it thorough access to its internals. It comes from the system’s creators or external teams that commit to the system *core*’s codebase. Examples: complex extensions for web browsers or static website generators. ### (inexact) Addon (or Add-on) ![An addon is a layer between a system and its client. It translates a single client request into multiple calls to the underlying system.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Addon.png) Though any of the variants of *Plugins* described above may sometimes be called an *addon*, there is a kind of system extension which perfectly matches the meaning of the word. A true *Addon* is built on top of the system’s *API* (not a dedicated *SPI* as with other kinds of *Plugins*) and calls into the system from outside – it is a kind of an external [[wiki/concepts/source/extension-metapatterns/proxy|*Adapter*]] or even [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] for the system. Examples: applications that provide [[wiki/concepts/source/extension-metapatterns/proxy|user interface]] for command-line tools such as git. ## Summary *Plugins* allow for customization of a component’s behavior at the cost of increased complexity, poor testability, and somewhat reduced performance. --- title: "About this book" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Introduction/About this book.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Introduction/About%20this%20book source_license_note: "See namespace README; preserve attribution and source links." --- # About this book > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Introduction/About this book.md`. When I was learning programming, there was [*Gang of Four*](https://en.wikipedia.org/wiki/Design_Patterns). The book promised to teach software design, and it did to an extent with the case study provided. However, the patterns it described were merely random tools which had little in common. After several years, having reinvented [*Hexagonal Architecture*](https://en.wikipedia.org/wiki/Hexagonal_architecture_(software)) along the way, I learned about [*Pattern-Oriented Software Architecture*](https://en.wikipedia.org/wiki/Pattern-Oriented_Software_Architecture). The series had many more intriguing patterns, and promised to provide a *system of patterns* or a *pattern language*, but failed to build an intuitive whole. Then there were specialized books with [*Domain-Driven Design*](https://en.wikipedia.org/wiki/Domain-driven_design) and *Microservices* patterns. There was the [*Software Architecture Patterns*](https://www.oreilly.com/content/software-architecture-patterns/) primer by Mark Richards. Its simplicity felt great, but it had only 5 architectural styles, while his next book, *Fundamentals of Software Architecture*, dived too deeply into practical details and examples to be easily grasped. Now, having leisure thanks to the war, burnout, unemployment, and depression I have had a chance to collect architectural patterns from multiple sources and build a taxonomy of architectures. My goal was to write the very book I lacked in those early years: a shallow but intuitive overview of all the software and systems architectures as used in practice, their properties and relations. I hope that it will be of some help both to novice programmers as a kind of a primer on the principles of high-level software design and to adept architects by reminding them of the big picture outside of their areas of expertise. The book is mostly technology-agnostic. It does not answer practical questions like “Which database should I use?” Instead it inclines towards the understanding of “When should I use a shared database?” Any specific technologies ~are easy to google~ can be found ~over the Internet~ somewhere in the Noosphere. This book started as a rather small project to prove that patterns can be intuitively classified (*These nightmarish creatures* can *be felled! They* can *be beaten!*) but grew into a multifaceted compendium of a hundred or so architectures and architectural patterns. It is grounded in the idea that software and systems architecture evolves naturally, as opposed to being scientifically planned. Thus, the architectures may exhibit [[wiki/concepts/source/foundations-of-software-architecture/programming-and-architectural-paradigms|fractal features]], just like those in biology – merely because the [[wiki/concepts/source/analytics/cohesers-and-decouplers|set of guidelines and forces]] remains the same for most systems that range from low-end embedded devices to world-wide financial networks. Moreover, in some cases we can see the same patterns applied to hardware design. The idea of unifying software and systems architecture is heretical. I am well aware of that. Still, the industry is in the early stage of alchemy these days: the same things are sold under multitudes of names, being remarketed or reinvented every decade. If this book manages to provide rules of thumb, similar to those of biology (a bat is a mammal, thus it should run on all four, while ostriches, as birds, must fly to Europe each spring), I will be happy with that. *Science makes progress funeral by funeral*. The latest version of the book is available for free on [GitHub](https://github.com/denyspoltorak/metapatterns) and [LeanPub](https://leanpub.com/metapatterns), and can be [read online](https://metapatterns.io/). As there is no one who has practiced all the known architectures, it will be full of mistakes. I rely on your goodwill to correct them and improve the text. Critical reviews are warmly welcome: please write an [email](mailto:descri@gmail.com) or contact me [on LinkedIn](https://www.linkedin.com/in/denyspoltorak/). ## Structure of the book The [[wiki/concepts/source/introduction/metapatterns|first chapter]] explains the main idea which makes this book different from others. The [[wiki/concepts/source/introduction/system-topologies|second chapter]] outlines the scope of the book’s content. The following chapters in the [[wiki/concepts/source/foundations-of-software-architecture/foundations-of-software-architecture|first part]] touch on several general topics that are referenced throughout the book. The next [[wiki/concepts/source/basic-metapatterns/basic-metapatterns|four parts]] iterate over *metapatterns* (clusters of closely related architectural patterns), starting with the simplest one, namely [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]], then heading towards more complex systems that may be derived from *Monolith* by recursively dissecting it with interfaces. Each chapter describes a group of related patterns that share benefits and drawbacks, adds in a few references to books and websites, and summarizes the ways those patterns can be transformed into other architectures. The format of these chapters is described in [[wiki/concepts/source/appendices/format-of-a-metapattern|Appendix F]]. The [[wiki/concepts/source/analytics/analytics|sixth part]] of the book is analytics – the fruits of the pattern classification explored in the earlier parts. Finally, there are [[wiki/concepts/source/appendices/appendices|appendices]]. [[wiki/concepts/source/appendices/books-referenced|Appendix B]] is the list of the books referenced, [[wiki/concepts/source/appendices/evolutions-of-architectures|Appendix E]] contains detailed evolutions of patterns, and [[wiki/concepts/source/appendices/index-of-patterns|Appendix I]] is the index of the patterns found in the book. ## Diagrams This book makes heavy use of diagrams to the extent that it can be treated as a kind of visual novel. As it is mostly made of patterns, and *no pattern is an island*, it must not be read sequentially – instead, the reader is advised to use the plentiful cross-links to open whatever (if any) content found to be intriguing and check the corresponding diagram. If it gets your attention, you may read the text below it. If you like the text, you may scroll up or down to see if there are more funny diagrams nearby. The diagrams are *NoUML* (boxes and arrows) and most of them belong to one of the following kinds: ![A structural, sequence, and dependency diagrams in NoSQL notation as used throughout the Architectural Metapatterns book.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Misc/Diagrams.png) Components in the diagrams are colored according to the kind of code they contain: - Use cases ([[wiki/concepts/source/basic-metapatterns/layers|application logic]]) are green. - Business rules ([[wiki/concepts/source/basic-metapatterns/layers|domain logic]]) are blue. - Generic code is white. - The data is gray. There are also non-code (external) components: - Users and clients resemble milk chocolate. - Hardware and the operating system are steel-colored. Please refer to the [[wiki/concepts/source/introduction/metapatterns|following chapter]] for the system of coordinates used in the diagrams. ## Notation - Pattern names are given in [[wiki/concepts/source/appendices/index-of-patterns|*Title Case Italics*]] and usually link to the pattern’s definition. - The first mention of a term or a name of a pattern component is *italicized*. - *Quotes and puns are in full italics*. - Book references are \[[wiki/concepts/source/appendices/books-referenced|[BRACKETED]]\] and link to the list of the books in Appendix B. - > Supplementary explanations are grayed-out. Many patterns match terms of the common language – indeed, as a pattern is a generalization of human experience, the more widespread a notion, the faster it is turned into a pattern. Such general-use terms, e.g. layers, services or pipeline, are usually not indicated in any way to preserve the overall readability. ## The architectural religions There are several schools of software architecture: 1. The believers in [SOLID](https://en.wikipedia.org/wiki/SOLID). 2. The followers of [nine qualities](https://iso25000.com/index.php/en/iso-25000-standards/iso-25010), [five views](https://en.wikipedia.org/wiki/4%2B1_architectural_view_model) and [as-many-as-one-gets certifications](https://en.wikipedia.org/wiki/Enterprise_architecture_framework#Types_of_enterprise_architecture_framework). 3. The aspirants to the [nameless](https://en.wikipedia.org/wiki/The_Timeless_Way_of_Building#Summary) way of [patterns](https://en.wikipedia.org/wiki/Software_design_pattern). In my opinion: 1. SOLID is a silver bullet that tends to produce a [*DDD*-layered kind of *Hexagonal Architecture*](https://herbertograca.com/2017/09/28/clean-architecture-standing-on-the-shoulders-of-giants/). It lacks the agility of pluralism found with evolutionary ecosystems. 2. Architectural frameworks are overcomplicated thus hard to understand and inflexible. 3. Patterns are like a kind of toolbox, the one which a mechanic is often seen carrying around. A skilled craftsman knows best uses of his tools, and can invent new instruments if something is missing in the standard toolset. However, the toolset’s size should be limited for the tools to be familiar to the practitioner and easily carried around. > There is a similarity between patterns and data structures. Even though you may know hundreds of specialized data structures, most software can be easily built with vectors, lists, and hash tables alone. Something like a [prefix tree](https://en.wikipedia.org/wiki/Trie) or [skip list](https://en.wikipedia.org/wiki/Skip_list) is a rare beast, but under certain circumstances it may save the day. In the same way, business logic can be written without any use of patterns, but the resulting code will likely be hard to maintain, and even its performance may suffer (see [*Spatial Partition*](https://gameprogrammingpatterns.com/spatial-partition.html) and [*Unit of Work*](https://martinfowler.com/eaaCatalog/unitOfWork.html) for patterns that drastically improve performance). It is likely that those approaches are best used with systems of different sizes: SOLID is aimed at stand-alone application design while the heavy frameworks and certifications suit distributed enterprise architectures. In such a worldview patterns span everything in between the two extremes. > Patterns of software architecture are abstract just like [Plato’s Ideas or Forms](https://en.wikipedia.org/wiki/Theory_of_forms) in philosophy or classes in object-oriented programming. There is only one instance of each given pattern, which is a general idea or a very high-level blueprint for every implementation of the pattern ever seen in the code. ## What’s wrong with patterns *Too much information is no information* or, as they say, *what is not remembered never existed*. There are literally thousands of patterns described for software and systems architectures. Nobody knows them all and nobody cares to know them all (if you say you do, you must have already read [the Pattern Languages of Programs archives](https://hillside.net/index.php/past-plop-conferences). Have you? Neither have I). Hundreds of patterns are generated yearly in just the conferences alone, not to mention the books and software engineering websites. Old patterns get [rebranded or forgotten and reinvented](https://datatracker.ietf.org/doc/html/rfc1925). This is especially true for the discrepancy between the pattern names in software architecture and systems architecture. The new [*N-tier*](https://en.wikipedia.org/wiki/Multitier_architecture) is just good old *Layers* under the hood, isn’t it? This undermines the original ideas which brought in the patterns hype: 1. *Patterns as a ubiquitous language*. Nowadays similar, if not identical, patterns bear different names, and some of them are too obscure to be ever heard of (see [the PLoP archives](https://hillside.net/index.php/past-plop-conferences)). 2. *Patterns as a vessel for knowledge transfer*. If an old pattern is reinvented or plagiarized, most of the old knowledge is lost. There is no continuity of experience. 3. *Pattern language as the ultimate architect’s tool*. As patterns are re-invented, so are pattern languages. At best, we have domain-specific or architecture-limited ([*DDD*](https://en.wikipedia.org/wiki/Domain-driven_design), [*Microservices*](https://microservices.io/patterns/index.html)) systems of patterns. There is no *single unified vision* which pattern enthusiasts of old promised to provide. Have we been fooled? ## TLDR Compare [[wiki/concepts/source/extension-metapatterns/proxy|*Firewall*]] and [[wiki/concepts/source/extension-metapatterns/proxy|*Response Cache*]]. Both represent a system to its users and implement generic aspects of the system’s behavior. Both are [[wiki/concepts/source/extension-metapatterns/proxy|*Proxies*]]. Take [[wiki/concepts/source/extension-metapatterns/orchestrator|*Saga Execution Component*]] and [[wiki/concepts/source/extension-metapatterns/orchestrator|*API Composer*]]. Both are high-level services that make a series of calls into an underlying system – they *orchestrate* it. Both are [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrators*]]. It’s that simple and stupid. We can classify architectural patterns. --- title: "Introduction" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Introduction/Introduction.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Introduction/Introduction source_license_note: "See namespace README; preserve attribution and source links." --- # Introduction > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Introduction/Introduction.md`. ## You’ll find inside - An atlas of [[wiki/concepts/source/introduction/system-topologies|system topologies]]. - A compendium of [architectural patterns](https://en.wikipedia.org/wiki/Architectural_pattern). - One of the largest and most cohesive [pattern languages](https://en.wikipedia.org/wiki/Pattern_language). ## Further chapters: - [[wiki/concepts/source/introduction/about-this-book|About this book]] - [[wiki/concepts/source/introduction/metapatterns|Metapatterns]] - [[wiki/concepts/source/introduction/system-topologies|System topologies]] --- title: "Metapatterns" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Introduction/Metapatterns.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Introduction/Metapatterns source_license_note: "See namespace README; preserve attribution and source links." --- # Metapatterns > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Introduction/Metapatterns.md`. Is there a way to bring the patterns into order? They are way too many, some obscure, others overly specialized. We can try. On a subset. And the subset should be: - *Important* enough to matter for the majority of programmers. - *Small* enough to fit in one’s memory or in a book. - *Complete* enough to assure that we don’t miss anything crucial. Is there such a set? I believe so. ## Architectural patterns \[[wiki/concepts/source/appendices/books-referenced|[POSA1]]\] defines three categories of patterns: - *Architectural patterns* which deal with the overall structure of a system and functions of its components. - *Design patterns* which describe relations between objects. - *Idioms* which provide abstractions on top of a given programming language. Architectural patterns are important by [definition](https://martinfowler.com/architecture/) (*Architecture is about the important stuff. Whatever that is*). Point 1 (*importance*) – checked. Any given system has an internal structure. When its developers talk about *architectural style* \[[wiki/concepts/source/appendices/books-referenced|[POSA1]]\] or draw structural diagrams that usually boils down to a composition of two or three well-known architectural patterns. Choosing architectural patterns as the subject of our study lets us feed on a large body of books and articles that describe similar designs over and over again. Moreover, as soon as a system no longer follows the latest fashions, it is widely advertised as a novelty (or its designers are labeled as old-fashioned and shortsighted), thus we may expect to have heard of nearly all of the architectures which are used in practice. Point 3 (*completeness*) – we have more than enough examples to analyze. To organize a set of patterns we rely on the concept of design space. ## Design space *Design space* \[[wiki/concepts/source/appendices/books-referenced|[POSA1]], [[wiki/concepts/source/appendices/books-referenced|POSA5]]\] is a model that allocates a dimension for each choice made while architecting the system. Thus it contains all the possible ways for a system to be designed. The only trouble – it is multidimensional, maybe infinite, and the dimensions will differ from system to system. There is a workaround – we can use a projection from the design space into a 2- or 3-dimensional space which we are more comfortable with. However, projection causes a loss of information. Counterintuitively, that is good for us – similar architectures that differ in minor details become identical as soon as the dimensions they differ in disappear. If we could only find 2 or 3 most important dimensions that apply equally well to each pattern in the set that we want to research, that is architectural patterns, which cover all the known system designs. ## Structure determines architecture Systems tend to have an internal structure. Those that don’t are derogatively called [*Big Balls of Mud*](http://www.laputan.org/mud/) for their peculiar properties. Structure is all about components, their roles and interactions. Many architectural styles, for example, [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] and [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]], are named after their structures, while others, like [[wiki/concepts/source/basic-metapatterns/pipeline|*Event-Driven Architecture*]], highlight some of its aspects, hinting that it is the structure which determines principal properties of a system. I am not the first person to reach such a conclusion. *Metapatterns* – clusters of patterns of similar structure – were [defined](https://softwareresearch.net/fileadmin/user_upload/Documents/publications/conference_proceedings/C010.pdf) shortly after the first collections of design patterns had appeared but they never made a lasting impact on software engineering. I believe that the approach was applied prematurely to analyze the \[[wiki/concepts/source/appendices/books-referenced|[GoF]]\] patterns, which make quite a random and incomplete subset of design patterns, resulting in an overgeneralization. I intend to plot structures of all the architectural patterns I encounter, group patterns of identical structure together into metapatterns, draw relations between the metapatterns, and maybe show how a system’s structure determines its properties. Quite an ambitious plan for a short book, isn’t it? Our set of architectural patterns is still not known to be complete, is not small and, moreover, the way structural diagrams are drawn differs from source to source – we cannot compare them unless we make up a universal system of coordinates. ## The system of coordinates Inventing a generic coordinate system to fit any pattern’s representation, from [*Iterator*](https://refactoring.guru/design-patterns/iterator) to [[wiki/concepts/source/basic-metapatterns/monolith|*Half-Sync/Half-Async*]], may be too hard, but we surely can find something for architectural patterns, as all of them share the scope, namely the system as a whole. Which dimensions an implementation of a system would usually be plotted along? 1. *Abstractness* – there are high-level use cases and low-level details. A single highly abstract operation unrolls into many lower-level ones: Python scripts run on top of a C runtime and assembly drivers; orchestrators call API methods of services, which themselves run SQL queries towards their databases which are full of low-level computations and disk operations. 2. *Subdomain* – any complex system manages multiple subdomains. An OS needs to deal with a variety of peripheral devices and protocols: a video card driver has very little resemblance to an HDD driver or to the TCP/IP stack. An enterprise has multiple departments, each operating a software that fits its needs. 3. *Sharding* – if several instances of a module are deployed, and that fact is an integral part of the architecture, we should represent the multiple instances on our structural diagram. We’ll draw the abstractness axis vertically with higher-level components positioned towards the upper side of the diagram, the subdomain axis horizontally, and sharding diagonally. Here is an (arbitrary) example of such a diagram: ![A diagram of a CQRS system in abstractness-subdomain-sharding coordinates with a detailed legend.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Intro/CQRS%20with%20notes.png) (A structural diagram for [[wiki/concepts/source/fragmented-metapatterns/layered-services|*CQRS*]], adapted from [Udi Dahan’s article](https://udidahan.com/2009/12/09/clarified-cqrs/), to introduce the notation) > Abstractness is usually inverse to the distance to the system’s clients. A graphical interface is highly abstract with its intuitive windows, forms, and scrollbars, and it is the part of the software which the users interact with. The opposite end of the system hosts device drivers which operate in cryptic bits and registers. Nevertheless, the reality is more complex: to draw UI windows on the screen the software still needs help from graphic card drivers deep inside the OS. Likewise, there are several layers of routing and proxies between a web page that you see in your browser and the server-side logic which that page allows you to access. Even though those intermediate layers are not highly abstract, we still draw them in the upper part of diagrams between a system and its clients to keep the diagrams [simple and stupid](https://en.wikipedia.org/wiki/KISS_principle). ## Map and reduce Now that we have the generic coordinates which seem to fit any architectural pattern, we can start mapping our set of architectural patterns into that coordinate system to reduce the multidimensional design space down to the few dimensions of structural diagrams which we were actually looking for. Then, after filtering out minor details, our hundred or so published patterns should yield a score of [[wiki/concepts/source/introduction/system-topologies|*topologies*]] – clusters of geometrically equivalent diagrams – just because there are very few simple systems that one can draw on a plane before repeating oneself. Each topology will represent an *architectural metapattern* – a generalization of architectural patterns of similar structure and function. Let’s return for a second to our requirements for classifying a set of patterns. The importance (point 1) of architectural patterns was proved before. The reasonable size of the resulting classification (point 2) is granted by the existence of only a few simple 2D or 3D shapes (topologies). The completeness of the analysis (point 3) comes from, on one hand, the geometrical approach which makes any blank spaces (possible geometries with no known patterns) obvious, and on the other – from the large sample of architectural patterns which we are classifying. Godspeed! ## An example of metapatterns Let’s consider the following structure: ![Two high-level components interact with one low-level component.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Intro/Example-Undefined.png) It features two (or more in real life) high-level modules that communicate with/via a lower-level module. Which patterns does it match? - [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] – a software that provides means of communication between other components. - [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Database*]] – a space for other components to store and exchange data. - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Model-View-Controller*]] – a platform-agnostic business logic with customized means of input and output. ![Diagrams for Services with a Middleware, Services with a shared database and Model-View-Controller.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Intro/Example-Defined.png) My idea of grouping patterns by structure seems to have backfired – we got three distinct patterns that have similar structural diagrams. The first two of them are related – both implement indirect communication, and their distinction is fading as a *Middleware* may feature a persistent storage for messages while a table in a *Shared Database* may be used to orchestrate services. The third one is very different – primarily because the bulk of its code, that is its *business logic*, resides in the lower layer, leaving the upper-level components a minor role. Notwithstanding, each of the patterns we found is a part of a distinct cluster: - *Middleware* is also known as *(Message) Broker* \[[wiki/concepts/source/appendices/books-referenced|[POSA1]], [[wiki/concepts/source/appendices/books-referenced|POSA4]], [[wiki/concepts/source/appendices/books-referenced|EIP]], [[wiki/concepts/source/appendices/books-referenced|MP]]\] and is an integral part of [[wiki/concepts/source/extension-metapatterns/middleware|*Message Bus*]] \[[wiki/concepts/source/appendices/books-referenced|[EIP]]\], [[wiki/concepts/source/extension-metapatterns/middleware|*Service Mesh*]] \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\], [[wiki/concepts/source/extension-metapatterns/middleware|*Event Mediator*]] \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\], [[wiki/concepts/source/extension-metapatterns/middleware|*Enterprise Service Bus*]] \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\], and [[wiki/concepts/source/extension-metapatterns/sandwich|*Space-Based Architecture*]] \[[wiki/concepts/source/appendices/books-referenced|[SAP]], [[wiki/concepts/source/appendices/books-referenced|FSA]]\]. - *Shared Database* is a kind of [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]] \[[wiki/concepts/source/appendices/books-referenced|[POSA4]]\] ([[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Memory*]], [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared File System*]]), and the foundation for [[wiki/concepts/source/extension-metapatterns/shared-repository|*Blackboard*]] \[[wiki/concepts/source/appendices/books-referenced|[POSA1]], [[wiki/concepts/source/appendices/books-referenced|POSA4]]\], [[wiki/concepts/source/extension-metapatterns/sandwich|*Space-Based Architecture*]] \[[wiki/concepts/source/appendices/books-referenced|[SAP]], [[wiki/concepts/source/appendices/books-referenced|FSA]]\], and [[wiki/concepts/source/extension-metapatterns/sandwich|*Service-Based Architecture*]] \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\]. - *Model-View-Controller* \[[wiki/concepts/source/appendices/books-referenced|[POSA1]], [[wiki/concepts/source/appendices/books-referenced|POSA4]]\] is a special kind of [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] (aka [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Ports and Adapters*]], [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Onion Architecture*, and *Clean Architecture*]]) which itself is derived from [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]] \[[wiki/concepts/source/appendices/books-referenced|[PEAA]]\] (*Addons*, *Plug-In Architecture* \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\], or the [[wiki/concepts/source/analytics/ambiguous-patterns|misnomer]] *Microkernel Architecture* \[[wiki/concepts/source/appendices/books-referenced|[SAP]], [[wiki/concepts/source/appendices/books-referenced|FSA]]\]). Our touching on a single topology revealed a web of twenty or so pattern names that spreads all around. With such a pace there is a hope of exploring the whole fabric which is known as *pattern language* \[[wiki/concepts/source/appendices/books-referenced|[GoF]], [[wiki/concepts/source/appendices/books-referenced|POSA1]], [[wiki/concepts/source/appendices/books-referenced|POSA2]], [[wiki/concepts/source/appendices/books-referenced|POSA5]]\]. There are three lessons to learn: - The distribution of business logic is a crucial aspect of topologies. - Metapatterns are interrelated in multiple ways, forming a pattern language. - Each metapattern includes several well-established patterns. ## What does that mean Chemistry has the [periodic table](https://en.wikipedia.org/wiki/Periodic_table). Biology has the [tree of life](https://en.wikipedia.org/wiki/Tree_of_life_(biology)). This book strives towards building something of that kind for software and systems architecture. You can say “That makes no sense! Chemistry and biology are empirical sciences while software architecture isn’t!” Is it? --- title: "System topologies" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Introduction/System topologies.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Introduction/System%20topologies source_license_note: "See namespace README; preserve attribution and source links." --- # System topologies > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Introduction/System topologies.md`. In the [[wiki/concepts/source/introduction/metapatterns|previous chapter]] we started with architectural patterns and grouped them in accordance with their structure and function into *metapatterns*. Now let’s traverse in the opposite direction: from *topology* (the structure of a system) to the patterns which describe it. We will draw and analyze a map of common system topologies and along the way outline the scope of this book. ## Methodology We will rely on our finding that any system has a characteristic representation in the [[wiki/concepts/source/introduction/metapatterns|abstractness-subdomain-sharding space]]. The amounts of a system’s partitioning along each of the three dimensions can be used as its coordinates on a map of system topologies: - *Abstractness* corresponds to the *technical partitioning* \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] – subdivision of a system into [[wiki/concepts/source/basic-metapatterns/layers|*layers*]] with different roles and technologies. Any use case will likely involve all the layers. - *Subdomain* represents the *domain partitioning* \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] – segregation of a system into [[wiki/concepts/source/basic-metapatterns/services|*modules* or *services*]] that encapsulate distinct parts of the business knowledge. A use case is often localized in one or two subdomains. - *Sharding* is about running multiple instances ([[wiki/concepts/source/basic-metapatterns/shards|*shards*]] or [[wiki/concepts/source/basic-metapatterns/shards|*replicas*]]) of a component. ![Technical partitioning into Layers, domain partitioning into Services, and multiple instances of a system.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Topologies/Partitioning.png) ### From theory to practice Having a distribution of architectures in a 3D space sounds great, but how do we represent it as human-readable media? There are at least the following issues: - Many system topologies feature similar levels of segregation into layers and services, meaning that they belong to the same neighborhood on the map. - It makes a difference whether a system’s business logic or its infrastructure is subdivided because business logic comprises the bulk of the code. Therefore we cannot estimate a system’s coordinates only from the number of layers or services it contains, which means that we are limited to something like “major layering” and “minor layering” instead of numeric values. - Sharding of many architectures varies widely and is hard to represent on a flat drawing. As a result, the following adjustments were necessary to make the map of system topologies comprehensible: - Sharding is omitted, transforming 3D coordinates into a flat map. Yes, much information is lost, but *too much information is no information*. Now the map is easier to read. - I took the liberty of shifting topologies from their real positions to resolve overlaps. - Even worse, I moved some of the topologies around to group similar architectures. - Exotic (e.g. [[wiki/concepts/source/implementation-metapatterns/mesh|*Leaf-Spine Architecture*]]) and duplicate (e.g. [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]]) topologies are omitted. ## The map of system topologies ![A map of system topologies arranged according to the amount of their partitioning into layers and services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Topologies/Topologies%20Map.png) The map contains only basic architectures which are easy to apprehend and name. Any complex system is very likely to be a combination of these simple topologies. I somewhat arbitrarily divided the map of system topologies into five partially overlapping regions: - [*Monolithic*](#monolithic-systems), where the bulk of the system is kept in a single component. - [*Layered*](#layered-architectures), with mostly technical partitioning and specialized components (drawn in one or two colors which correspond to different kinds of code). - [*Services*](#services-area) with domain partitioning, meaning that each of the main components includes several kinds of code. - [*Fragmented*](#fragmented-patterns) *systems* built of many smaller parts. - [*Plugins*](#plugins-family) that usually have a cohesive core and external modular layers. This grouping allows us to study the topologies piecemeal without getting lost in their numbers and features. ## Monolithic systems In the simplest cases a project is too small for any internal structure to be justified – you can code it in a couple of hours without any preliminary design. In other cases the domain is known to be so cohesive that you cannot find good module boundaries – any internal interfaces result in much boilerplate code or degrade performance. Or there is no time left for a thoughtful design! ### True Monoliths ![Diagrams of Monolith, Shards, and Replicas.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Topologies/True%20Monoliths.png) Few system topologies are truly monolithic with one kind of system components: - [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] keeps everything together in a single cohesive application which makes sense for small, one-off projects. A long-running *Monolith* may need to handle inputs and events, for which there are several options: - [[wiki/concepts/source/basic-metapatterns/monolith|*Reactor*]] uses a thread for each request and blocks on calls to the OS or other components. This is the simplest server-side implementation. - [[wiki/concepts/source/basic-metapatterns/monolith|*Proactor*]] relies on callbacks that all run in a single thread to achieve real-time latency and avoid locks. It is widely used in embedded programming. - [[wiki/concepts/source/basic-metapatterns/monolith|*Half-Sync/Half-Async*]] is an internally layered approach that allocates a coroutine or fiber to each task. It is more resource-efficient than [[wiki/concepts/source/basic-metapatterns/monolith|*Reactor*]] but lacks the real-time responsiveness and flexibility of [[wiki/concepts/source/basic-metapatterns/monolith|*Proactor*]]. - [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]] are multiple instances of a [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]], each owning a slice of the system’s data. A client must know which shard to access either through storing its address or by querying an [[wiki/concepts/source/extension-metapatterns/proxy|*Ambassador Proxy*]] library written by the team that deploys the shards. This is the architecture of choice when clients are independent from each other but the entire dataset is too large to fit in a single server. - [[wiki/concepts/source/basic-metapatterns/shards|*Replicas*]] are instances of a [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] with identical data used to achieve fault tolerance and high throughput. Any writes to one replica must be propagated to the other replicas: - With [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|semi-specialized replicas]] all write requests go to a single *leader* instance which publishes the changes for the other replicas, called *followers*, to apply to their datasets. Read requests usually go to the followers, and the more read traffic there is, the more followers are deployed. - If all the replicas are identical, any of them can handle a write request and publish the update for the other replicas to apply. This scales write throughput but involves the chance of data conflicts when the same data record is simultaneously changed on multiple replicas. See [[wiki/concepts/source/extension-metapatterns/shared-repository|*Data Grid* of *Space-Based Architecture*]]. ### Monoliths with auxiliary layers ![Diagrams of Monolith with Backends for Frontends, Managed Shards, Peer-to-Peer Mesh, Monolith with a database, and Monolith with Polyglot Persistence.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Topologies/Monoliths%20with%20Layers.png) In other kinds of systems, common in server-side programming, some functionality moves to a dedicated layer while the business logic remains monolithic: - [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] *with a database* relies on an external [[wiki/concepts/source/basic-metapatterns/layers|data storage component]] for persistence. - [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] *with* [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]] uses specialized databases to improve performance. - [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] *with* [*Backends for Frontends*]() employs a [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]] for each kind of client to address variations in the clients’ protocols and security. - *Managed* [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]] run behind a single [[wiki/concepts/source/extension-metapatterns/proxy|*Sharding Proxy*]] which connects each system’s client to the shard that has that client’s data thus isolating the clients from the knowledge of the system’s internal composition. - [[wiki/concepts/source/implementation-metapatterns/mesh|*Peer-to-Peer Mesh*]] interconnects multiple instances of an application, acting as a distributed [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]]. ### Monoliths with Plugins ![Diagrams of Monolith with Plugins, Model-View-Controller, and Hexagonal Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Topologies/Monoliths%20with%20Plugins.png) A monolithic core can be extended with disposable additions: - [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]] allow for parts of the core’s workflow to be supplied by internal or external teams, customizing the experience of the system’s users without modifications to its main code. - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Model-View-Controller* and related patterns]] provide a [[wiki/concepts/source/extension-metapatterns/proxy|*presentation layer*]] that isolates the main code from dependencies on the UI framework or network protocol, thus minimizing the effort of porting the software to another platform. - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] keeps the entire business logic self-sufficient by wrapping every dependency with a dedicated [[wiki/concepts/source/extension-metapatterns/proxy|*Adapter*]], which not only improves portability but also helps with testing and allows for changing vendors late in the development cycle. ### Underdeveloped Moduliths ![Diagrams of Monolith with libraries and Modulith with shared code.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Topologies/Underdeveloped%20Moduliths.png) If a [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] evolves for a long time, it will likely become segmented into subdomain components, yielding a [[wiki/concepts/source/basic-metapatterns/services|*Modulith*]]. As that process is not instantaneous, there are a couple of transitional architectures: - [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] *with* [[wiki/concepts/source/basic-metapatterns/layers|*libraries*]] involves subdomain-specific third-party components which are called by its cohesive business logic. - [[wiki/concepts/source/basic-metapatterns/services|*Modulith*]] *with shared code* has the business logic largely separated into subdomain modules which still rely on a common codebase for shared functionality. ## Layered architectures Layering enables the use of specialized technologies and third-party components while avoiding the [risky](https://martinfowler.com/bliki/MonolithFirst.html) subdivision of business logic. It also allows for parts of the system to [[wiki/concepts/source/foundations-of-software-architecture/forces-asynchronicity-and-distribution|differ in their qualities, placement, and scalability]]. All of that makes layered architectures suitable for full-featured, medium-sized projects run by one or two teams where both the speed of development and supportability matter. ### Ordinary Layers ![Diagrams of DDD-Style Layers, Layers with Polyglot Persistence, Layers with Backends for Frontends, and Monolith with a database.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Topologies/Ordinary%20Layers.png) Typical layered architectures include: - [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] of various composition, for example: - [[wiki/concepts/source/basic-metapatterns/layers|*Entity-Control-Boundary*]] which represent the [[wiki/concepts/source/basic-metapatterns/layers|*domain model*]], [[wiki/concepts/source/basic-metapatterns/layers|*use cases*]], and [[wiki/concepts/source/basic-metapatterns/layers|*interface*]], respectively. This pattern originated in the age of complex desktop applications. - [[wiki/concepts/source/basic-metapatterns/layers|*Domain-Driven Design* decomposition]] into *presentation* (interface), *application* (use cases), *domain* (business rules), and *infrastructure* (communication and persistence). It targets enterprise systems. - [[wiki/concepts/source/basic-metapatterns/layers|*Embedded systems*]] with pairs of UI \+ HMI, SDK \+ HAL, and FW \+ HW implemented by distinct parties in the supply chain. - [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] *with* [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]] where the [[wiki/concepts/source/basic-metapatterns/layers|*persistence*]] layer involves multiple databases, usually chosen for their performance with specialized payloads. - [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] *with* [*Backends for Frontends*]() with a dedicated [[wiki/concepts/source/basic-metapatterns/layers|*interface*]] and/or [[wiki/concepts/source/basic-metapatterns/layers|*application*]] component for each kind of client when the clients differ in their protocols and/or workflows. - [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] *with a* [[wiki/concepts/source/basic-metapatterns/layers|*database*]] as a case of rudimentary layering of server-side systems. ### Scaled Layers ![Diagrams of Three-Tier System, MapReduce, Managed Shards, Scaled Service, and Peer-to-Peer Mesh.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Topologies/Scaled%20Layers.png) Several layered architectures build around scalability: - [[wiki/concepts/source/basic-metapatterns/layers|*Three-Tier Architecture*]] contains a frontend layer with an instance per system’s user, scaled backend, and non-scaled database. It exploits the physical distribution of the system to reap [[wiki/concepts/source/foundations-of-software-architecture/forces-asynchronicity-and-distribution|cost, performance, and security benefits]]. - [[wiki/concepts/source/basic-metapatterns/services|*Scaled service*]] runs multiple instances of a stateless application between a [[wiki/concepts/source/extension-metapatterns/proxy|*Load Balancer*]], which evenly distributes user requests among the instances, and a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Database*]]. It is the default approach for scaling a server-side service. - [[wiki/concepts/source/extension-metapatterns/orchestrator|*MapReduce* or *Scatter-Gather*]] runs a coupled part of a calculation in a non-scaled layer while mutually independent parts are delegated to multiple worker shards. - *Managed* [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]] rely on a [[wiki/concepts/source/extension-metapatterns/proxy|*Sharding Proxy*]] layer to connect a client to the appropriate shard. This removes the need for the client to know which shard contains its data. - [[wiki/concepts/source/implementation-metapatterns/mesh|*Peer-to-Peer Mesh*]] builds a distributed [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] layer that interconnects instances of a client application. ### Other layered systems ![Diagrams of Model-View-Presenter, Onion Architecture, and Sandwich.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Topologies/Other%20Layered.png) Besides that, there are a few peculiar layered systems: - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Model-View-Presenter* family of patterns]] features layered user interfaces which decouple the main system from a GUI or web framework with the goal of being able to easily switch to another framework version or vendor. - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Onion Architecture* or *Clean Architecture*]] is a [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] (see [below](#hexagonal-architecture)) with a layered core structured along the ideas of [[wiki/concepts/source/basic-metapatterns/layers|*Domain-Driven Design*]]. - [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]] architectures are [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]] with the [[wiki/concepts/source/basic-metapatterns/layers|*domain logic* layer]] split into subdomains. It is a pragmatic low effort approach to tackle complexity in quickly evolving projects that can afford several development teams. It also addresses [[wiki/concepts/source/foundations-of-software-architecture/programming-and-architectural-paradigms|data-centric]] domains. ## Plugins family Some architectures specialize in separating complex core logic from miscellaneous details to make the *core* independent and reusable under changing conditions. In most cases the core contains monolithic business logic but that may vary among patterns. This family of topologies is prevalent in long-living or highly customizable products whose codebases are too expensive to rewrite to address every trend or fad. ### Plugin Architecture ![A plugin, library, and extension called by a core.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Topologies/Plugin%20Architecture.png) [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]] are external components which supply predefined parts of a host component’s workflow. They may be created by the company that makes the product, often for the sake of selling several flavors with limited or specialized functionality. Or they may come from external programmers, as codecs in video players or customizations for accounting software, to extend the usefulness of a product without overburdening its core codebase. ### Separated Presentation ![Diagrams of Model-View-Presenter and Model-View-Controller.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Topologies/Separated%20Presentation.png) [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Separated Presentation*]] extracts the [[wiki/concepts/source/basic-metapatterns/layers|user or network interface]] functionality into a dedicated layer which is often further subdivided. This makes the main codebase reusable in different environments: - The [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Model-View-Controller* family of patterns]] has separate modules for platform-specific input and for output which is beneficial when there is no web or GUI framework that can provide a unified high-level user interface. - The [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Model-View-Presenter* family]] builds on top of a pre-existing platform-specific [[wiki/concepts/source/basic-metapatterns/layers|presentation layer]]. Most of these patterns add an intermediate [[wiki/concepts/source/extension-metapatterns/proxy|*Adapter*]] between the platform-dependent code and the core application. ### Control patterns ![Diagrams of Pedestal and Microkernel.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Topologies/Control%20Patterns.png) A couple of topologies originate with embedded or systems programming where it is important to abstract the business logic from the hardware components which tend to quickly go out of production and thus need to be replaced with incompatible models: - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Pedestal*]] wraps each hardware component in a system with a dedicated driver to reduce the dependency of the business logic on hardware specifications thus allowing for the software to be reused with different hardware setups. - [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel Architecture*]] relies on an eponymous layer to mediate between resource consumers and resource producers which implement generic interfaces and thus are replaceable. This approach is surprisingly ubiquitous: - [[wiki/concepts/source/implementation-metapatterns/microkernel|*Operating systems*]] are the origin of [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]], with user space applications competing for system resources owned by the device drivers. - [[wiki/concepts/source/implementation-metapatterns/microkernel|*Interpreters*]] run user scripts in a sandbox and provide them access to installed libraries. - [[wiki/concepts/source/implementation-metapatterns/microkernel|*Software frameworks*]] follow a similar approach, building a [*Facade*](https://refactoring.guru/design-patterns/facade) to grant user code a managed access to the framework’s internal components. - [[wiki/concepts/source/implementation-metapatterns/microkernel|*Hypervisors*, *Virtualizers*, and *Distributed Runtimes*]] abstract a guest operating system or applications from the platform they run on. ### Hexagonal Architecture ![Diagrams of Ports and Adapters and Onion Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Topologies/Hexagonal%20Architecture.png) A few architectures fully isolate business logic from its environment, resulting in great portability, simpler automated testing and improved separation of concerns: - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Ports and Adapters*]] (the original [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]]) inserts an [[wiki/concepts/source/extension-metapatterns/proxy|*Adapter*]] into every communication pathway in or out of its business logic core but does not specify the structure of the core itself, which makes the pattern universally applicable. - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Onion Architecture* or *Clean Architecture*]] structures the core in accordance with the [[wiki/concepts/source/basic-metapatterns/layers|rules of *Domain-Driven Design*]], limiting the applicability of this topology to enterprise systems or complex backends. ### Cell ![Several intercommunicating subservices are wrapped with a cell gateway that receives client requests, adapters for outgoing communication, and a plugin.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Variants/4/Cell.png) [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cell*]] is a building block of huge systems that follow [*Domain-Oriented Microservice Architecture*]() or [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Cell-Based Architecture*]]. It is a kind of [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]] with a modular and often distributed core. The internals of a *Cell* are hidden behind a [[wiki/concepts/source/extension-metapatterns/proxy|*Cell Gateway*]] which implements the *Cell*’s public interface. Any outgoing communication, initiated from inside the *Cell*, goes through its [[wiki/concepts/source/extension-metapatterns/proxy|*Adapters*]] or through [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins* supplied by peer *Cells*]]. ## Services area Partitioning a system into [[wiki/concepts/source/basic-metapatterns/services|*modules*]] or [[wiki/concepts/source/basic-metapatterns/services|*services*]] which match its subdomains and assigning the components to dedicated teams greatly reduces the cognitive load that the programmers face as each person needs to comprehend only the service they work on. Given that it is [cognitive load that determines development speed](https://realmensch.org/2018/05/04/we-are-all-10x-developers/), most large projects have service-based topologies. However, full domain partitioning \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] is [[wiki/concepts/source/foundations-of-software-architecture/modules-and-complexity|beneficial only when the system’s subdomains are weakly coupled]] along every level of their functionality, which is why many real-world topologies mix cohesive system-wide layers and decoupled subdomain services. ### Barebone services ![Diagrams of Services, Three-Layered Services, Pipeline, and Two-Layered Services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Topologies/Barebone%20Services.png) A few architectures are completely segmented into subdomains: - Distributed [[wiki/concepts/source/basic-metapatterns/services|*Services*]] or in-process [[wiki/concepts/source/basic-metapatterns/services|*Modules*]] rely on [[wiki/concepts/source/foundations-of-software-architecture/orchestration|*mutual orchestration*]]. They come in several kinds: - [[wiki/concepts/source/basic-metapatterns/services|*Service-Based Architecture*]] tends to employ single instances of services which cover entire subdomains. It is used for multi-team server-side projects with no special performance considerations. - [[wiki/concepts/source/basic-metapatterns/services|*Modulith*]] (*Modular Monolith*) runs subdomain-sized components in a single process, sacrificing fault tolerance for consistency and operational costs. This architecture fits smaller Internet businesses. - [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]] with highly scalable sub-subdomain components implement high load and high budget systems with well-established domain knowledge but frequently changing business needs. - [[wiki/concepts/source/basic-metapatterns/services|*Actors*]] are asynchronous objects used for real-time tasks that range from embedded telephony to instant messengers to financial systems. Consider them if you benefit from modeling every user of your system as a lightweight independently acting entity. - [[wiki/concepts/source/fragmented-metapatterns/layered-services|*Three-Layered Services*]] subdivide each service into the [[wiki/concepts/source/basic-metapatterns/layers|*use cases*]], [[wiki/concepts/source/basic-metapatterns/layers|*domain logic*]], and [[wiki/concepts/source/basic-metapatterns/layers|*persistence*]] layers, allowing for further specialization of staff and technologies. - [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]] is a [[wiki/concepts/source/foundations-of-software-architecture/choreography|*choreographed*]] system where each component implements a single stage of data or event processing: - [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipes and Filters*]] is a local and usually linear [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]] that processes a data stream. It is the architecture of choice for systems with customizable workflows and polymorphic algorithms such as video capture or replay. - [[wiki/concepts/source/basic-metapatterns/pipeline|*Choreographed Event-Driven Architecture*]] runs multiple branched [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipelines*]], each implementing a single use case, over a shared set of services. It is an easily extendable alternative to [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]] for domains with a few highly loaded yet simple scenarios. - [[wiki/concepts/source/basic-metapatterns/pipeline|*Data Mesh*]] collects, transforms, and processes analytical data from a system of services. - [[wiki/concepts/source/fragmented-metapatterns/layered-services|*Two-Layered Services*]] split each component of a [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]] (usually a [[wiki/concepts/source/basic-metapatterns/pipeline|*Choreographed Event-Driven Architecture*]]) into [[wiki/concepts/source/basic-metapatterns/layers|*domain logic*]] and [[wiki/concepts/source/basic-metapatterns/layers|*persistence*]] layers, emphasizing the use of databases private to their services. Noticeably, the [[wiki/concepts/source/basic-metapatterns/layers|*use case logic*]] is present only through the connections between the services. ### Services with extensions ![Diagrams of Services with a Gateway; Orchestrated Services; Services with: an API Gateway, Backends for Frontends, Shared Repository, Middleware, and Pplyglot Persistence; and of Service Mesh.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Topologies/Services%20with%20Extensions.png) [[wiki/concepts/source/basic-metapatterns/services|*Services*]] become simpler when common aspects are extracted to a dedicated layer: - [[wiki/concepts/source/basic-metapatterns/services|*Services*]] *with a* [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] rely on an external [[wiki/concepts/source/basic-metapatterns/layers|transport and deployment layer]] which is usually a framework available off-the-shelf: - [[wiki/concepts/source/extension-metapatterns/middleware|*Service Mesh*]] is a distributed [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] for highly scalable systems. - [[wiki/concepts/source/extension-metapatterns/middleware|*Message Bus*]] interconnects services that use different communication technologies by translating between their protocols. It is useful in integration of legacy systems. - [[wiki/concepts/source/extension-metapatterns/middleware|*Event Mediator*]] drives communication in [[wiki/concepts/source/basic-metapatterns/pipeline|*Event-Driven Architectures*]]. - [[wiki/concepts/source/extension-metapatterns/middleware|*Enterprise Service Bus*]] is an [[wiki/concepts/source/foundations-of-software-architecture/orchestration|*orchestrating*]] [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] that unites several historically separate subsystems into an [*Enterprise Service-Oriented Architecture*](). - [[wiki/concepts/source/basic-metapatterns/services|*Services*]] *with a* [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]] share a [[wiki/concepts/source/basic-metapatterns/layers|data storage or exchange layer]] and are eligible to implement [[wiki/concepts/source/foundations-of-software-architecture/programming-and-architectural-paradigms|data-centric]] domains: - [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Database*]] simplifies architectural design and makes data synchronization trivial (see [[wiki/concepts/source/extension-metapatterns/sandwich|*Service-Based Architecture*]]). - [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared File System*]] is among the simplest methods of organizing [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipelines*]] for processing large volumes of data records. - [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Memory*]] is the fastest method of data exchange especially suitable for low latency software. - [[wiki/concepts/source/extension-metapatterns/shared-repository|*Data Grid*]] is a highly scalable, distributed in-memory data store of [[wiki/concepts/source/extension-metapatterns/sandwich|*Space-Based Architecture*]]. - [[wiki/concepts/source/basic-metapatterns/services|*Services*]] *with* [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]] employ several data stores, usually to improve performance by using each data store in the role it is optimized for. - [[wiki/concepts/source/basic-metapatterns/services|*Services*]] *with a* [[wiki/concepts/source/extension-metapatterns/proxy|*Gateway*]] rely on a shared [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]] layer to handle [[wiki/concepts/source/basic-metapatterns/layers|communication with clients]]. Third-party *Proxies* reliably cover security and networking concerns with very little effort from the programmers’ side. - In [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrated Services*]] it is the [[wiki/concepts/source/basic-metapatterns/layers|*use cases*]] which are extracted into a system-wide layer. Such subdivision of business logic saves the day when there are many complex system-wide scenarios while the business rules are specific to particular subdomains. - [[wiki/concepts/source/basic-metapatterns/services|*Services*]] *with an* [[wiki/concepts/source/extension-metapatterns/proxy|*API Gateway*]] implement public-API-related tasks – both [[wiki/concepts/source/basic-metapatterns/layers|protocol support]] and [[wiki/concepts/source/basic-metapatterns/layers|basic orchestration]] – in a single component which calls underlying services with the [[wiki/concepts/source/basic-metapatterns/layers|domain logic]]. This is a simplified architecture for ordinary server-side systems. - [[wiki/concepts/source/basic-metapatterns/services|*Services*]] *with* [*Backends for Frontends*]() have a layer of client-specific components that encapsulate clients’ [[wiki/concepts/source/basic-metapatterns/layers|protocols]] and/or [[wiki/concepts/source/basic-metapatterns/layers|scenarios]] and are useful when a system serves drastically different kinds of clients. ### Hierarchies of services ![Diagrams of Cell-Based Architecture and Hierarchical Middleware.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Topologies/Hierarchies%20of%20Services.png) Services are building blocks for a couple of hierarchical architectures used in huge projects: - [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Cell-Based Architecture*]] is a system of clusters of (often co-deployed) services called [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Cells*]]. Recursive decomposition lowers the top-level system complexity and decouples the subdomains by making their interdependencies explicit. - [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchical Middleware*]] interconnects several subsystems of services which belong to different organizations or physical networks. ### Partially merged services ![Diagrams of Sandwich and Modulith with shared code.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Topologies/Partially%20Merged%20Services.png) There are systems in-between [[wiki/concepts/source/basic-metapatterns/services|*Services*]] and [[wiki/concepts/source/basic-metapatterns/monolith|*Monolith*]] or [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]]: - In [[wiki/concepts/source/basic-metapatterns/services|*Modulith*]] *with shared code* the business logic is split into subdomains but still relies on a shared codebase. It is a transitional architecture often seen in growing projects that [explore subdomain boundaries](https://martinfowler.com/bliki/MonolithFirst.html). - In [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]] only the [[wiki/concepts/source/basic-metapatterns/layers|*domain logic* layer]], which is usually the largest part of the codebase, is segmented into subdomains. This is the most natural subdivision for many real-world systems, which inspires multiple architectures: - [[wiki/concepts/source/extension-metapatterns/sandwich|*Service-Based Architecture*]] – the pragmatic approach to server-side development – often uses a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Database*]] and an [[wiki/concepts/source/extension-metapatterns/orchestrator|*API Gateway*]]. - [[wiki/concepts/source/extension-metapatterns/sandwich|*Space-Based Architecture*]] provides unparalleled elasticity and scalability for data-centric domains with its *replicated cache* called [[wiki/concepts/source/extension-metapatterns/shared-repository|*Data Grid*]]. - [[wiki/concepts/source/extension-metapatterns/sandwich|*Blackboard Architecture*]] schedules specialized algorithms to solve ill-structured problems. - [[wiki/concepts/source/extension-metapatterns/sandwich|*Nanoservices*]] are independently scalable functions that run in a cloud and share an [[wiki/concepts/source/extension-metapatterns/orchestrator|*(API) Gateway*]] and a [[wiki/concepts/source/extension-metapatterns/shared-repository|database]]. ## Fragmented patterns Finally, some architectures are subdivided into both layers of abstraction and subdomains, resulting in topologies containing many small components. This happens when interacting parts of a system vary in their qualities and technologies and thus should stay separate, ordinary decomposition results in components too large for comfortable development, or both. ### Layers of services ![Diagrams of Services with Polyglot Persistence, Services with Backends for Frontends, and Service-Oriented Architecture.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Topologies/Layers%20of%20Services.png) A few topologies are made of layers, each of which is subdivided into services: - In [[wiki/concepts/source/basic-metapatterns/services|*Services*]] *with* [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]] there are several specialized data stores with shared access. This topology may emerge from a [[wiki/concepts/source/appendices/evolutions-of-a-shared-repository|performance optimization]] of [[wiki/concepts/source/basic-metapatterns/services|*Services*]] *with a* [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]]. - [[wiki/concepts/source/basic-metapatterns/services|*Services*]] *with* [*Backends for Frontends*]() employ a dedicated [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]], [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]], or [[wiki/concepts/source/extension-metapatterns/orchestrator|*API Gateway*]] for each kind of client. This makes sense when the system's clients have very little in common. - [*Service-Oriented Architecture*]() features fragmented application, domain, and utility layers, with each component of a higher level calling multiple components from a layer below it. It enables code reuse, for better or worse, and has reasonably small services even in huge projects but suffers from slow development caused by extensive interdependencies between teams. ### Layered services ![Diagrams of Orchestrated Three-Layered Services and Choreographed Two-Layered Services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Topologies/Layered%20Services.png) More often than not, services are layered internally: - [[wiki/concepts/source/fragmented-metapatterns/layered-services|*Orchestrated Three-Layered Services*]] distinguish between the [[wiki/concepts/source/basic-metapatterns/layers|*application*]] (use cases), [[wiki/concepts/source/basic-metapatterns/layers|*domain*]] (business rules), and [[wiki/concepts/source/basic-metapatterns/layers|*persistence*]] (database) layers. - [[wiki/concepts/source/fragmented-metapatterns/layered-services|*Choreographed Two-Layered Services*]] contain only the [[wiki/concepts/source/basic-metapatterns/layers|*domain*]] and [[wiki/concepts/source/basic-metapatterns/layers|*persistence*]] layers because the [[wiki/concepts/source/basic-metapatterns/layers|*application*]] logic resides in the graph of connections between the services. ### Hierarchies ![Diagrams of Orchestrator of Orchestrators, Middleware of Middlewares, and Services of Services.](/pixi-wiki/wiki/software-architecture-metapatterns/assets/images/Relations/Hierarchy.png) Finally, there are [[wiki/concepts/source/fragmented-metapatterns/hierarchy|hierarchical]] topologies with recursive partitioning: - [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Top-Down Hierarchy*]] is arguably the best way to implement a system that involves many kinds of somewhat related entities. It emerges in domains as diverse as compilers, industrial automation, graphical user interfaces, and online marketplaces. - [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchical Middleware*]] interconnects subsystems that differ in their communication protocols. - [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Cell-Based Architecture*]] splits every large subdomain service into a group of subservices encapsulated with a [[wiki/concepts/source/extension-metapatterns/proxy|*Cell Gateway*]]. This keeps individual services small without spreading hundreds of them into the system level. ## Common motifs Every area of the topologies map highlights certain design principles: - Small and simple systems may stay cohesive as [[wiki/concepts/source/basic-metapatterns/monolith|*Monoliths*]] or [[wiki/concepts/source/basic-metapatterns/shards|*Shards*]]. - Medium-sized software benefits from functional partitioning \[[wiki/concepts/source/appendices/books-referenced|[FSA]]\] into [[wiki/concepts/source/basic-metapatterns/layers|*Layers*]]. - Long-lived projects become stabilized by extracting any volatile code into expendable modules. Different applications of this principle yield [[wiki/concepts/source/implementation-metapatterns/plugins|*Plugins*]], [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|*Hexagonal Architecture*]], and [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]]. - Large software is decomposed into subdomains owned by dedicated teams. See [[wiki/concepts/source/basic-metapatterns/services|*Services*]] and [[wiki/concepts/source/basic-metapatterns/pipeline|*Pipeline*]]. - Huge systems require recursive decomposition as found in [*Service-Oriented Architecture*]() and [[wiki/concepts/source/fragmented-metapatterns/hierarchy|*Hierarchy*]]. Other motifs are harder to notice as they apply to both scaled layered systems and those subdivided into services: - There is often a *managing layer* that makes use of underlying components: - A [[wiki/concepts/source/extension-metapatterns/proxy|*Proxy*]] is an [[wiki/concepts/source/basic-metapatterns/layers|*interface*]] that receives and pre-processes client input, then forwards the resulting request to whatever is behind it. - An [[wiki/concepts/source/extension-metapatterns/orchestrator|*Orchestrator*]] is an [[wiki/concepts/source/basic-metapatterns/layers|*application*]] that implements complex use cases which turn a single event or client request into a chain of calls to the lower layer. - [*Backends for Frontends*]() segment a managing layer into client-specific services. - A *platform layer* provides some functionality to other system components: - A [[wiki/concepts/source/extension-metapatterns/middleware|*Middleware*]] deploys and [[wiki/concepts/source/basic-metapatterns/layers|interconnects]] [[wiki/concepts/source/basic-metapatterns/services|*Services*]] or [[wiki/concepts/source/basic-metapatterns/shards|*Replicas*]]. - A [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]] stores the system’s [[wiki/concepts/source/basic-metapatterns/layers|data]], offering consistency and persistence. - [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]] subdivides a [[wiki/concepts/source/extension-metapatterns/shared-repository|*Shared Repository*]] layer. - [[wiki/concepts/source/extension-metapatterns/sandwich|*Sandwich*]] wraps [[wiki/concepts/source/basic-metapatterns/services|*Services*]] or [[wiki/concepts/source/basic-metapatterns/shards|*Replicas*]] with both managing and platform layers. - A [[wiki/concepts/source/implementation-metapatterns/mesh|*Mesh*]] interconnects any components that use it. As we see, [[wiki/concepts/source/introduction/metapatterns|metapatterns]] emerge as archetypes shared among system topologies. ## Summary There are many system topologies with various degrees of segregation into layers and subdomains. No single architecture is a silver bullet, each topology has its use depending on the circumstances. The following chapters of this book explore archetypes shared among topologies which are called *metapatterns*. --- title: "Source Wiki Home" created: 2026-07-02 updated: 2026-07-02 type: source-page status: imported namespace: software-architecture-metapatterns source_repository: https://github.com/denyspoltorak/metapatterns source_wiki: https://github.com/denyspoltorak/metapatterns/wiki source_path: "Home.md" source_url: https://github.com/denyspoltorak/metapatterns/wiki/Home source_license_note: "See namespace README; preserve attribution and source links." --- # Source Wiki Home > Imported source page from Denys Poltorak's *Architectural Metapatterns* wiki. Source path: `Home.md`. # Architectural Patterns Wiki This site has multiple goals: 1. #### Become a wiki of architectural patterns There are many patterns here. I invested over a year in collecting architectural patterns and styles that relate to the structure of a system. You will find here [[wiki/concepts/source/basic-metapatterns/services|*Microservices*]] and [[wiki/concepts/source/implementation-metapatterns/microkernel|*Microkernel*]] but not [*Test-Driven Development*](https://herbertograca.com/2018/08/27/distillation-of-tdd-where-did-it-all-go-wrong/) (which is a methodology pattern) or [*Dead Letter Channel*](https://www.enterpriseintegrationpatterns.com/patterns/messaging/DeadLetterChannel.html) (a communication pattern). 2. #### Show that patterns make a hierarchy There are way too many patterns. Thus some of them are similar, as there are only so many different things a human mind can invent. This wiki groups patterns according to their structure and function, and describes specific features, use cases, benefits and drawbacks for each group of patterns as a whole to outline the big picture and avoid repetition. For example, [[wiki/concepts/source/extension-metapatterns/orchestrator|*Scatter-Gather* and *MapReduce*]] are almost identical (*Scatter-Gather* lacks the Reduce step) while their relation to [[wiki/concepts/source/extension-metapatterns/orchestrator|*Saga*]] is more remote but still traceable (each of the three patterns coordinates components of a system). 3. #### Provide an intuitive way to study patterns Having patterns arranged into a hierarchy greatly reduces the amount of information one must remember to learn a new pattern as it differs from others in its group in minor details. Also, if we can enumerate patterns and see how they relate to each other, then we can [[wiki/concepts/source/analytics/comparison-of-architectural-patterns|compare them]] and even spot empty spaces in our knowledge - which is how I discovered [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|*Polyglot Persistence*]]. Places to start: * [[wiki/concepts/source/appendices/index-of-patterns|Index of patterns]] * [[wiki/concepts/source/introduction/about-this-book|Diagrams and notation]] * [[wiki/concepts/source/introduction/metapatterns|The underlying theory]] You can download this as a book from [GitHub](https://github.com/denyspoltorak/metapatterns) or [Leanpub](https://leanpub.com/metapatterns) (which also features a few testimonials and a detailed table of contents). The work is licensed under [Creative Commons Attribution 4.0 International](https://creativecommons.org/licenses/by/4.0/). Any help with the content, including adding new patterns or correcting the ones already described, is appreciated. ## Table of Contents: ### [[wiki/concepts/source/introduction/introduction|Introduction]] - [[wiki/concepts/source/introduction/about-this-book|About this book]] - [[wiki/concepts/source/introduction/metapatterns|Metapatterns]] - [[wiki/concepts/source/introduction/system-topologies|System topologies]] ### [[wiki/concepts/source/foundations-of-software-architecture/foundations-of-software-architecture|Foundations of software architecture]] - [[wiki/concepts/source/foundations-of-software-architecture/modules-and-complexity|Modules and complexity]] - [[wiki/concepts/source/foundations-of-software-architecture/forces-asynchronicity-and-distribution|Forces, asynchronicity, and distribution]] - [[wiki/concepts/source/foundations-of-software-architecture/four-kinds-of-software|Four kinds of software]] - [[wiki/concepts/source/foundations-of-software-architecture/arranging-communication|Arranging communication]] - [[wiki/concepts/source/foundations-of-software-architecture/programming-and-architectural-paradigms|Programming and architectural paradigms]] - [[wiki/concepts/source/foundations-of-software-architecture/orchestration|Orchestration]] - [[wiki/concepts/source/foundations-of-software-architecture/choreography|Choreography]] - [[wiki/concepts/source/foundations-of-software-architecture/shared-data|Shared data]] - [[wiki/concepts/source/foundations-of-software-architecture/comparison-of-communication-styles|Comparison of communication styles]] ### [[wiki/concepts/source/basic-metapatterns/basic-metapatterns|Basic metapatterns]] - [[wiki/concepts/source/basic-metapatterns/monolith|Monolith]] - [[wiki/concepts/source/basic-metapatterns/shards|Shards]] - [[wiki/concepts/source/basic-metapatterns/layers|Layers]] - [[wiki/concepts/source/basic-metapatterns/services|Services]] - [[wiki/concepts/source/basic-metapatterns/pipeline|Pipeline]] ### [[wiki/concepts/source/extension-metapatterns/extension-metapatterns|Extension metapatterns]] - [[wiki/concepts/source/extension-metapatterns/middleware|Middleware]] - [[wiki/concepts/source/extension-metapatterns/shared-repository|Shared Repository]] - [[wiki/concepts/source/extension-metapatterns/proxy|Proxy]] - [[wiki/concepts/source/extension-metapatterns/orchestrator|Orchestrator]] - [[wiki/concepts/source/extension-metapatterns/sandwich|Sandwich]] ### [[wiki/concepts/source/fragmented-metapatterns/fragmented-metapatterns|Fragmented metapatterns]] - [[wiki/concepts/source/fragmented-metapatterns/layered-services|Layered Services]] - [[wiki/concepts/source/fragmented-metapatterns/polyglot-persistence|Polyglot Persistence]] - [Backends for Frontends (BFF)]() - [Service-Oriented Architecture (SOA)]() - [[wiki/concepts/source/fragmented-metapatterns/hierarchy|Hierarchy]] ### [[wiki/concepts/source/implementation-metapatterns/implementation-metapatterns|Implementation metapatterns]] - [[wiki/concepts/source/implementation-metapatterns/plugins|Plugins]] - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Hexagonal Architecture]] - [[wiki/concepts/source/implementation-metapatterns/microkernel|Microkernel]] - [[wiki/concepts/source/implementation-metapatterns/mesh|Mesh]] ### [[wiki/concepts/source/analytics/analytics|Analytics]] - [[wiki/concepts/source/analytics/comparison-of-architectural-patterns|Comparison of architectural patterns]] - [[wiki/concepts/source/analytics/sharing-functionality-or-data-among-services|Sharing functionality or data among services]] - [[wiki/concepts/source/analytics/pipelines-in-architectural-patterns|Pipelines in architectural patterns]] - [[wiki/concepts/source/analytics/dependency-inversion-in-architectural-patterns|Dependency inversion in architectural patterns]] - [[wiki/concepts/source/analytics/indirection-in-commands-and-queries|Indirection in commands and queries]] - [[wiki/concepts/source/analytics/ambiguous-patterns|Ambiguous patterns]] - [[wiki/concepts/source/analytics/architecture-and-product-life-cycle|Architecture and product life cycle]] - [[wiki/concepts/source/analytics/real-world-inspirations-for-architectural-patterns|Real-world inspirations for architectural patterns]] - [[wiki/concepts/source/analytics/the-heart-of-software-architecture|The heart of software architecture]] - [[wiki/concepts/source/analytics/cohesers-and-decouplers|Cohesers and decouplers]] - [[wiki/concepts/source/analytics/deconstructing-patterns|Deconstructing patterns]] - [[wiki/concepts/source/analytics/choose-your-own-architecture|Choose your own architecture]] ### [[wiki/concepts/source/appendices/appendices|Appendices]] - [[wiki/concepts/source/appendices/acknowledgements|Acknowledgements]] - [[wiki/concepts/source/appendices/books-referenced|Books referenced]] - [[wiki/concepts/source/appendices/copyright|Copyright]] - [[wiki/concepts/source/appendices/disclaimer|Disclaimer]] - [[wiki/concepts/source/appendices/evolutions-of-architectures|Evolutions of architectures]] - [[wiki/concepts/source/appendices/evolutions-of-a-monolith-that-lead-to-shards|Evolutions of a Monolith that lead to Shards]] - [[wiki/concepts/source/appendices/evolutions-of-a-monolith-that-result-in-layers|Evolutions of a Monolith that result in Layers]] - [[wiki/concepts/source/appendices/evolutions-of-a-monolith-that-make-services|Evolutions of a Monolith that make Services]] - [[wiki/concepts/source/appendices/evolutions-of-a-monolith-that-rely-on-plugins|Evolutions of a Monolith that rely on Plugins]] - [[wiki/concepts/source/appendices/evolutions-of-shards-that-share-data|Evolutions of Shards that share data]] - [[wiki/concepts/source/appendices/evolutions-of-shards-that-share-logic|Evolutions of Shards that share logic]] - [[wiki/concepts/source/appendices/evolutions-of-layers-that-make-more-layers|Evolutions of Layers that make more layers]] - [[wiki/concepts/source/appendices/evolutions-of-layers-that-help-large-projects|Evolutions of Layers that help large projects]] - [[wiki/concepts/source/appendices/evolutions-of-layers-to-improve-performance|Evolutions of Layers to improve performance]] - [[wiki/concepts/source/appendices/evolutions-of-layers-to-gain-flexibility|Evolutions of Layers to gain flexibility]] - [[wiki/concepts/source/appendices/evolutions-of-services-that-restructure-services|Evolutions of Services that restructure services]] - [[wiki/concepts/source/appendices/evolutions-of-services-that-add-layers|Evolutions of Services that add layers]] - [[wiki/concepts/source/appendices/evolutions-of-a-pipeline|Evolutions of a Pipeline]] - [[wiki/concepts/source/appendices/evolutions-of-a-middleware|Evolutions of a Middleware]] - [[wiki/concepts/source/appendices/evolutions-of-a-shared-repository|Evolutions of a Shared Repository]] - [[wiki/concepts/source/appendices/evolutions-of-a-proxy|Evolutions of a Proxy]] - [[wiki/concepts/source/appendices/evolutions-of-an-orchestrator|Evolutions of an Orchestrator]] - [[wiki/concepts/source/appendices/evolutions-of-a-sandwich|Evolutions of a Sandwich]] - [[wiki/concepts/source/appendices/format-of-a-metapattern|Format of a metapattern]] - [[wiki/concepts/source/appendices/glossary|Glossary]] - [[wiki/concepts/source/appendices/history-of-changes|History of changes]] - [[wiki/concepts/source/appendices/index-of-patterns|Index of patterns]] --- title: "Software Architecture Metapatterns — Master Index" created: 2026-07-02 updated: 2026-07-02 type: index status: compiled namespace: software-architecture-metapatterns --- # Software Architecture Metapatterns — Master Index > Compiled index for `software-architecture-metapatterns`. ## Agent Entrypoints - [[summaries/for-agents-software-architecture-retrieval|For Agents — Software Architecture Retrieval]] — Retrieval and comparison contract for architecture reasoning. - [[syntheses/architecture-metapatterns-fit-for-pixi|Architecture Metapatterns — Fit for Pixi]] — Why this corpus belongs in Pixi Wiki under Knowledge Systems. - [[summaries/license-and-provenance|License and Provenance]] — Source roots, license ambiguity, and attribution guardrails. ## Imported Corpus Imported source Markdown pages: 79. ### Source Sections - `analytics`: 13 imported pages - `appendices`: 28 imported pages - `basic-metapatterns`: 6 imported pages - `extension-metapatterns`: 6 imported pages - `foundations-of-software-architecture`: 10 imported pages - `fragmented-metapatterns`: 6 imported pages - `implementation-metapatterns`: 5 imported pages - `introduction`: 4 imported pages - `root`: 1 imported pages ### Representative Starting Points - [[wiki/concepts/source/appendices/evolutions-of-architectures|Evolutions of architectures]] - [[wiki/concepts/source/appendices/index-of-patterns|Index of patterns]] - [[wiki/concepts/source/basic-metapatterns/layers|Layers]] - [[wiki/concepts/source/basic-metapatterns/monolith|Monolith]] - [[wiki/concepts/source/basic-metapatterns/pipeline|Pipeline]] - [[wiki/concepts/source/basic-metapatterns/services|Services]] - [[wiki/concepts/source/extension-metapatterns/orchestrator|Orchestrator]] - [[wiki/concepts/source/extension-metapatterns/proxy|Proxy]] - [[wiki/concepts/source/implementation-metapatterns/hexagonal-architecture|Hexagonal Architecture]] - [[wiki/concepts/source/introduction/metapatterns|Metapatterns]] - [[wiki/concepts/source/introduction/system-topologies|System topologies]] ## Source Roots - `https://github.com/denyspoltorak/metapatterns` - `https://github.com/denyspoltorak/metapatterns/wiki` - `https://metapatterns.io/` ## Maintenance - Refresh with `scripts/import_metapatterns_wiki.py` from this namespace directory. - Preserve `source_repository`, `source_wiki`, `source_path`, and `source_url` metadata. - Keep license/provenance notes visible in README and source pages. --- title: "Software Architecture Metapatterns — Activity Log" created: 2026-07-02 updated: 2026-07-02 type: log status: active namespace: software-architecture-metapatterns --- # Software Architecture Metapatterns — Activity Log ## 2026-07-02 - Created the `software-architecture-metapatterns` namespace under Knowledge Systems. - Imported 79 Markdown source pages from `denyspoltorak/metapatterns.wiki`. - Added provenance/license guardrails and agent-facing software architecture retrieval guidance. - Mirrored source diagrams into namespace-local `assets/images/` and rewrote raw GitHub `` HTML into Markdown image links for Pixi Wiki rendering. --- title: For Agents — Software Architecture Retrieval created: 2026-07-02 updated: 2026-07-02 type: summary status: compiled namespace: software-architecture-metapatterns sources: - README.md - wiki/concepts/source/ --- # For Agents — Software Architecture Retrieval Use this namespace as a software-architecture reasoning source, not as a menu of fashionable shapes. ## Retrieval Loop 1. Clarify the design target: product surface, service, module, workflow, data system, integration boundary, or whole codebase. 2. Identify the current forces: latency, throughput, dependency churn, deployment boundaries, data ownership, team ownership, extensibility, testability, and migration risk. 3. Retrieve 3–8 relevant pages from `software-architecture-metapatterns` by pattern name, alias, force, or evolution path. 4. Compare candidates by: - structural shape - communication style - dependency direction - where business logic lives - performance tradeoff - operational/team cost - possible next evolution 5. Produce a recommendation with citations to source pages and a small verification checklist. ## Output Shape ```text Design target: Observed forces: Relevant metapatterns: - Metapattern/source page: reason selected Recommendation: - preferred shape and why Avoid: - shapes that are overkill or mismatched Evolution path: - safe next step, rollback, and later split point Verification checklist: - concrete checks tied to dependency, performance, and ownership assumptions License note: - source is attributed external reference material; preserve provenance ``` ## Useful Starting Queries - `monolith layers services migration` - `hexagonal architecture dependency inversion adapters` - `orchestrator choreography saga event mediator` - `pipeline throughput latency batch stream` - `proxy gateway backend for frontend adapter` - `polyglot persistence data ownership shared repository` ## Pixi/Jamie Use - For codebase review: use this namespace to name the current shape before proposing changes. - For product prototypes: prefer simple monolith/layers unless forces justify distribution. - For agent work: cite the metapattern pages that shaped the plan so Pixoid can review the reasoning. - For migration: retrieve the relevant evolution pages before recommending a split. --- title: License and Provenance created: 2026-07-02 updated: 2026-07-02 type: summary status: compiled namespace: software-architecture-metapatterns sources: - https://github.com/denyspoltorak/metapatterns - https://github.com/denyspoltorak/metapatterns/wiki --- # License and Provenance ## Source Roots - Repository: `https://github.com/denyspoltorak/metapatterns` - GitHub wiki: `https://github.com/denyspoltorak/metapatterns/wiki` - Website: `https://metapatterns.io/` - Latest release inspected: `https://github.com/denyspoltorak/metapatterns/releases/tag/v1.2` ## License Notes Observed source signals are not perfectly uniform: - The repository root `LICENSE` is Creative Commons Attribution-NonCommercial-ShareAlike 4.0. - `ArchitecturalMetapatterns/LICENSE` is Creative Commons Attribution 4.0. - The GitHub wiki `Appendices/Copyright.md` describes Creative Commons Attribution 4.0. - The repo README says the book's diagrams and ODT file are available under CC BY. ## Conservative Guardrail Pixi Wiki should preserve attribution, source links, and license notes. Do not present the imported corpus as unrestricted commercial training data. For public summaries, describe it as an attributed external reference corpus. --- title: Architecture Metapatterns — Fit for Pixi created: 2026-07-02 updated: 2026-07-02 type: synthesis status: compiled namespace: software-architecture-metapatterns sources: - README.md - wiki/concepts/source/root/source-wiki-home.md - wiki/concepts/source/introduction/metapatterns.md --- # Architecture Metapatterns — Fit for Pixi This corpus belongs under Knowledge Systems because it is a system for organizing software architecture knowledge, not just a pile of definitions. ## Why It Fits - It turns scattered architecture pattern names into a navigable pattern language. - It groups aliases and near-duplicates by structure and function. - It includes dependency, performance, applicability, relation, and evolution framing. - It helps agents reason about architecture forces before suggesting implementation moves. ## Best Pixi Uses - Architecture review vocabulary for Tinker/Pixoid. - Migration planning from monolith/layers/services/pipeline/proxy/orchestrator shapes. - Agent retrieval before recommending hexagonal architecture, service splits, BFFs, eventing, or plugin systems. - Crosslinks to `agent-workflows` when architecture choices affect agent-built software delivery. ## Boundary Keep the imported source corpus separate from Jamie-authored architecture decisions. Use it to support review and reasoning; do not let it override live code evidence or project constraints.