CPSA-F – Solving the common issues effectively with patterns

Hi, This is the next post of my CPSA exam preparation. This time there will be a lot of concrete information about a design patterns and cross-cutting solutions. Let’s go!

Cross-cutting concepts

Usually when building the IT system, we can distinguish many independent blocks, but at the same time there are many aspects that work the same in every part of the system. Here I mean such things like logging, authentication, monitoring, caching etc. This aspect should be the part of the software architecture with explicit explanation of how and why the development team should implement it.

Cross-cutting topics are so important that when they are problematic, all components using it will have the problem, so architect have to think about it thoroughly.

POSA

Every architect should know how and when to use which design pattern.

POSA (Pattern-Oriented Software Architecture) mentions fascinating topics, which I am going to describe shortly.

Architectural patterns

Layers
The Layers pattern structures a system into hierarchical layers, each building upon the services provided by the layer beneath it. This separation promotes modularity and simplifies maintenance. For example, in a typical application, layers might include:

  • Presentation Layer: Handles user interface and user interaction.
  • Application Layer: Manages business logic and workflows.
  • Data Layer: Manages data access and storage.

This organization allows for independent development and testing of each layer, enhancing scalability and flexibility.

Other example can be ISO/OSI network layers where lower layers hide certain details from layers above.

Having above examples we can distinguish two types of layering: strict and loose:

  • in strict layering, layers can communicate only with closest layer. Crossing layers is prohibited
  • in loose layering, one layer may reach any layer

Pipes and filters
The Pipes and Filters pattern decomposes a task into a sequence of processing steps (filters), where each step transforms the data and passes it to the next via a pipe. This pattern is particularly useful for data processing pipelines, such as compilers or stream processing systems. Each filter operates independently, enabling easy addition, removal, or reordering of processing steps.

Blackboard
The Blackboard pattern is used for problems that have no deterministic solution strategy. It involves multiple specialized subsystems (knowledge sources) that work cooperatively on a common data structure (the blackboard). Each knowledge source produces partial solutions, and the blackboard serves as a global repository for these contributions. This pattern is often applied in domains like speech recognition vehicle identification and tracking and complex problem-solving systems.

Broker
The Broker pattern is designed for distributed systems with decoupled components that interact via remote service invocations. A broker component coordinates communication between clients and servers, managing connections, message routing, and other communication-related tasks. This pattern facilitates scalability and flexibility in distributed applications.

Model-View-Controller
The MVC pattern separates an application into three interconnected components:

  • Model: Represents the application’s data and business logic.
  • View: Displays the data (user interface).
  • Controller: Handles user input and updates the model and view accordingly.

This separation allows for independent development, testing, and maintenance of each component, enhancing the application’s modularity and scalability.

Here it’s worth to mention other patterns from the family shortly:

  • MVVM is an evolution of MVC that introduces a ViewModel to bridge the gap between the View and the Model, commonly used in frameworks like Angular and libraries like React with its hooks.
  • MVU is a reactive pattern that emphasizes unidirectional data flow. It’s popular in frameworks like Elm and some aspects of React.
  • Model-View-Presenter (MVP) – The Presenter replaces the Controller and takes on more responsibility, including handling UI logic and directly updating the View. The View delegates all user interactions to the Presenter. Common in mobile development (e.g., Android).
  • Model-View-Intent (MVI) – Designed for reactive programming paradigms. Intent represents user actions and system events, which are processed to update the Model. Focuses on state management and functional programming principles.
  • Passive View – A variation of MVP where the View is entirely passive and contains no logic. Presenter handles all decisions and updates.

Presentation-Abstraction-Control
The PAC pattern organizes an interactive system into a hierarchy of cooperating agents, each consisting of three components:

  • Presentation: Manages the user interface.
  • Abstraction: Encapsulates the core functionality and data.
  • Control: Handles communication between the presentation and abstraction components and coordinates with other agents.

This pattern is particularly useful for complex, interactive applications requiring a high degree of modularity and flexibility.

You can find more info about Architectural patterns in a following book. Pattern-Oriented Software Architecture, Volume 1: A System of Patterns.

Design patterns

Whole-Part
The Whole-Part pattern helps in structuring complex systems by defining a whole object and its constituent parts. This pattern allows a whole object to aggregate its parts, manage their interactions, and present a unified interface to clients. It’s particularly useful when dealing with hierarchies or compositions where the whole and its parts need to be treated uniformly. Sometimes called Composite.

Example: In a graphical application, a Graphic object (Whole) can consist of multiple Shape objects (Parts), allowing operations to be performed on the entire graphic or individual shapes.

Master-Slave
The Master-Slave pattern involves a master component that delegates tasks to multiple slave components. The master coordinates the slaves and processes their results. This pattern is beneficial for parallel processing, fault tolerance, and load balancing.

Example: In a database replication system, the master database (Master) propagates changes to multiple replica databases (Slaves) to ensure data consistency and availability.

Proxy
The Proxy pattern provides a surrogate or placeholder for another object to control access to it. Proxies are useful for implementing lazy initialization, access control, logging, and remote object representation.

Example: Spring Boot uses a proxy to add additional behavior to the methods like transactional or caching.

Command Processor
The Command Processor pattern separates the request for a service from its execution by encapsulating requests as objects. This allows for parameterization, queuing, logging, and undoable operations.

Example: In a text editor, each user action (e.g., typing, deleting) can be encapsulated as a command object, enabling features like undo and redo.

View Handler
The View Handler pattern manages multiple views of the same data model, ensuring consistency and synchronization among them. It’s particularly useful in applications where data can be presented in various formats or perspectives.

Example: In a financial application, a View Handler can manage different representations of stock data, such as charts, tables, and summaries, ensuring all views reflect the current data state.

Forwarder-Receiver
The Forwarder-Receiver is a communication pattern that decouples communication between components by introducing forwarders and receivers that handle the transmission and reception of messages. This enhances flexibility and scalability in distributed systems.

Example: In a distributed logging system, a Forwarder component can send log messages to a remote Receiver, which processes and stores them, allowing for scalable and decoupled logging.

Client-Dispatcher-Server
The Client-Dispatcher-Server is another communication pattern and introduces a dispatcher component between clients and servers. The dispatcher manages client requests, locates the appropriate server, and forwards the request, providing location transparency and load balancing.

Example: In a microservices architecture, a Dispatcher can route client requests to the appropriate service instance based on factors like load and availability.

Publisher-subscriber
The Publisher-Subscriber pattern, also known as Observer, defines a one-to-many dependency between objects. When the publisher’s state changes, all subscribed observers are notified and updated automatically.

Example: In a news application, a Publisher can broadcast news updates to multiple Subscribers, such as user interfaces, logs, and external systems, ensuring all parts of the system receive the latest information.

You can find more info about design patterns in a following book. Pattern-Oriented Software Architecture, Volume 1: A System of Patterns.

Service access and configuration patterns

Wrapper Facade
The Wrapper Facade pattern encapsulates existing non-object-oriented APIs within object-oriented class interfaces. This encapsulation provides a more concise, robust, portable, and maintainable interface to the underlying functions and data structures. By creating wrapper classes, developers can enhance code readability and reusability, and facilitate easier maintenance.

Example: In a system that utilizes low-level C APIs, a wrapper facade can be implemented in C++ to provide object-oriented access to these functions, thereby improving type safety and reducing complexity.

Component Configurator
The Component Configurator pattern allows an application to dynamically link and unlink its component implementations at runtime without requiring modifications, recompilation, or static relinking. This pattern is particularly useful in applications with high availability requirements, as it enables the reconfiguration of components without shutting down the system.

Example: A web server can use the component configurator pattern to load or unload modules (such as authentication handlers or logging mechanisms) on-the-fly based on current needs or administrative commands.

Interceptor
The Interceptor pattern enables services to be added transparently to a framework and triggered automatically when certain events occur. Interceptors allow for the separation of cross-cutting concerns, such as logging, authentication, or transaction management, from the core business logic.

Example: In a middleware system, interceptors can monitor and log messages passing through the system without altering the primary processing logic, thereby facilitating debugging and auditing.

Extension interface
The Extension Interface pattern allows multiple interfaces to be exported by a component, preventing interface bloat and minimizing the impact on client code when extending or modifying component functionality. This pattern promotes flexibility and scalability by enabling components to evolve without breaking existing clients.

Example: A graphics library can provide a core interface for basic operations and separate extension interfaces for advanced features like 3D rendering or image processing, allowing clients to implement only the interfaces relevant to their needs.

You can find more info about service access and configuration patterns in a following book. Pattern-Oriented Software Architecture, Volume 2: Patterns for Concurrent and Networked Objects

Event handling patterns

Event handling patterns are essential in designing responsive and efficient software systems, particularly in networked and concurrent environments.

Reactor
The Reactor pattern enables event-driven applications to demultiplex and dispatch service requests that are delivered to an application from one or more clients. It allows an application to register specific event handlers for various events and uses a synchronous event demultiplexer to wait for events to occur. Upon event occurrence, the reactor dispatches the appropriate event handler to handle the event. This pattern is particularly useful for applications that require high performance and scalability, such as web servers and GUI applications.

Example: In a server application, the reactor pattern can manage multiple client connections by using a single-threaded event loop that listens for events like incoming connections or data readiness, dispatching the appropriate handler for each event.

Proactor
The Proactor pattern allows event-driven applications to efficiently demultiplex and dispatch service requests triggered by the completion of asynchronous operations. Unlike the reactor pattern, which waits for events to occur, the proactor pattern initiates asynchronous operations and processes their completion events. This pattern is beneficial in environments that support asynchronous I/O operations, as it can improve application responsiveness and scalability.

Example: In a high-performance web server, the proactor pattern can be used to handle file I/O operations asynchronously, allowing the server to process other requests while waiting for I/O operations to complete.

Asynchronous Completion Token
The Asynchronous Completion Token (ACT) pattern allows an application to demultiplex and process efficiently the responses of asynchronous operations it invokes on services. By associating a unique token with each asynchronous operation, the application can identify and handle the completion of each operation appropriately. This pattern is often used in conjunction with the proactor pattern to manage multiple outstanding asynchronous operations.

Example: In a networked application that sends multiple asynchronous requests to a server, the ACT pattern can be used to associate each request with a unique token, enabling the application to match incoming responses with their corresponding requests.

Acceptor-Connector
The Acceptor-Connector pattern decouples the connection and initialization of cooperating peer services in a networked system from the processing they perform once connected and initialized. This pattern allows applications to configure their connection topologies in a manner largely independent of the services they provide, enhancing flexibility and scalability.

Example: In a client-server application, the acceptor-connector pattern can be used to manage the establishment of connections, allowing the server to accept incoming client connections and initialize the necessary resources without intertwining connection logic with business logic.

You can find more info about event handling patterns in a following book. Pattern-Oriented Software Architecture, Volume 2: Patterns for Concurrent and Networked Objects

Synchronization patterns

Synchronization patterns are essential in concurrent programming to ensure that multiple threads or processes can access shared resources without conflicts or inconsistencies.

Scoped Locking
The Scoped Locking idiom ensures that a lock is acquired when control enters a scope and released automatically when control leaves the scope, regardless of how the scope is exited (e.g., normal exit, exception). This pattern leverages the RAII (Resource Acquisition Is Initialization) principle, commonly used in languages like C++, to manage resource lifetimes.

Strategized Locking
The Strategized Locking pattern parameterizes synchronization mechanisms used in a component to protect its critical sections from concurrent access. By allowing the locking strategy to be configured, this pattern enables flexibility in choosing different synchronization policies (e.g., no locking, single-threaded locking, multi-threaded locking) without modifying the component’s core logic.

Example: A class template in C++ can accept a locking policy as a template parameter, allowing the same class to be used in both single-threaded and multi-threaded contexts by selecting the appropriate locking strategy.

Thread-Safe Interface
The Thread-Safe Interface pattern ensures that intra-component method calls avoid self-deadlock and minimize locking overhead. It involves designing interfaces that are safe to call from multiple threads simultaneously, often by internalizing synchronization within the component and preventing clients from needing to manage locks explicitly.

Example: A thread-safe queue class encapsulates all necessary synchronization, allowing multiple threads to enqueue and dequeue items without external locking.

Double-checked locking
The Double-Checked Locking optimization reduces the overhead of acquiring a lock by first testing the locking criterion (e.g., whether the resource is initialized) without actually acquiring the lock. Only if the check indicates that locking is required does the actual lock acquisition occur, followed by a second check to ensure the condition still holds. This pattern is commonly used in implementing lazy initialization in a thread-safe manner.

Example: In a singleton pattern implementation, double-checked locking can be used to ensure that the singleton instance is created only once, even in a multi-threaded environment, without incurring the performance cost of locking on every access.

You can find more info about synchronization patterns in a following book. Pattern-Oriented Software Architecture, Volume 2: Patterns for Concurrent and Networked Objects

Concurrency patterns

Concurrency patterns are essential in designing systems that efficiently manage multiple tasks simultaneously.

Active object
The Active Object pattern decouples method execution from method invocation to enhance concurrency and simplify synchronized access to objects that reside in their own threads of control. It achieves this by introducing an intermediary that handles method invocation requests, allowing clients to invoke methods asynchronously.

Example: In a real-time trading system, an active object can manage incoming trade requests, queuing them for processing without blocking the client applications.

Monitor object
The Monitor Object pattern synchronizes concurrent method execution to ensure that only one method at a time runs within an object. It also allows an object’s methods to cooperatively schedule their execution sequences, preventing race conditions and ensuring thread safety.

Example: In a banking application, a monitor object can manage account transactions, ensuring that deposits and withdrawals are processed sequentially to maintain data integrity.

Half-Sync/Half-Async
The Half-Sync/Half-Async architectural pattern decouples asynchronous and synchronous service processing in concurrent systems, simplifying programming without unduly reducing performance. It introduces two intercommunicating layers: one for asynchronous and one for synchronous service processing, with a queueing layer mediating communication between them.

Example: In a web server, the half-sync/half-async pattern can handle client requests asynchronously at the network level while processing them synchronously at the application level, balancing responsiveness and simplicity.

Leader/Followers
The Leader/Followers pattern provides an efficient concurrency model where multiple threads take turns sharing a set of event sources to detect, demultiplex, dispatch, and process service requests that occur on the event sources. This pattern can improve performance and simplify programming by eliminating the need for a separate dispatcher thread and reducing context-switching overhead.

Example: In a high-performance server, the leader/followers pattern can be used to manage a pool of threads that handle incoming client connections, with each thread taking turns as the leader to accept new connections.

Thread-Specific Storage
The Thread-Specific Storage pattern allows multiple threads to use one ‘logically global’ access point to retrieve an object that is local to a thread, without incurring locking overhead on each access to the object. This pattern is useful for managing data that is unique to each thread, such as error codes or transaction IDs.

Example: In a multi-threaded logging system, thread-specific storage can be used to maintain separate log buffers for each thread, preventing contention and ensuring thread-safe logging.

You can find more info about concurrency patterns in a following book. Pattern-Oriented Software Architecture, Volume 2: Patterns for Concurrent and Networked Objects

Resource acquisition

Effective resource management is crucial in software development to ensure optimal performance and resource utilization. The Pattern-Oriented Software Architecture (POSA) series outlines several patterns for resource acquisition

Lookup
The Lookup pattern describes how to find and access resources, whether local or distributed, by using a lookup service as a mediating instance. This pattern centralizes resource discovery, allowing clients to locate resources dynamically at runtime.

Example: In a distributed system, a client can use a directory service to discover the network address of a specific service instance, enabling dynamic binding and reducing hard-coded dependencies.

Lazy acquisition
The Lazy Acquisition pattern defers resource acquisition to the latest possible time during system execution to optimize resource use. By delaying the acquisition until the resource is actually needed, this pattern can reduce initial startup time and conserve resources.

Example: In a database application, a connection to the database can be established only when a query is executed for the first time, rather than at application startup, thereby conserving resources when the database is not in use.

Eager acquisition
The Eager Acquisition pattern describes how run-time acquisition of resources can be made predictable and fast by eagerly acquiring and initializing resources before their actual use. This approach can improve performance by reducing latency when the resource is needed, at the cost of increased initial resource consumption.

Example: In a multimedia application, loading all necessary codecs at startup ensures that media files can be played without delay when requested, enhancing user experience.

You can find more info about resource acquisition patterns in a following book. Pattern-Oriented Software Architecture, Volume 3: Patterns for Resource Management

Resource lifecycle

Effective management of a resource’s lifecycle is crucial for optimizing performance and ensuring efficient utilization in software systems.

Caching
The Caching pattern involves storing copies of frequently accessed resources in a cache to reduce the cost and latency associated with retrieving them from their original source. By keeping these resources readily available, systems can improve performance and responsiveness.

Example: In web applications, caching is commonly used to store the results of expensive database queries or computations, allowing subsequent requests to retrieve the data quickly without re-executing the underlying operations.

Pooling
The Pooling pattern manages a set of initialized resources, known as a pool, that can be reused by multiple clients. This approach minimizes the overhead associated with creating and destroying resources, leading to more efficient resource utilization.

Example: Database connection pools maintain a set of open connections that can be reused by different parts of an application, reducing the time and resources needed to establish new connections for each database operation.

Coordinator
The Coordinator pattern introduces a component that manages the interactions and dependencies between multiple resources. By centralizing control, the coordinator ensures that resources are used in a consistent and efficient manner, preventing conflicts and optimizing their utilization.

Example: In a distributed transaction system, a transaction coordinator oversees the commit and rollback operations across multiple databases, ensuring data consistency and integrity.

Resource Lifecycle Manager
The Resource Lifecycle Manager pattern decouples the management of a resource’s lifecycle from its usage. By introducing a dedicated manager responsible for the creation, initialization, maintenance, and disposal of resources, this pattern allows clients to focus on using resources without concerning themselves with the complexities of their lifecycle management.

Example: In a server application, a resource lifecycle manager can handle the allocation and deallocation of thread pools, ensuring that threads are efficiently managed and resources are released appropriately when no longer needed.

You can find more info about resource lifecycle patterns in a following book. Pattern-Oriented Software Architecture, Volume 3: Patterns for Resource Management

Resource release

Effective resource release strategies are crucial in software systems to ensure optimal performance and prevent resource leaks.

Leasing
The Leasing pattern involves allocating resources to clients for a specified period, known as a lease. If the client requires the resource beyond this period, it must renew the lease; otherwise, the resource is reclaimed. This approach helps manage resources efficiently, especially in distributed systems, by preventing indefinite resource occupation.

Example: In a distributed caching system, a cache entry can be assigned a lease duration. If the client doesn’t renew the lease before it expires, the cache entry is invalidated and removed, ensuring that stale data doesn’t persist indefinitely.

Evictor
The Evictor pattern manages resource lifecycles by monitoring their usage and reclaiming those that are no longer needed. It typically employs strategies like reference counting, time-based expiration, or usage patterns to determine when to release resources. This pattern is particularly useful in environments with limited resources, ensuring that unused resources are promptly released.

Example: In a memory management system, an evictor can monitor objects and release those that haven’t been accessed for a certain period, thereby preventing memory leaks and optimizing resource utilization.

You can find more info about resource release patterns in a following book. Pattern-Oriented Software Architecture, Volume 3: Patterns for Resource Management

Software architecture

Understanding various software architecture patterns is essential for designing robust and maintainable systems.

Domain model
The Domain Model pattern involves creating a conceptual model of the domain that incorporates both behavior and data. This model represents the real-world entities and the relationships between them, serving as the foundation for the system’s business logic.

Example: In an e-commerce application, the domain model would include entities like Customer, Order, and Product, along with their interactions.

Layers
The Layers pattern structures applications into hierarchical layers, each with specific responsibilities. Common layers include presentation, business logic, and data access. This separation enhances maintainability and scalability.

Example: A typical web application might have a presentation layer for the user interface, a business logic layer for processing data, and a data access layer for database interactions.

Model-View-Controller
The MVC pattern divides an application into three interconnected components:

  • Model: Manages the data and business logic.
  • View: Displays the data to the user.
  • Controller: Handles user input and updates the model and view accordingly.

This separation facilitates modularity and ease of maintenance.

Example: In a web application, the model represents the database, the view is the HTML/CSS interface, and the controller processes user requests and updates the model and view.

Presentation-Abstraction-Control
The PAC pattern organizes an interactive system into a hierarchy of cooperating agents, each consisting of three components:

  • Presentation: Manages the user interface.
  • Abstraction: Encapsulates the business logic and data.
  • Control: Handles the communication between presentation and abstraction.

This structure supports complex user interactions and enhances system flexibility.

Example: In a complex user interface, such as a CAD system, PAC can manage different tools and views, each as a separate agent with its own presentation, abstraction, and control components.

Microkernel
The Microkernel pattern separates the core system functionality from extended features, allowing the addition of plug-in modules. This architecture is beneficial for systems that require flexibility and adaptability.

Example: An operating system kernel that provides basic services, with additional functionalities like file systems or device drivers added as modules.

Reflection
The Reflection pattern enables a system to adapt its behavior at runtime by modifying its own structure and behavior. It involves a meta-level that provides information about the system’s capabilities and a base level that performs the actual operations.

Example: A programming language runtime that allows introspection and modification of object structures at runtime, such as Python’s inspect module.

Pipes and filters
The Pipes and Filters pattern processes data through a sequence of components (filters), each performing a specific transformation. The data flows through pipes connecting these filters, allowing for flexible and reusable processing steps.

Example: A compiler that processes source code through lexical analysis, parsing, semantic analysis, optimization, and code generation stages, each implemented as a separate filter.

Shared repository
The Shared Repository pattern involves a central data store that multiple components can access and modify. This approach promotes data consistency and simplifies data sharing among components.

Example: A version control system where the central repository stores all code changes, and developers commit and retrieve code from this shared repository.

Blackboard
The Blackboard pattern is used for problems that have no deterministic solution strategies. It consists of three components:

  • Blackboard: A global memory structure that stores the current state of the solution.
  • Knowledge Sources: Independent modules that contribute to the solution based on their expertise.
  • Control Component: Manages the flow of information and coordinates the knowledge sources.

This pattern is suitable for complex problem-solving tasks like speech recognition or AI systems.

Example: An AI system for medical diagnosis where various expert modules analyze patient data and contribute to the diagnosis through a shared blackboard.

Domain object
The Domain Object pattern represents real-world entities within the domain model, encapsulating both data and behavior. These objects are the building blocks of the domain model and interact to implement business logic.

Example: In a library management system, Book, Member, and Loan would be domain objects representing the entities and their interactions within the system.

Distribution infrastructure

Understanding distribution infrastructure patterns is essential for designing robust and scalable distributed systems.

Message channel
A Message Channel is a conduit through which messages are sent between systems or components. It decouples the sender and receiver, allowing them to operate independently. Channels can be point-to-point, where a message is sent to a single receiver, or publish-subscribe, where messages are broadcast to multiple subscribers.

Example: In a microservices architecture, services communicate via message channels to ensure loose coupling and scalability.

Message endpoint
A Message Endpoint is an interface that enables a system to send or receive messages. It acts as the connection point to a message channel, handling the transmission and reception of messages. Endpoints can be configured to process messages synchronously or asynchronously.

Example: In an enterprise application, a message endpoint could be a web service interface that processes incoming XML messages.

Message translator
A Message Translator converts messages from one format to another to facilitate communication between systems with different data formats or protocols. This ensures interoperability and seamless integration.

Example: In a supply chain system, a message translator can convert EDI messages to XML format for internal processing.

Message route
A Message Router directs messages to the appropriate destination based on specific criteria, such as message content or headers. This pattern enables dynamic routing and processing of messages.

Example: In a payment processing system, a message router can direct transactions to different payment gateways based on the transaction type.

Publisher-subscriber
The Publisher-Subscriber pattern allows multiple subscribers to receive messages from a publisher. Subscribers express interest in specific topics, and the publisher broadcasts messages to all interested subscribers.

Example: In a news distribution system, subscribers can receive updates on topics they are interested in, such as sports or politics.

Broker
A Broker acts as an intermediary that facilitates communication between clients and servers. It manages message routing, transformation, and distribution, enabling decoupled and scalable interactions.

Example: In a middleware system, a broker can handle communication between different services, managing message queues and ensuring reliable delivery.

Client proxy
A Client Proxy provides a local representative for a remote service, allowing clients to interact with remote services as if they were local. It handles the complexities of remote communication, such as serialization and network protocols.

Example: In a distributed object system, a client proxy can represent a remote object, enabling method calls as if the object were local.

Requestor
A Requestor is responsible for initiating a request to a remote service. It prepares the request, manages communication details, and handles the response.

Example: In a web service client, the requestor constructs SOAP messages, sends them to the server, and processes the responses.

Invoker
An Invoker receives requests from a requestor and invokes the appropriate service or method on the server side. It manages the execution of the requested operation and returns the result to the requestor.

Example: In a remote procedure call (RPC) system, the invoker receives the call from the client and executes the corresponding function on the server.

Client request handler
A Client Request Handler manages the client’s side of a request, including marshalling data, establishing communication channels, and sending the request to the server.

Example: In a distributed application, the client request handler serializes the request data and sends it over the network to the server.

Server request handler
A Server Request Handler manages the server’s side of a request, including unmarshalling data, invoking the appropriate service, and sending the response back to the client.

Example: In a distributed application, the server request handler deserializes incoming requests, processes them, and sends back the results.

More info about some patterns from this section here and in book Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions by Gregor Hohpe and Bobby Woolf.

Adaptation and execution

Understanding various design patterns is essential for creating flexible and maintainable software architectures. Below is an overview of several key patterns related to adaptation and execution.

Bridge
The Bridge pattern decouples an abstraction from its implementation, allowing both to vary independently. This is achieved by defining a separate interface for the implementation, which can be extended without affecting the abstraction.

Example: In a graphics application, you might have an Image abstraction with different implementations like PNGImage and JPEGImage. The bridge pattern allows you to add new image formats without modifying the existing codebase.

Object Adapter
The Object Adapter pattern enables incompatible interfaces to work together by wrapping an existing class with a new interface. This allows classes with incompatible interfaces to collaborate.

Example: If you have a Rectangle class and need it to work with a Shape interface, you can create an adapter that implements Shape and delegates calls to an instance of Rectangle.

Chain of responsibility
The Chain of Responsibility pattern allows a request to pass through a chain of handlers until one of them handles it. This decouples the sender and receiver of the request, promoting flexibility in assigning responsibilities.

Example: In a logging system, a message might pass through multiple handlers like ConsoleLogger, FileLogger, and EmailLogger, each deciding whether to handle the message or pass it along.

Interpreter
The Interpreter pattern defines a grammatical representation for a language and provides an interpreter to process sentences in that language. It’s useful for designing languages or expressions.

Example: A simple calculator that interprets and evaluates mathematical expressions entered as strings.

Interceptor
The Interceptor pattern allows additional behavior to be added to a framework without modifying the core code. It intercepts incoming or outgoing requests and processes them accordingly.

Example: In a web application, interceptors can be used to log requests, perform authentication, or modify responses before they reach the client.

Visitor
The Visitor pattern separates an algorithm from the object structure it operates on by moving the algorithm into a separate object. This allows adding new operations without modifying the object structure.

Example: In a document editor, you might have different elements like Text, Image, and Table. A visitor can be used to spell-check all elements without changing their classes.

Decorator
The Decorator pattern allows behavior to be added to individual objects, either statically or dynamically, without affecting the behavior of other objects from the same class. It provides a flexible alternative to subclassing for extending functionality.

Example: In a GUI framework, you might have a Window class. Decorators like ScrollDecorator or BorderDecorator can add scrolling or border features to windows.

Execute-Around Object
The Execute-Around Object pattern ensures that certain setup and cleanup operations are performed around the execution of a core operation. This is useful for managing resources like file handles or database connections.

Example: In a database application, an execute-around object can ensure that a connection is opened before a query and closed afterward, even if an exception occurs.

Template method
The Template Method pattern defines the skeleton of an algorithm in a method, deferring some steps to subclasses. This allows subclasses to redefine certain steps without changing the algorithm’s structure.

Example: In a game framework, a Game class might have a play method that outlines the game’s steps, while subclasses implement specific game logic.

Strategy
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. This allows the algorithm to vary independently from clients that use it.

Example: In a sorting application, different sorting algorithms like QuickSort and MergeSort can be encapsulated as strategies, allowing the client to choose the appropriate one at runtime.

Null Object
The Null Object pattern provides an object with a neutral (“null”) behavior, avoiding the need to check for null values. This simplifies code by eliminating null checks.

Example: In a logging system, a NullLogger can be used when no logging is required, implementing the same interface as other loggers but performing no operations.

Wrapper Facade
The Wrapper Facade pattern provides a simplified interface to a complex subsystem. It “wraps” the subsystem’s interfaces to make them easier to use.

Example: A database access layer that provides simple methods for querying data, hiding the complexity of the underlying database operations.

Declarative component configuration
The Declarative Component Configuration pattern allows the configuration of components using declarative means, such as configuration files or annotations, rather than imperative code. This promotes flexibility and separation of concerns.

Example: In a Java application, using annotations to configure dependency injection, allowing the wiring of components without explicit code.

More info in book Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma

Resource management

Effective resource management is crucial in software development to ensure optimal performance and resource utilization. Below is an overview of key patterns and concepts related to resource management

Container
A Container is a runtime environment that manages the lifecycle, configuration, and dependencies of components. It provides services such as instantiation, configuration, and destruction, facilitating component management.

Example: In Java EE, the application server acts as a container, managing enterprise beans and their interactions.

Component configurator
The Component Configurator pattern allows the dynamic configuration and reconfiguration of components at runtime without stopping the system. This enables flexible and adaptive systems.

Example: A web server that can load and unload modules (e.g., authentication handlers) without restarting.

Object manager
An Object Manager oversees the creation, usage, and destruction of objects, ensuring efficient resource utilization and preventing leaks.

Example: A database connection manager that pools connections and allocates them to requesting clients.

Lookup
The Lookup pattern provides a mechanism to locate and access resources or services, often through a registry or directory service.

Example: A DNS server that resolves domain names to IP addresses.

Virtual Proxy
A Virtual Proxy controls access to a resource by acting as a stand-in, loading the actual resource only when necessary.

Example: An image viewer that displays a placeholder until the high-resolution image is fully loaded.

Lifecycle callback
Lifecycle Callbacks are hooks provided by a framework or container that allow developers to execute code at specific points in an object’s lifecycle, such as creation or destruction.

Example: In Java EE, the @PostConstruct annotation marks a method to be called after dependency injection is complete.

Task coordinator
A Task Coordinator manages the execution of tasks, handling dependencies, scheduling, and resource allocation to ensure efficient operation.

Example: A build system that coordinates compilation, testing, and packaging tasks.

Resource pool
The Resource Pool pattern maintains a collection of reusable resources, such as threads or database connections, to optimize resource utilization and reduce overhead.

Example: A thread pool that reuses a fixed number of threads to execute multiple tasks.

Resource cache
A Resource Cache stores frequently accessed resources in memory to reduce access time and improve performance.

Example: A web browser cache that stores images and scripts to speed up page loading.

Lazy Acquisition
The Lazy Acquisition pattern delays the acquisition of a resource until it is actually needed, conserving resources and improving startup times.

Example: A database connection that is established only when a query is executed.

Eager Acquisition
The Eager Acquisition pattern acquires resources at the earliest opportunity, ensuring their availability when needed and potentially improving performance.

Example: Loading all necessary plugins during application startup.

Partial Acquisition
The Partial Acquisition pattern involves acquiring only a subset of resources initially, with the ability to acquire more as needed.

Example: A video streaming service that buffers the initial portion of a video and continues buffering as playback progresses.

Activator
An Activator is responsible for initializing and starting components or services, often on demand, to manage resources efficiently.

Example: A service activator in an enterprise application that starts services when a message is received.

Evictor
The Evictor pattern manages resource lifecycles by monitoring usage and reclaiming resources that are no longer needed, preventing leaks and optimizing utilization.

Example: A cache eviction policy that removes least recently used items to free up space.

Leasing
The Leasing pattern allocates resources to clients for a specified period, requiring renewal to continue usage, thus preventing indefinite resource occupation.

Example: A distributed lock with a lease time that must be renewed to maintain the lock.

Automatic Garbage Collection
Automatic Garbage Collection is a memory management feature that automatically reclaims memory occupied by objects no longer in use, preventing memory leaks.

Example: The Java Virtual Machine’s garbage collector that automatically deallocates unused objects.

Counting handle
The Counting Handle pattern uses reference counting to manage the lifecycle of shared resources, releasing them when no longer referenced.

Example: A shared pointer in C++ that deletes the managed object when the last reference is destroyed.

Abstract Factory
The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.

Example: A GUI toolkit that creates windows, buttons, and menus for different operating systems.

Builder
The Builder pattern separates the construction of a complex object from its representation, allowing the same construction process to create different representations.

Example: A builder that constructs various configurations of a house, such as different floor plans and materials.

Factory method
The Factory Method pattern defines an interface for creating an object but allows subclasses to alter the type of objects that will be created.

Example: A document application that creates different types of documents (e.g., text, spreadsheet) using factory methods.

Disposal Method
A Disposal Method is a mechanism provided to release resources explicitly, ensuring proper cleanup and preventing leaks.

Example: In C#, the Dispose method is called to release unmanaged resources held by an object.

You can find more info about resource management patterns in a following book. Pattern-Oriented Software Architecture, Volume 3: Patterns for Resource Management

Database access

Understanding various database access patterns is essential for designing efficient and maintainable data access layers in software applications. Below is an overview of key patterns

Database access layer
A Database Access Layer (DAL) serves as an intermediary between the application and the database, encapsulating all the logic required to interact with the database. This abstraction promotes separation of concerns, making the application more modular and easier to maintain.

Example: In a .NET application, the Entity Framework can act as a DAL, providing an object-oriented approach to database interactions.

Data mapper
The Data Mapper pattern separates the in-memory objects from the database schema, allowing both to evolve independently. It maps fields from a database to properties in an object, handling the transfer of data between them.

Example: Hibernate in Java is a popular implementation of the Data Mapper pattern, facilitating object-relational mapping (ORM).

Row data gateway
A Row Data Gateway is an object that acts as a gateway to a single record in a data source. Each instance of the gateway corresponds to a row in the table, encapsulating the database access for that row.

Example: In a PHP application, an instance of a UserGateway class might represent a single row in the users table, providing methods to retrieve and update that user’s data.

Table data gateway
The Table Data Gateway pattern provides an interface to a database table. It encapsulates all the SQL operations for that table, offering methods to perform CRUD (Create, Read, Update, Delete) operations.

Example: A ProductGateway class in a Ruby application might provide methods like findAll, findByID, insert, and update to interact with the products table.

Active record
The Active Record pattern combines the data access and business logic in a single object. Each object instance corresponds to a row in the database table, and the class provides methods to interact with the database.

Example: In Ruby on Rails, models like User or Post are implementations of the Active Record pattern, where each model instance represents a row in the corresponding database table.

Few More Concepts In Software Architecture

CQRS

CQRS is a design pattern that separates the read operations (queries) from write operations (commands) in a system. It ensures that each operation has its own distinct model, optimized for its specific task.

Key Characteristics:

  1. Separation of Concerns:
    • Commands modify data (write model).
    • Queries fetch data (read model).
  2. Scalability: The read and write sides can be scaled independently.
  3. Different Models: The write model can be normalized for transactional consistency, while the read model is often denormalized for performance.

Benefits:

  • Improved performance, scalability, and security.
  • Easier to apply optimizations for reads or writes independently.

Challenges:

  • Increased complexity due to having separate models.
  • Synchronization between the read and write models.

Event Sourcing

Event sourcing is a design pattern where the state of an application is stored as a sequence of events. Instead of storing the current state, every change to the state is recorded as an event.

How It Works:

  • Event Log: Events are stored in an append-only log.
  • State Reconstruction: The current state is derived by replaying events from the log.
  • Event Handlers: Business logic is applied to interpret and process these events.

Benefits:

  • Auditing: Complete history of changes is available.
  • Replayability: You can reconstruct the application state at any point in time.
  • Integration: Events can be used to notify external systems.

Challenges:

  • Event versioning can become complex as the system evolves.
  • Large event logs can lead to performance issues unless snapshots are introduced.

Hexagonal Architecture (Ports and Adapters)

Hexagonal Architecture, also known as the Ports and Adapters architecture, aims to create loosely coupled components of a system. It ensures that the core business logic is isolated and independent of external systems like databases, APIs, or user interfaces.

Structure:

  • Core Domain: Contains the business logic (use cases, entities).
  • Ports: Interfaces that define the entry points (inbound) or integration points (outbound).
  • Adapters: Implement the ports to interact with external systems.

Benefits:

  • Easy to test the core logic in isolation.
  • Promotes a clean separation of concerns.
  • Allows swapping external systems (e.g., database, UI) without affecting the core logic.

Challenges:

  • Increased upfront design effort.
  • Developers need to understand the principles well to avoid over-complicating the system.

RPC (Remote Procedure Call)

RPC is a protocol that allows executing a function or procedure on a remote server as if it were local. The client sends a request, and the server executes the procedure and returns the result.

Common Implementations:

  • gRPC
  • JSON-RPC
  • XML-RPC

Benefits:

  • Simplifies the interaction with remote services.
  • Abstracts the network communication layer.

Challenges:

  • Tight coupling between client and server (requires both to understand the same procedure definitions).
  • May introduce latency and network reliability issues.

SOA (Service-Oriented Architecture)

SOA is a design principle where software is organized as a collection of services. Each service is a standalone unit that performs a specific business function and communicates with other services via a network.

Characteristics:

  • Loose Coupling: Services are independent and interact using well-defined protocols.
  • Reusability: Services can be reused across different applications.
  • Standardized Communication: Often uses protocols like SOAP or REST.

Benefits:

  • Facilitates reuse and modularity.
  • Enables scalability by distributing services across multiple servers.

Challenges:

  • Overhead in managing and orchestrating services.
  • Potential performance impact due to network communication.

Microservices

Microservices Architecture is a design style where an application is built as a collection of small, independent services, each focused on a specific business capability. Each service runs in its own process, communicates with other services via lightweight protocols (e.g., REST, gRPC), and is independently deployable.

Key Characteristics:

  • Decentralization: Microservices are self-contained and manage their own data and logic.
  • Technology Agnosticism: Each service can use different programming languages, databases, or frameworks.
  • Scaling: Services can be scaled independently.

How It Differs from SOA: While SOA also focuses on service-based architecture, microservices:

  • Are smaller in scope and more granular.
  • Avoid shared data layers (unlike SOA, which often uses a centralized data store).
  • Emphasize automation for deployment and monitoring (DevOps).

Benefits:

  • Resilience: Failure in one service doesn’t bring down the entire system.
  • Flexibility: Teams can work on services independently, even using different technologies.
  • Scalability: Scale only the services that require additional resources.

Challenges:

  • Complexity: Distributed systems introduce challenges like inter-service communication, monitoring, and fault tolerance.
  • Consistency: Ensuring data consistency across services can be difficult.
  • DevOps Overhead: Requires robust CI/CD pipelines and monitoring tools.

Summary

That will be all for today. Of course aforementioned list is not complete list of the patterns. Especially interesting for software developers can be patterns described on awesome page refactoring.guru. I strongly recommend reading it.

Thanks for reading. I hope you also enjoyed reading about patterns like me.