What Is System Design?
System design is the process of defining the architecture, components, modules, interfaces, and overall structure of a system to meet specified requirements and goals. It involves creating a blueprint that outlines how various elements interact and work together to achieve the desired functionality, performance, and reliability.
This is part of a series of articles about system design.
In this article:
- System Design vs. Software Design
- System Design Concepts
- System Design Architecture and Patterns
- System Design Examples
- Tools and Techniques of System Design
System Design vs. Software Design
System design and software design are related but distinct concepts in the context of software development. They both involve creating a blueprint for a system or application but focus on different aspects and levels of abstraction. Here are several important concepts that play a role in modern system design.
Horizontal and Vertical Scaling
Horizontal scaling is the process of adding more nodes (servers or instances) to a system to distribute the load and increase its capacity. This approach improves system performance and reliability by allowing it to handle more requests simultaneously. Horizontal scaling is often used in cloud environments and can be achieved through load balancing or sharding (splitting a software system into a few instances, each holding part of the data).
Vertical scaling involves adding more resources (CPU, memory, storage) to an existing node or server, thus increasing its capacity to handle a higher workload. This can be achieved by upgrading the hardware or allocating more resources in a virtualized environment. Vertical scaling has limitations due to hardware constraints and can result in downtime during the scaling process.
Redundancy and Replication
Redundancy is the duplication of critical system components or data to improve reliability and fault tolerance. Replication is the process of creating and maintaining multiple copies of data across different nodes in a distributed system. Both redundancy and replication are used to ensure system availability, fault tolerance, and data durability in the face of hardware failures or network partitions.
Microservices is an architectural pattern where a large application is broken down into smaller, independent, and loosely-coupled services. Each microservice is responsible for a specific functionality and communicates with other services via APIs. This approach promotes modularity, scalability, and ease of maintenance, allowing individual services to be developed, deployed, and scaled independently.
The CAP theorem, also known as Brewer's theorem, states that a distributed system can only achieve two of the following three guarantees at any given time: consistency, availability, and partition tolerance.
Consistency ensures that all nodes see the same data at the same time, availability guarantees that the system will respond to every request, and partition tolerance ensures that the system continues to function even if communication between nodes is disrupted.
A proxy server is an intermediary between clients and servers that acts as a gateway, forwarding requests from clients to the appropriate servers. Proxy servers can be used for load balancing, security, caching, and other purposes. They can help improve performance, distribute traffic, and protect backend servers from malicious traffic.
Message queues are a communication mechanism used in distributed systems to decouple components and enable asynchronous processing. They store and transmit messages between components, allowing them to operate independently and scale without being directly affected by each other's workload. Message queues help improve system reliability, fault tolerance, and scalability.
A file system is a method of organizing and storing data on a storage device such as a hard drive, SSD, or other media. It manages files and directories, controls access to data, and ensures data integrity.
File systems can be local (stored on a single device) or distributed (spanning across multiple devices in a network), providing different levels of performance, scalability, and fault tolerance.
System Design Architecture and Patterns
Here are some of the most common design patterns used to build software applications.
Two-Phase Commit (2PC)
2PC is a distributed transaction protocol that ensures data consistency across multiple resources or services in a distributed system. It is a form of atomic commitment, which guarantees that either all parts of a transaction are committed or none are.
The two phases are the prepare phase, where each participating resource votes on whether to commit or abort the transaction, and the commit phase, where the transaction coordinator decides the outcome based on the votes and informs the participants to either commit or abort.
Replicated Load-Balanced Services (RLBS)
RLBS is an architectural pattern where multiple instances of a service are deployed and managed by a load balancer, which distributes incoming requests among the instances. This pattern improves system performance, reliability, and fault tolerance by allowing the system to handle more requests and recover from instance failures.
Replication ensures that multiple copies of the service are available, while load balancing ensures that the workload is distributed evenly among the instances, preventing overloading and providing better response times.
Command and Query Responsibility Segregation (CQRS)
CQRS is an architectural pattern that separates the operations that modify data (commands) from the operations that read data (queries). By splitting these responsibilities into distinct components, CQRS enables greater flexibility, scalability, and maintainability.
This separation allows for optimization of each component according to its specific requirements, such as using different data storage systems, scaling read and write operations independently, or implementing different levels of consistency and performance.
Saga is a pattern for managing long-lived transactions in a distributed system, particularly when 2PC is not feasible or desirable. A saga is a series of local transactions, each with a corresponding compensating action that can undo the effects of the transaction.
If any local transaction fails, the compensating actions are executed in reverse order to rollback the saga. Sagas provide a way to maintain consistency across multiple services without relying on distributed locks or global transactions, trading off some consistency guarantees for increased availability and performance.
To illustrate the Saga pattern, in an e-commerce application consisting of three microservices—Order Service, Inventory Service, and Payment Service—this pattern can be used to maintain consistency across services when a customer places an order. Local transactions and corresponding compensating actions are defined for each service:
- The Order Service creates and reserves an order, then cancels it and releases items if necessary.
- The Inventory Service updates stock levels for reserved items, then reverts them if items are released.
- The Payment Service processes a customer's payment, then issues a refund if required.
If any local transaction fails, the Saga pattern ensures that compensating actions are executed in reverse order to rollback the entire operation, maintaining consistency across all services without relying on distributed locks or global transactions.
Sharding is a technique for partitioning data across multiple instances of a service or storage system to distribute the load and improve scalability. Each shard contains a subset of the total data and operates independently, allowing the system to handle more requests and store more data by adding more shards.
Sharded services can be used in conjunction with load balancing and replication to achieve even better performance, scalability, and fault tolerance. However, sharding can also introduce complexity in data management, querying, and consistency.
System Design Examples
An eCommerce system includes components like a web application for users to browse products, a shopping cart, a payment processing system, an order management system, and a warehouse management system. These components need to communicate with each other to facilitate browsing, purchasing, and shipping products to customers. The system design must address issues such as scalability, security, and availability to handle increasing workloads and provide a seamless user experience.
Content Delivery Network (CDN)
A CDN is a globally distributed network of servers that cache and deliver content, such as images, videos, and web pages, to users based on their geographic location. The system design must ensure efficient content caching, load balancing, and routing to minimize latency and improve the user experience. It also needs to address fault tolerance and security to ensure uninterrupted and secure content delivery.
Social Networking Platform
A social networking platform consists of various subsystems, including user authentication, profile management, messaging, news feed generation, media storage, and search. The system design must facilitate the efficient and secure exchange of information between users and handle large volumes of data and traffic. Scalability and performance are critical factors in such a system, as the number of users and their interactions can grow rapidly.
Internet of Things (IoT) System
An IoT system typically consists of a large number of interconnected devices, such as sensors, actuators, and gateways, that collect and transmit data to a central server or cloud platform. The system design must address issues such as device management, data processing, security, and communication protocols. Additionally, it must ensure that the system can scale to support the growing number of devices and data volumes.
Tools and Techniques of System Design
Data Flow Diagrams (DFD)
Data flow diagrams are a graphical representation of the flow of data within a system, illustrating how data moves between processes, external entities, data stores, and data transformations. DFDs help in understanding the system's functionality, identifying potential bottlenecks, and visualizing the relationships between different components.
DFDs are particularly useful during the analysis and design stages of a system development project. Swimm is a code documentation platform that integrates with the Mermaid.js diagramming tools.
Architecture diagrams are an essential tool in system design, as they provide a visual representation of the structure and organization of a software system. These diagrams help communicate the system's architecture to stakeholders, developers, and other team members, facilitating better understanding and collaboration. They also aid in identifying potential design issues and areas for improvement.
Swimm can help you generate architecture diagrams using Mermaid.js and then automatically keep them up to date as the code changes.
A data dictionary is a comprehensive, structured documentation of the data elements within a system, describing their meaning, relationships, format, constraints, and usage. It serves as a reference for understanding the data structure, ensuring data consistency, and facilitating communication among team members and stakeholders.
Data dictionaries typically include information such as data element names, data types, lengths, default values, descriptions, and relationships to other data elements. In the context of system design, data dictionaries play a crucial role in defining the data model, validating requirements, and guiding the implementation of data storage and manipulation.
Decision trees are a visual representation of a decision-making process, showing the possible outcomes of a series of decisions based on certain conditions. They are used in system design to model complex decision logic, analyze potential consequences, and choose the optimal course of action.
Decision trees can also serve as a communication tool for explaining the decision-making process to stakeholders.
Swimm can help you automatically generate decision trees from your code via Mermaid.js.
Decision tables are a tabular representation of decision logic, specifying the conditions, actions, and outcomes for a set of related decisions. They help in organizing and analyzing complex decision rules, ensuring that all possible combinations of conditions are considered and that the resulting actions are consistent.
Decision tables can be used during system design to define business rules, validate requirements, and document decision logic for implementation.
Pseudocode is a simplified, informal representation of programming code that uses a mix of natural language and programming constructs. It is used to describe the intended algorithm or logic for a system component without getting into the specific syntax of a programming language.
Pseudocode is often used during system design to communicate the high-level logic, facilitate discussions among team members, and serve as a basis for actual code implementation.
Unified Modelling Language (UML)
UML is a standardized visual language for modeling and documenting software systems, providing a set of graphical notations for representing various aspects of system design, such as structure, behavior, and interactions. It includes a variety of diagram types, such as class diagrams, sequence diagrams, and use case diagrams.
UML helps in creating a clear and consistent representation of the system design, facilitating communication among team members and stakeholders, and serving as a basis for implementation, testing, and maintenance.
APIs and Contracts
APIs and contracts play a crucial role in system design, as they define how different components and systems interact and communicate with each other. They serve as a bridge between different software elements, allowing them to work together efficiently and reliably.
APIs are sets of rules, protocols, and tools that define how software components should interact. They provide a standardized way for developers to access the functionality of a software module or system, without needing to understand its internal implementation. APIs can be used to expose functionality within a single application, between different applications, or across different platforms and devices.
Contracts in system design are formal, precise, and verifiable specifications that define the behavior and requirements of software components, including APIs. Contracts help ensure that components adhere to a consistent set of rules and expectations, facilitating better collaboration between developers and more robust systems.
With Swimm, you can go beyond the API docs and create rich how-to tutorials that explain how your service works and how to interact with it. Swimm will make sure these docs are up to date with the latest version of the code, and found from the IDE when developers need them.