This is a section from my book Architectural Metapatterns: the Pattern Language of Software Architecture which is now available for free on Leanpub and GitHub. Any feedback is warmly welcome.

Now that we’ve seen patterns decomposed into coupling and cohesion, we can try reconstructing architecture based on your project’s needs.

Project size

Expected project’s 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’s task will likely be monolithic, a man-month of work needs layering while anything larger calls for at least partial separation into subdomain modules or services. Very large projects may require further subdivision into Service-Oriented Architecture (SOA) or Cell-Based Architecture.

Another factor to consider in the initial design is any inherent decoupling within your domain. For example, the layer with domain logic is very likely to contain independent subdomains which naturally make modules or services at next to no development or runtime cost. Likewise, 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 pipeline, which is a very flexible architectural style.

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 layered architecture as there are only so many layers in any system. Thus, having a large number of teams strongly hints at ServicesPipelineSOA or Hierarchy.

If not all the teams are available since day 0 but some will be hired later, it is still preferable to initially set up component boundaries for the prospected number of teams as subdividing an already implemented component is a terrible experience. However, it may be easier and safer for now to leave multiple components running in a single process (as modules) to avoid the overhead of going distributed and have less trouble moving pieces of code between them if needed (as new requirements often make fun of your design). You should be able to make modules into services through a moderate effort once that becomes imperative.

Domain features

We’ve 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 that cannot be matched to your subdomains as every use case involves multiple components, spreading over the entire system. You would usually collect the global use cases into a dedicated component — orchestrator. And if the orchestrator grows out of control, it is subdivided into layers or services.

Other systems build around data. You cannot split it into private databases because almost every service needs access to the whole. You need a shared repository or highly performant Space-Based Architecture.

Once you go distributed, you will likely employ a middleware to centralize communication between your services. And you will have various proxies, such as FirewallReverse Proxy and Response Cache. You may even deploy a proxy per kind of client if the clients vary in protocols, resulting in Backends for Frontends.

Runtime performance

Moreover, there are non-functional requirements, such as performance or fault tolerance.

High throughput is achieved by sharding or replicating your business logic or even your data. Sharding also helps process huge datasets while replication improves fault tolerance. Space-Based Architecture replicates in memory the entire dataset for faster access.

Alternatively, you may use several specialized databases (Polyglot Persistence) or redesign the highly loaded part of your system as a self-scaling pipeline.

Scalability under uneven load is achieved through Function as a Service (Nanoservices), Service-Mesh-based Microservices and, to a higher extent, Space-Based Architecture.

Fault tolerance requires you to have replicas of every component, including databases, ideally over multiple data centers. If you are not that rich, be content with Actors or Mesh.

Low latency makes you place a simplified first response logic close to your input, leading to:

Flexibility

If your product needs customization, you go for Plugins.

If it is to survive for a decade, you need Hexagonal Architecture to be able to change vendors.

If you mediate between resource or service providers and consumers, you build Microkernel.

When your teams develop services and you want them to be less interdependent, you insert an anticorruption layeropen host service or CQRS view between them.

When you have built a large system and really need that thorough data analytics, consider implementing a data mesh.

Every domain is unique

No 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. 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.

LINK: https://itnext.io/choose-your-own-architecture-92c56b12f7b0