Design Patterns
Architectural Styles
Common system design styles
- Big compute
- large-scale computing resources to perform complex computations and data processing tasks
- Examples: High-performance computing (HPC), Genomic sequencing, Climate modeling, climate modeling, financial modeling, cryptocurrency mining, Machine learning and deep learning, Drug discovery and design
- Azure example
- Big data
- Broker
- A pattern in which a central broker is used to manage the communication between different components of a system, allowing them to remain loosely coupled.
- Domain Driven Design
- Event Bus
- A pattern in which a central event bus is used to decouple different components of a system by allowing them to communicate with each other through a publish-subscribe mechanism.
- Event-driven architecture (EDA)
- An event-driven architecture is a design pattern in which event producers and event consumers are decoupled, allowing for flexibility and scalability.
- Importance of Event Driven Architecture
- The Pros and Cons of Event Driven Architecture
- Azure example
- Microservices vs Event-Driven Architecture
- Microservices
- A microservices architecture is an approach to building a system as a collection of small, independently deployable services that communicate with each other through APIs. An imlpementation ofthe SOA pattern.
- Azure example
- Microservices vs Event-Driven Architecture
- Monolithic
- A monolithic architecture is a traditional approach in which an application is built as a single, self-contained unit.
- N-tier application
- Pipe and Filter
- A pattern in which data is transformed as it passes through a series of pipes (components) that filter the data and modify it in some way.
- Service-oriented (SOA)
- A service-oriented architecture is a design pattern in which a system is built as a collection of services that communicate with each other to achieve a specific goal. Microservices is a specific implementation of the SOA pattern that involves building a system as a collection of small, independently deployable services that communicate with each other through a lightweight mechanism such as HTTP.
- SOA is a broader concept that covers the design of a system as a collection of services while Microservices is a specific implementation of that design.
- SOA is more suitable for large-scale, enterprise systems, while Microservices is more suited for smaller, agile projects.
- A service-oriented architecture is a design pattern in which a system is built as a collection of services that communicate with each other to achieve a specific goal. Microservices is a specific implementation of the SOA pattern that involves building a system as a collection of small, independently deployable services that communicate with each other through a lightweight mechanism such as HTTP.
- Web-queue-worker
Other systems design styles
- Actor Model
- An actor model is a mathematical model for concurrent computation that organizes a system as a collection of independent actors that communicate with each other by sending messages.
- Backends for Frontends
- "where applications have one backend per user experience, instead of having only one general-purpose API backend"
- https://aws.amazon.com/blogs/mobile/backends-for-frontends-pattern/
- Blackboard
- A pattern in which a central blackboard is used to store the state of a system and allow different components to read and write to it.
- The Clean Architecture
- Separates the system into several layers, with the outer layers depending on the inner layers.
- This makes the system more flexible, maintainable, and testable.
- Client-server
- A client-server architecture is a design pattern in which a client sends requests to a server, which then processes the requests and returns the results.
- Event Sourcing and CQRS
- A combination of Event sourcing and Command Query Responsibility Segregation(CQRS) pattern where the system stores all changes to the state of the system as a sequence of events and separates the handling of reading and writing state of the system.
- Hexagonal Architecture
- Also known as "Ports and Adapters" architecture, this pattern separates the core business logic of a system from the external interfaces, such as web or database interfaces, allowing for flexibility and ease of testing.
- Layered
- A layered architecture is a design pattern in which a system is broken down into layers, each with a specific purpose, such as presentation, business logic, and data access.
- MVC (Model View Controller)
- This pattern separates the application into three components: the model, which represents the data and the business logic, the view, which represents the user interface, and the controller, which manages the interaction between the model and the view.
- Used to separate the concerns of a user interface, while N-Tier is a pattern that is used to separate the concerns of an entire application into multiple layers.
- Object Pool A pattern in which a pool of objects is maintained, so that objects can be reused, instead of creating new ones, to improve performance.
- Object-Relational Mapping (ORM) A pattern in which an object-oriented representation of data is mapped to a relational database, so that data can be easily stored and retrieved.
- Onion Architecture
- A variation of layered architecture, the onion architecture organizes the different layers of a system as concentric circles, with the core business logic at the center, and the external interfaces at the outer layers.
- Service Mesh
- A pattern that involves using a dedicated infrastructure layer to manage service-to-service communication in a microservices-based architecture. This layer typically includes features such as service discovery, load balancing, and traffic management, allowing for more robust and scalable communication between services.
- Space-based Architecture This approach organizes software as a collection of loosely-coupled, independently-deployable services that communicate with each other via message queues.
Ambassador
- Service to act as a proxy between clients and the microservices they need to access.
- Allows for a decoupling of the client from the microservices, and can provide benefits such as:
Used for:
- Organizations that have a large number of microservices and need to manage the traffic between them in a more structured way.
- When you need to add security, observability or other cross-cutting concerns to your microservices without modifying the code of the microservices themselves.
Pros:
- Improved security, as the Ambassador can handle tasks such as authentication and authorization before forwarding requests to the microservices.
- Improved scalability, as the Ambassador can handle tasks such as load balancing and service discovery without the need for the client to be aware of these details.
- Improved observability, as the Ambassador can handle tasks such as logging and monitoring of requests without the need for the client to be aware of these details.
Cons:
- Increased latency, as requests must pass through the Ambassador before reaching the microservices.
- Increased complexity, as the Ambassador must be properly configured and maintained.
- Increased risk of single point of failure, as the Ambassador becomes a critical component of the overall system.
Ambassador Step by Step:
- Identify the services that need to communicate: Determine which microservices need to communicate with each other, and identify the communication points between them.
- Create an Ambassador for each service: For each service that needs to communicate, create an Ambassador service. The Ambassador service should act as a proxy for the original service, handling the communication between the two services.
- Implement the Ambassador service: Implement the Ambassador service using a language and framework of your choice. It should handle the communication between the services, such as routing requests, handling authentication and authorization, and translating data formats.
- Update the client service to use the Ambassador: Update the client service to communicate with the Ambassador service instead of the original service. This can be done by changing the service endpoint or the service discovery mechanism.
- Monitor and test the communication: Monitor the communication between the services, and test the communication to ensure that it is working correctly.
- Scale the Ambassador services as needed: As the system grows and the number of requests increases, you may need to scale the Ambassador services to handle the increased load.
- Use the Ambassador for service discovery: Ambassador service can also be used for service discovery. By using the Ambassador for service discovery, you can avoid having to update the client service each time a service endpoint changes.
- Fallback mechanism: Add a fallback mechanism to the Ambassador service so that if the original service is unavailable, the Ambassador can still respond to the client with a default response or redirect the request to another service.
Big Compute
High-performance computing (HPC): This involves using clusters of computers, often with specialized hardware such as GPUs or FPGAs, to perform complex scientific and engineering simulations, weather forecasting, and other tasks that require large amounts of computational power.
Data warehousing and analytics: This involves using large-scale data storage and processing systems, such as Hadoop or Spark clusters, to store and analyze large amounts of data from various sources, such as social media, IoT devices, or financial transactions.
Machine learning and deep learning: This involves using large-scale computing resources, such as GPU clusters, to train and deploy machine learning and deep learning models on large datasets.
Cryptocurrency mining: This involves using large-scale computing resources to perform complex mathematical calculations to validate and record transactions on a blockchain network.
Genomic sequencing: This involves using large-scale computing resources to process and analyze large amounts of genetic data, such as DNA sequences, in order to understand the genetic basis of diseases and other biological phenomena.
Drug discovery and design: This involves using large-scale computing resources to perform complex simulations of chemical compounds and their interactions with biological systems, in order to discover new drugs and design more effective drug therapies.
Climate modeling: This involves using large-scale computing resources to perform complex simulations of the Earth's climate, in order to understand and predict the effects of climate change and develop effective mitigation and adaptation strategies.
Big Compute Step by Step:
Identify the computational needs: Understand the computational needs of the application, such as the types of computations to be performed, the size of the input data, and the desired output.
Determine the hardware requirements: Based on the computational needs, determine the hardware requirements, such as the number of processors, the amount of memory, and the storage capacity needed.
Select the appropriate hardware: Select the appropriate hardware, such as CPU clusters, GPU clusters, or FPGA clusters, to meet the hardware requirements.
Design the network architecture: Design the network architecture, such as the topology, the switch fabric, and the interconnects, to ensure high-speed communication between the hardware components.
Choose the appropriate software stack: Choose the appropriate software stack, such as Hadoop, Spark, or MPI, to handle the distributed computing and data processing tasks.
Optimize the system for performance: Optimize the system for performance by tuning the software and hardware parameters, such as the number of threads, the memory allocation, and the network settings.
Monitor and test the system: Monitor the system to ensure that it is running efficiently, and test the system to ensure that it is meeting the computational needs of the application.
Scale the system as needed: As the computational needs of the application grow, scale the system by adding more hardware and software resources.
Add security: Ensure that you have proper security measures in place to protect the system from unauthorized access and data breaches.
Maintanance: Regularly maintain the system by updating software, monitoring the performance, and replace hardware components as needed.
Big compute systems can be complex to design and operate, it's important to have a clear understanding of the computational needs and the available hardware and software options before proceeding with the design. Also, the system should be monitored and tested regularly to ensure that it is meeting the computational needs of the application, and to identify and resolve any performance issues that may arise.
Big Data
- Big data in systems architecture refers to the use of large-scale data storage and processing systems to handle the volume, velocity, and variety of data generated by modern application
- Data warehousing: This involves using large-scale data storage systems, such as Hadoop or Spark clusters, to store and manage large amounts of structured and unstructured data from various sources, such as social media, IoT devices, or financial transactions.
- Data lakes: This involves using large-scale data storage systems, such as Hadoop or Spark clusters, to store and manage large amounts of raw data in its native format, allowing for more flexible and cost-effective data processing and analysis.
- Stream processing: This involves using systems such as Apache Kafka, Apache Storm, or Apache Flink to process and analyze large amounts of real-time data streams, such as social media feeds, log files, or sensor data, in near real-time.
- NoSQL databases: This involves using non-relational databases, such as MongoDB, Cassandra, or Hbase, to store and manage large amounts of unstructured data, such as text, images, or videos, in a flexible and scalable way.
- Search and discovery: This involves using systems such as Elasticsearch, Solr, or Lucene to index and search large amounts of data, such as text documents, images, or videos, in order to enable fast and efficient data discovery and retrieval.
- Machine learning and deep learning: This involves using large-scale data storage and processing systems, such as Hadoop or Spark clusters, to train and deploy machine learning and deep learning models on large datasets.
- Predictive modeling: This involves using large-scale data storage and processing systems, such as Hadoop or Spark clusters, to perform complex statistical analyses and build predictive models, such as customer segmentation, fraud detection, or risk management.
Big Data Step by Step
Identify the data needs: Understand the data needs of the application, such as the types of data to be collected, the volume of data to be stored, and the desired data access patterns.
Determine the data storage and processing requirements: Based on the data needs, determine the data storage and processing requirements, such as the amount of storage capacity, the data processing performance, and the data access patterns.
Select the appropriate data storage and processing technology: Select the appropriate data storage and processing technology, such as Hadoop, Spark, or NoSQL databases, to meet the data storage and processing requirements.
Design the data pipeline: Design the data pipeline, such as the data ingestion, data processing, and data access components, to ensure efficient and effective data flow and processing.
Choose the appropriate data management tools: Choose the appropriate data management tools, such as data integration, data quality, and data governance tools, to ensure data consistency, completeness, and accuracy.
Optimize the system for performance: Optimize the system for performance by tuning the software and hardware parameters, such as the number of threads, the memory allocation, and the network settings.
Monitor and test the system: Monitor the system to ensure that it is running efficiently, and test the system to ensure that it is meeting the data needs of the application.
Scale the system as needed: As the data needs of the application grow, scale the system by adding more hardware and software resources.
Add security: Ensure that you have proper security measures in place to protect the system from unauthorized access and data breaches.
Maintanance: Regularly maintain the system by updating software, monitoring the performance, and replace hardware components as needed.
Broker
- Is an architectural pattern that can be used to decouple a system for better scalability and maintainability.
- Introduces an intermediate layer between the client and the server, which acts as a broker for communication.
- Enables a system to be easily extended with additional functionality.
- Enables the system to be more resilient against changes in other parts of the system.
- Provides a way to separate the business logic from the technical implementation.
- Enables the system to be more agile in responding to changes in the environment.
- Simplifies the development process by allowing developers to focus on the business logic rather than technical implementation.
- Enables multiple clients to communicate with the same server.
- Provides an asynchronous approach to communication, allowing for better scalability and availability.
Pros:
- It increases the flexibility of the system by allowing the different components to interact without knowing the details of each other.
- It isolates the changes in the system, making it easier to modify and extend the system.
- It is a well-known and proven design pattern, so it is easy to implement and maintain.
Cons:
- It can add complexity to the system, as it requires the development of additional components.
- It can make the system more difficult to debug, as the broker component can make it difficult to trace the source of errors.
- It can add overhead to the system, as the broker component needs to be constantly running to coordinate the different components.
Define the interfaces for the components involved in the broker pattern:
- The Broker interface defines the operations to handle communication between components.
- The Component interface defines the operations that can be performed by a component.
Implement the Component classes:
- Each component class implements the Component interface and encapsulates the logic for a specific task.
Implement the Broker class:
- The Broker class implements the Broker interface and acts as a mediator between components.
- It maintains a list of components and forwards messages between them.
Register the components with the Broker:
- Each component needs to be registered with the Broker to receive and send messages.
Implement the communication logic:
- Components communicate with each other through the Broker by sending and receiving messages.
- The Broker forwards the messages to the appropriate component.
Test the implementation:
- Verify that the Broker correctly handles communication between components.
- Ensure that the components can receive and send messages as expected.
Domain Driven Design
- Domain-Driven Design (DDD) is an approach to software development that focuses on the business domain and domain logic.
- DDD emphasizes the importance of understanding the problem domain, and using that understanding to drive the design of the software.
- DDD uses a variety of tools and techniques, such as Domain Models, Bounded Contexts, and Ubiquitous Language, to help ensure that the software accurately reflects the domain.
- DDD is particularly well-suited for complex and large-scale software systems, where traditional approaches may struggle to capture the complexity of the domain.
- To use DDD, start by identifying the key concepts and entities in the domain, and then use those concepts to drive the design of the software.
- Use a ubiquitous language to ensure that the same terms and concepts are used consistently throughout the codebase and between developers, business stakeholders, and domain experts.
- Use Bounded Contexts to separate different parts of the domain that have different contexts and use cases.
- Continuously verify and validate the design with domain experts to ensure that it accurately reflects the domain.
- Use Domain Models to encapsulate the domain logic and business rules in the system.
- Use Event-Driven Architecture and CQRS (Command Query Responsibility Segregation) pattern to decouple the read and write parts of the system for scalability and maintainability.
Step by Step:
- Understand the problem domain: Start by gaining a deep understanding of the problem domain. Conduct research, speak to domain experts, and understand the business requirements. This will help you identify the key concepts and entities in the domain.
- Identify the Bounded Contexts: Divide the problem domain into smaller, distinct areas called Bounded Contexts. Each Bounded Context should have its own set of concepts and entities that are relevant to it.
- Define the Ubiquitous Language: Use a common vocabulary, or "ubiquitous language," to ensure that the same terms and concepts are used consistently throughout the codebase and between developers, business stakeholders, and domain experts.
- Create Domain Models: Use the concepts and entities identified in step 1 and 2 to create Domain Models. These models should encapsulate the domain logic and business rules in the system.
- Implement the Domain Models: Use the Domain Models to drive the implementation of the software. Use the ubiquitous language to name classes, methods, and variables, and make sure that the implementation reflects the domain.
- Use Event-Driven Architecture and CQRS: To decouple the read and write parts of the system for scalability and maintainability, use the Event-Driven Architecture and Command Query Responsibility Segregation pattern.
- Continuously validate the design: Continuously verify and validate the design with domain experts to ensure that it accurately reflects the domain.
- Refactor and iterate as needed: As you gain a deeper understanding of the domain, you may need to refactor and iterate on the design. Make sure to keep the domain experts involved in the process.
Event-Driven Architecture
- Identify the events: The first step in implementing event-driven architecture is to identify the events that will drive the system's behavior. These events can be triggered by external sources such as user interactions, sensor data, or external API calls, or by internal processes within the system.
- Define event data: Once the events have been identified, the next step is to define the data that will be associated with each event. This should include the data's structure, format, and any validation rules that should be applied.
- Create event producers: Next, create the event producers, which are the components or services within the system that will generate the events. These producers should be designed to send the events to a centralized event bus or message queue.
- Create event consumers: Next, create the event consumers, which are the components or services within the system that will respond to the events. These consumers should be designed to subscribe to the events on the centralized event bus or message queue.
- Implement event handling: Implement the event handling logic within the consumers, which will determine how the system will respond to each event. This can include updating data, triggering other events, or calling external services.
- Test and validate: Before deploying the event-driven architecture, it's important to test and validate the system to ensure it's working as expected. This can include testing the event producers, consumers, and event bus or message queue, as well as testing the system's overall behavior and performance.
- Deploy and monitor: Once the system has been tested and validated, it can be deployed to a production environment. It's important to monitor the system to ensure it's running as expected and to identify and troubleshoot any issues that arise.
Pros of Event-Driven Architecture (EDA)
Loose Coupling: EDA promotes loose coupling between components, which makes the system more flexible and easier to change.
Scalability: EDA allows for horizontal scaling, as new consumers can be added to handle increased event traffic.
Asynchronous Processing: EDA allows for asynchronous processing, which can improve the overall performance and responsiveness of the system.
Real-time Processing: EDA enables real-time processing of events, allowing for immediate response to changing conditions.
Improved Resilience: EDA can improve the resilience of the system by allowing for the decoupling of critical components, reducing the impact of failures.
Cons of Event-Driven Architecture (EDA)
Complexity: EDA can add complexity to the system, making it more difficult to understand and maintain.
Debugging: EDA can make debugging more difficult as it can be challenging to trace the flow of events through the system.
Event Storming: If not implemented properly, EDA can lead to an event storming, where too many events are generated and the system can't handle it.
Latency: EDA can introduce latency as events have to be sent and processed.
Event Ordering: EDA can be affected by the order of events, making it challenging to ensure that events are processed in the correct order.