Architectural Patterns & Tactics in Software Design

Ahmed Boutaraa
14 min readJan 18, 2021

In this article we will go through the list of useful and widely used Architectural Patterns, these patterns are the following:
*runtime elements (such as broker or client-server).
*design-time elements (such as layers).
I will also define what a Tactic is and how it relates to Architectural Patterns.

Tactics

Tactics are simpler than patterns. Tactics typically use just a single structure or computational mechanism, and they are meant to address a single architectural force. For this reason, they give more precise control to an architect when making design decisions than patterns, which typically combine multiple design decisions into a package. Tactics are the “building blocks” of design from which architectural patterns are created. Tactics are atoms and patterns are molecules.

Patterns

Patterns can be categorized by the dominant type of elements that they show: module patterns show modules, component-and-connector (C&C) patterns show components and connectors, and allocation patterns show a combination of software elements (modules, components, connectors) and nonsoftware elements. Most published patterns are C&C patterns, but there are module patterns and allocation patterns as well.

for each pattern, I will list the context, problem, solution, Constraints & Weaknesses. and before I start, Applying a pattern is not an all-or-nothing proposition. in practice, architects may choose to violate them in small ways when there is a good design tradeoff to be had. for example, the layered pattern expressly forbids software in lower layers from using the software in the upper layers, but there may be cases (such as to gain some performance).
well without so much overhead let’s get started.

Module Patterns

*Layered Pattern* (OSI-model, Web-model,..)
— Context: All complex systems experience the need to develop and evolve portions of the system independently. For this reason the developers of the system need a clear and well-documented separation of concerns, so that modules of the system may be independently developed and maintained.
— Problem: The software needs to be segmented in such a way that the modules can be developed and evolved separately with little interaction among the parts, supporting portability, modifiability, and reuse.
— Solution: To achieve this separation of concerns, the layered pattern divides the software into units called layers. Each layer is a grouping of modules that offers a cohesive set of services. Layers are completely a partition set of software, and each partition is exposed through a public interface(API).
— Constraints & Weaknesses:
-Every piece of software is allocated to exactly one layer. There are at least two layers (but usually more than two).
-They should not be circular i.e (lower layer cannot use a layer above)
-The addition of layers adds up-front cost and complexity to a
system
-Layers contribute a performance penalty.

Component-and-Connector Patterns

*Broker Pattern* (Apache_kafka, RabbitMQ,..)
— Context: Many systems are constructed from a collection of services distributed
across multiple servers. Implementing these systems is complex because you need to worry about how the systems will interoperate — how they will connect to each other and how they will exchange information as well as the availability of the component services.
— Problem: How do we structure distributed software so that service users do not need to know the nature and location of service providers, making it easy to dynamically change the bindings between users and providers?
— Solution: The broker pattern separates users of services (clients) from providers of services (servers) by inserting an intermediary, called a broker. When a client needs a service, it queries a broker via a service interface. The broker then forwards the client’s service request to a server, which processes the request. The service result is communicated from the server back to the broker, which then return the result back to the requesting client (Proxies are commonly introduced as intermediaries in addition to the broker).
— Constraints & Weaknesses:
-The client can only attach to a broker potentially via a client-side proxy(forward-proxy) and The server can only attach to a broker potentially via a server-side proxy(reverse-proxy).
-Brokers add a layer of indirection, and hence latency, between clients
and servers, and that layer may be a communication bottleneck.
-The broker can be a single point of failure.
-A broker adds up-front complexity, may be a target for security attacks and difficult to test.

*Model-View-Controller(MVC) Pattern* (Django, Rails, ASP.NET, Adobe’s Flex,…)
— Context: User interface software is typically the most frequently modified portion of an interactive application. For this reason it is important to keep modifications to the user interface software separate from the rest of the system. Users often wish to look at data from different perspectives, such as a bar graph or a pie chart. These representations should both reflect the current state of the data.
— Problem: How can user interface functionality be kept separate from application functionality and yet still be responsive to user input, or to changes in the underlying application’s data? And how can multiple views of the user interface be created, maintained, and coordinated when the underlying application data changes?
— Solution: The model-view-controller (MVC) pattern separates application functionality into three kinds of components:
1-A model, which contains the application’s data
2-A view, which displays some portion of the underlying data and interacts with the user
3-A controller, which mediates between the model and the view and manages the notifications of state changes
— Constraints & Weaknesses:
-There must be at least one instance each of model, view, and controller.
-The model component should not interact directly with the controller.
-The complexity may not be worth it for simple user interfaces.
-The model, view, and controller abstractions may not be good fits for some user interface toolkits.

*Pipe-and-Filter Pattern* (UNIX pipes, Yahoo! Pipes, request process of Apache-WS)
— Context:
Many systems are required to transform streams of discrete data items, from input to output. Many types of transformations occur repeatedly in practice, and so it is desirable to create these as independent, reusable parts.
— Problem: Such systems need to be divided into reusable, loosely coupled components with simple, generic interaction mechanisms. In this way they can be flexibly combined with each other. The components, being generic and loosely coupled, are easily reused. The components, being independent, can execute in parallel.
— Solution: The pattern of interaction in the pipe-and-filter pattern is characterized by successive transformations of streams of data. Data arrives at a filter’s input port(s), is transformed, and then is passed via its output port(s) through a pipe to the next filter. A single filter can consume data from, or produce data to, one or more ports.
Pipes buffer data during communication. Because of this property, filters can execute asynchronously and concurrently. Moreover, a filter typically does not know the identity of its upstream or downstream filters. For this reason, pipeline pipe-and-filter systems have the property that the overall computation can be treated as the functional composition of the computations of the filters, making it easier for the architect to reason about end-to-end behavior.
Data transformation systems are typically structured as pipes and filters, with each filter responsible for one part of the overall transformation of the input data.
— Constraints & Weaknesses:
-Connected filters must agree on the type of data being passed along the connecting pipe.
-Specializations of the pattern may restrict the association of components to an acyclic graph or a linear sequence, sometimes called a pipeline.
-Other specializations may prescribe that components have certain named ports, such as the stdin, stdout, and stderr ports of UNIX filters.
-The pipe-and-filter pattern is typically not a good choice for an interactive system.
-Having large numbers of independent filters can add substantial amounts of computational overhead.
-Pipe-and-filter systems may not be appropriate for long-running computations.

*Client-Server Pattern* (Web-based applications, E-Commerce-Site,…)
— Context: There are shared resources and services that large numbers of distributed clients wish to access, and for which we wish to control access or quality of service.
— Problem: By managing a set of shared resources and services, we can promote modifiability and reuse, by factoring out common services and having to modify these in a single location, or a small number of locations. We want to improve scalability and availability by centralizing the control of these resources and services, while distributing the resources themselves across multiple physical servers.
— Solution: Clients interact by requesting services of servers, which provide a set of services. Some components may act as both clients and servers. There may be one central server or multiple distributed ones.
the component types are clients and servers. the principal connector type for the client-server pattern is a data connector driven by a request/reply protocol used for invoking services.
The client-server pattern separates client applications from the services they use. This pattern simplifies systems by factoring out common services, which are reusable. Because servers can be accessed by any number of clients, it is easy to add new clients to a system. Similarly, servers may be replicated to support scalability or availability.
The World Wide Web is the best-known example of a system that is based on the client-server pattern, allowing clients (web browsers) to access information from servers across the Internet using the HTTP-Protocol.
In early forms of client-server, service invocation is synchronous:
the requester of a service waits, or is blocked, until a requested service completes its actions, possibly providing a return result. However, variants of the client-server pattern may employ more-sophisticated connector protocols. For example:
Web browsers don’t block until the data request is served up.
In some client-server patterns, servers are permitted to initiate certain actions on their clients. This might be done by allowing a client to register notification procedures, or callbacks, that the server calls at specific times.
In other systems service calls over a request/reply connector are bracketed by a “session” that delineates the start and end of a set of a client-server interaction.
— Constraints & Weaknesses:
-Clients are connected to servers through request/reply connectors.
-Server components can be clients to other servers.
-Specializations may impose restrictions:
Numbers of attachments to a given port
Allowed relations among servers
-Components may be arranged in tiers, which are logical groupings of related functionality that will share a host computing environment
-Server can be a performance bottleneck.
-Server can be a single point of failure.
-Decisions about where to locate functionality (in the client or in the server) are often complex and costly to change after a system has been built.

*Peer-to-Peer Pattern* (bitcoin, blockchain, file sharing, instant messaging, BitTorrent, Skype,…)
— Context:
Distributed computational entities — each of which is considered equally important in terms of initiating an interaction and each of which provides its own resources — need to cooperate and collaborate to provide a service to a distributed community of users.
— Problem: How can a set of “equal” distributed computational entities be connected to each other via a common protocol so that they can organize and share their services with high availability and scalability?
— Solution: In the peer-to-peer (P2P) pattern, components directly interact as peers. All peers are “equal” and no peer or group of peers can be critical for the health of the system. Peer-to-peer communication is typically a request/ reply interaction without the asymmetry found in the client-server pattern. That is, any component can, in principle, interact with any other component by requesting its services. The interaction may be initiated by either party — that is, in client-server terms, each peer component is both a client and a server. Sometimes the interaction is just to forward data without the need for a reply. Each peer provides and consumes similar services and uses the same protocol. Connectors in peer-to-peer systems involve bidirectional interactions, reflecting the two-way communication that may exist between two or more peer-to-peer components.
A peer-to-peer architecture may have specialized peer nodes (called supernodes) that have indexing or routing capabilities and allow a regular peer’s search to reach a larger number of peers. Peers can be added and removed from the peer-to-peer network with no sig-
nificant impact
— Constraints & Weaknesses:
-Restrictions may be placed on the following:
-The number of allowable attachments to any given peer
-The number of hops used for searching for a peer
-Which peers know about which other peers
-Some P2P networks are organized with star topologies, in which peers only connect to supernodes
-Managing security, data consistency, data/service availability, backup, and recovery are all more complex.
-Small peer-to-peer systems may not be able to consistently achieve quality goals such as performance and availability.

*Service-Oriented-Architecture(SOA) Pattern* (bank services,…)
— Context: A number of services are offered (and described) by service providers and consumed by service consumers. Service consumers need to be able to understand and use these services without any detailed knowledge of their implementation.
— Problem: How can we support interoperability of distributed components running on different platforms and written in different implementation languages, provided by different organizations, and distributed across the Internet? How can we locate services and combine (and dynamically recombine) them into meaningful coalitions while achieving reasonable performance, security, and availability?
— Solution: The service-oriented architecture (SOA) pattern describes a collection of distributed components that provide and/or consume services. In an SOA, service provider components and service consumer components can use different implementation languages and platforms. Services are largely standalone: service providers and service consumers are usually deployed independently, and often belong to different systems or even different organizations. Components have interfaces that describe the services they request from other components and the services they provide. A service’s quality attributes can be specified and guaranteed with a service-level agreement (SLA). In some cases, these are legally binding. Components achieve their computation by requesting services from one another.
Service invocation can be mediated by an enterprise service bus (ESB). ESB adds overhead thereby lowering performance, and introduces an additional point of failure. So to improve the independence of service providers, a service registry can be used and we can use orchestration server. as you can see SOA environments may involve a mix of the three connectors.
The basic types of connectors used in SOA are these legacy protocols SOAP, REST and Asynchronous messaging.
— Constraints & Weaknesses:
-Service consumers are connected to service providers, but intermediary components (e.g., ESB, registry, orchestration-server) may be used.
-SOA-based systems are typically complex to build.
-You don’t control the evolution of independent services.
-There is a performance overhead associated with the middleware, and services may be performance bottlenecks, and typically do not provide performance guarantees.

*Publish-Subscribe Pattern* (youtube,…)
— Context: There are a number of independent producers and consumers of data that must interact. The precise number and nature of the data producers and consumers are not predetermined or fixed, nor is the data that they share.
— Problem: How can we create integration mechanisms that support the ability to transmit messages among the producers and consumers in such a way that they are unaware of each other’s identity, or potentially even their existence?
— Solution: In the publish-subscribe pattern components interact via announced messages, or events. Components may subscribe to a set of events. It is the job of the publish-subscribe runtime infrastructure to make sure that each published event is delivered to all subscribers of that event. Thus, the main form of connector in these patterns is an event bus. Publisher components place events on the bus by announcing them; the connector then delivers those events to the subscriber components that have registered an interest in those events. Any component may be both a publisher and a subscriber.
— Constraints & Weaknesses:
-it provides less control over ordering of messages and uncertainty.
-delivery of messages is not guaranteed.
-Typically increases latency and has a negative effect on scalability and predictability.

  • Shared-Data Pattern* (windows & web app, headless programs(no GUI),…)
    — Context: Various computational components need to share and manipulate large amounts of data. This data does not belong solely to any one of those components.
    — Problem: How can systems store and manipulate persistent data that is accessed by multiple independent components?
    — Solution: In the shared-data pattern, interaction is dominated by the exchange of persistent data between multiple data accessors and at least one shared-data store. Exchange may be initiated by the accessors or the data store. The connector type is data reading and writing. The general computational model associated with shared-data systems is that data accessors perform operations that require data from the data store and write results to one or more data stores. That data can be viewed and acted on by other data accessors. In a pure shared-data system, data accessors interact only through one or more shared-data stores. However, in practice shared-data systems also allow direct interactions between data accessors. The data-store components of a shared-data system provide shared access to data, support data persistence, manage concurrent access to data through transaction management, provide fault tolerance, support access control, and handle the distribution and caching of data values.
    — Constraints & Weaknesses:
    -The shared-data store may be a performance bottleneck.
    -The shared-data store may be a single point of failure.
    -Producers and consumers of data may be tightly coupled.

Allocation Patterns

*Map-Reduce Pattern* (Google, Facebook, eBay, Yahoo,…)
— Context: Businesses have a pressing need to quickly analyze enormous volumes of data they generate or access, at petabyte scale. Examples include logs of interactions in a social network site, massive document or data repositories, and pairs of <source, target> web links for a search engine. Programs for the analysis of this data should be easy to write, run efficiently, and be resilient with respect to hardware failure.
— Problem: For many applications with ultra-large data sets, sorting the data and then analyzing the grouped data is sufficient. The problem the map-reduce pattern solves is to efficiently perform a distributed and parallel sort of a large data set and provide a simple means for the programmer to specify the analysis to be done.
— Solution: The map-reduce pattern requires three parts: First, a specialized infrastructure takes care of allocating software to the hardware nodes in a massively parallel computing environment and handles sorting the data as needed. A node may be a standalone processor or a core in a multi-core chip. Second and third are two programmer-coded functions called, predictably enough, map and reduce.
Map is a function with multiple instances deployed across multiple processors that performs the extract and transformation portions of the analysis.
Reduce is a function that may be deployed as a single instance or as multiple instances across processors to perform the load portion of extract-transform-load.
The infrastructure is the framework responsible for deploying map and reduce instances, shepherding the data between them, and detecting and recovering from failure
— Constraints & Weaknesses:
-The data to be analyzed must exist as a set of files.
-The map functions are stateless and do not communicate with each other.
-If you do not have large data sets, the overhead of map-reduce is not justified.
-If you cannot divide your data set into similar sized subsets, the advantages of parallelism are lost.
-Operations that require multiple reduces are complex to orchestrate.

*Multi-tier Pattern* (Java EE, Microsoft.NET,…)
“The multi-tier pattern is a C&C pattern or an allocation pattern, depending on the criteria used to define the tiers”
— Context: In a distributed deployment, there is often a need to distribute a system’s infrastructure into distinct subsets. This may be for operational or business reasons (for example, different parts of the infrastructure may belong to different organizations).
— Problem: How can we split the system into a number of computationally independent execution structures — groups of software and hardware — connected by some communications media? This is done to provide specific server environments optimized for operational requirements and resource usage.
— Solution: The execution structures of many systems are organized as a set of logical groupings of components. Each grouping is termed a tier. The grouping of components into tiers may be based on a variety of criteria, such as the type of component, sharing the same execution environment, or having the same runtime purpose. Tiers are not components, but rather logical groupings of components. Also, don’t confuse tiers with layers! Layering is a pattern of modules, while tiers applies only to runtime entities. Tiers make it easier to ensure security, and to optimize performance and availability in specialized ways. They also enhance the modifiability of the system, as the computationally independent subgroups need to agree on protocols for interaction, thus reducing their coupling.
— Constraints & Weaknesses:
-A software component belongs to exactly one-tier.
-Substantial up-front cost and complexity.

Summary

An architectural pattern is a package of design decisions that are found repeatedly in practice, has known properties that permit reuse, and describes a class of architectures. Because patterns are (by definition) found repeatedly in practice, one does not invent them; one discovers them. architectural patterns are just a combination of tactics. Applying successive tactics is like moving through a game space, and it’s a little like chess: Good players are able to see the consequences of the move they’re considering, and the very good players are able to look several moves ahead.
and I hope this brief overview wasn’t so much for you to go through.

--

--