Software System Design Principles
1. System Design Principles – Introduction
In this post, we feature a comprehensive article on Software System Design Principles. Software Architecture is very critical for creating complex software structures required for achieving several goals at the same time. Creation of right software architecture at the start of a project lead to better results in the long term in terms of Scalability, Availability, Reliability and reduced maintenance costs. Bad Software Architecture and architectural decisions potentially result in increased maintenance costs and systems hard to maintain.
Let’s take a look at some of the best System Design Principles.
2. Separation of Concerns
Separation of Concerns is the most important principle in Software Design, i.e divide your software system into components and build each part once. Separation of concerns leads to the modularity of application and modularity is the key to scalable and maintainable software architecture.
The software systems need to be decomposed with as little overlapping functionality as possible. Each module or service in the system should focus on one dedicated group of functionality. This approach makes it Easier to Understand, Develop, Maintain, and Easy to enhance applications. Designing a system with separated concerns helps in developing different modules a system in different programming languages. Inheritance and Composition features in Object-Oriented Programming help improve the modularity of applications.
Key Benefits of Modularity are:
Reuse code becomes easy with Modularization. The common code can be packaged as a library and can be used in multiple projects and/or modules.
When software applications are modularized, it is easy to troubleshoot the application, easy to fix any bugs and easy to maintain. Since each component is self contained, the dependency issues can be easily mitigated. It is also easy to test each module by mocking other dependencies. Modularization helps in getting more productivity from development teams as the work can be easily separated among developers or development teams.
Modularization abstracts modules from each other. It becomes easier to extend the functionality of each module or add new features to an existing module without major impact to other modules as long as the backward compatibility is maintained or new version of APIs are created. New capabilities can be easily added by creating new components or modules.
3. Highly Cohesive and Loosely Coupled
Cohesion is the degree to which the related elements of a module belong together and do one dedicated thing. All related code in a module should be close to each other. This helps in reducing the latency in processing the request. Coupling refers to the degree to which different modules in an application depends on each other.
Coupling is the degree of dependency between two systems. Make software components as loosely coupled as possible, i.e each module should be independent of other modules as far as possible so that changes in one system will have minimal to no impact on other components. Inheritance is an example of tight coupling. The composition is an example of loose coupling. Reduced coupling results in increased cohesion.
4. Event Driven Architecture
Event-driven architecture helps applications get notified when interested data item is changed in the backend systems. Application programs listen to these notifications and refresh their caches and take any follow-up actions. If Client applications can handle event notifications, it helps in reducing the number of calls made to backend systems.
Several notification mechanisms are available for use in application development. Some of them are ActiveMQ, RabbitMQ, distributed messaging systems like Kafka etc.. Each of the cloud provider also provides different messaging/notification services.
5. Reduce the amount of processing in the Client request path
Significant processing in client request path results in increased latency in client calls and limits the amount of throughput application handles. Minimal processing results in quicker response and the application can handle more load with less capacity. Minimal processing can be achieved by precalculating the possible response items upfront and cache them in distributed caches. When a client makes a request for a data item, the precalculated data can be directly fetched from the populated cache.
6. Use Effective Caches to make it highly responsive
Use caches where ever applicable to reduce Remote service calls, Database calls and amount of processing in request path. This helps in reducing the latency in client calls to service. Use event notification mechanism to keep caches warm and avoid serving stale data to clients.
7. Distributed System Characteristics
Distributed systems are designed to be highly scalable, reliable and available to serve the variations in the load, and is highly reliable and available. Following are more details on what these characteristics are:
Scalability is a capability of a system to grow and manage an increase in demand automatically with the addition of additional capacity. There are two types of scaling, Vertical Scaling, and Horizontal Scaling.
7.1.1 Vertical Scaling
In Vertical Scaling, you add more advanced hardware with increased capacity like more RAM, powerful Processor, etc. to serve increased load on your application. The issue with Vertical Scaling is that there is always a limit on how much the capacity can grow. This type of scaling is expensive due to hardware cost and takes time to get new hardware. If you want to quickly scale your application for increased load, this type of scaling isn’t a great choice.
7.1.2 Horizontal Scaling
In Horizontal Scaling, you can add more servers to the existing capacity to serve increased load on your application. Increased load on application is spread across all the servers in the cluster through a Load Balancer. This type of scaling is best choice if you want to grow your software quickly and it isn’t expensive.
It’s easy to scale applications in Cloud Environments through configuration change, which automatically adds additional servers to the cluster based on the performance monitoring metrics.
Reliability of a system is the probability of failure of software operation for a specific period of time in a given environment. The system is considered highly reliable if it continues to serve the client requests even if there is a hardware or software failure in part of the system. Any failed component can be replaced without impacting the client requests.
Reliability of a distributed system can be achieved by through redundancy of both software components and data, so any loss of one component or data container will not result in degraded performance or failure of client requests. High reliability results in additional cost due to the redundancy of software components and data.
Availability is the time the system remains operational and continues to perform its function in a specific period. If a system is reliable, then it’s automatically available, i.e it continues to function.
Every system design is to make the system as simple as possible. Simplicity of the system fetches several benefits such as reuse of the code, easy to make changes, easy to add new features, easy to test and troubleshoot any production issues.
8. Use Asynchronous process
Using Asynchronous processing and parallel processing would help expedite the client requests and reduce the overall latency of the request. Use frameworks like RxJava for parallel processing of multiple subrequests involved in a given client request. Wherever possible use asynchronous calls for calling other systems. Reducing the number of calls made in client request path results in better performance of the application and the application is easily scalable.
Very nicely drafted key design principles. I feel if you could put a diagram to show the design principles that would have made it more interesting. But overall really nicely written.