Did you guys know that this entire series is now available on Amazon as a book! Check Knowing Software Architects to read the excellent compilation and keep supporting me in pursuing something unique!
It’s been a while since I posted in this series, well to be honest life just caught up and with a book release happening ( woah ) I best believed the timing was just not right to pursue weekly releases.
But here we are now!
This time, let me introduce you to the amazing world of Design patterns in Software Architecture and get you a glance at them through the very eyes of Software Architects themselves!
So let’s get started!
Introduction to Design Patterns
A design pattern can be referred to as a collection of general reusable solutions to common design problems.
Example:
– How to communicate between classes.
– How to initialise interface implementations.
– How to access data stores.
Using design patterns have a lot of benefits
- They are already tested and used by a lot of developers and hence are very reliable solutions.
- They make code more standardised and easy to modify and test. Since design patterns are very specific, they also help in templating the entire code.
Some common design patterns are:
- Factory Pattern
- Repository Pattern
- Facade Pattern
- Command Pattern
Factory Pattern
Factory pattern helps us in creating objects without specifying the exact class of the object.
Now, what is the motivation for creating the Factory Pattern?
– As we learnt earlier, we want to avoid strong coupling between classes
- We want to keep the code modular.
- We want the code to be extensible
- New is glue. So we tend to avoid it altogether
- It can be used to avoid strong coupling
- It also acts as a basis for other patterns
Let us take an example of Factory Pattern with the help of a problem:
Problem Statement: Let us say that an app requires to get the weather forecast patterns of the cities. Now there are multiple providers of weather and the app lead decides to go with the provider ‘Hot or not weather’
So initially they might create a class as:
| class HONWeather { public int GetWeather(string city, DateTime date) {…} } |
As seen above, this class has a method to return implementation of getting weather.
Below is the usage for the same:
| private void ShowWeather() { HONWeather weatherProvider = new HONWeather(); int weather = weatherProvider.GetWeather(“Shimla”, DateTime.Now); //Shows the weather on the screen } |
Now let us say that the company HON goes out of business and we need to change provider.
A new company Either Neither Weather or EN weather takes over and now in our app we need that provider. The traditional way would be to simply create the class:
| class ENWeather { public int GetWeather(string city, DateTime date) {…} } |
Now we need to find all the instances of HONWeather that were created earlier and change them manually to replace the implementation
| private void ShowWeather() { ENWeather weatherProvider = new ENWeather(); int weather = weatherProvider.GetWeather(“Shimla”, DateTime.Now); //Shows the weather on the screen } |
This is very tedious and introduces unnecessary coupling. Thus we prove that new is glue.
Now let us see how we can do the same with an interface:
1) First we create an interface that contains the method we want to use:
| interface IWeatherProvider { int GetWeather(string city, DateTime date); } |
2) Make HONweather and ENweather class implement this interface
| class ENWeather : IWeatherProvider { public int GetWeather(string city, DateTime date) {…} } class HONWeather : IWeatherProvider { public int GetWeather(string city, DateTime date) {…} } |
3) Create a factory method that returns the instance of the classes using the interface:
| private IWeatherProvider GetWeatherProvider() { return new HONWeather(); } |
4) Implement only the FactoryMethod
| private void ShowWeather() { IWeatherProvider weatherProvider = new GetWeatherProvider(); int weather = weatherProvider.GetWeather(“Shimla”, DateTime.Now); //Shows the weather on the screen } |
Now even if we add more providers, we will still not need to change code everywhere but only the factory method
| private IWeatherProvider GetWeatherProvider() { return new ENWeather(); } |
Repository Pattern
Repository pattern states that modules not handling the actual work with the datastore should be oblivious to the datastore type.
It shares similarities with the Data Access Layer.
The main difference is between the audience between these:
- DAL is for architects
- Repository pattern is for the developers.
Let us take an example to understand this.
Problem statement: Application needs to maintain HR resources and need to do basic CRUD operations.
- Create
- Read by ID and by Department Name
- Update
- Delete
The team creates the following method to add vacation days to the Employees:
So eventually the team does the same thing again and again:
This is tedious as basically the same thing is being repeated in various levels.
Changing any query means looking for all implementations and thus making the task error prone, boring and tedious.
With the Repository Pattern, the methods will never directly interact with the database but instead call a method from a factory.
The interface here, IEmployeesRepository exposes the following methods:
Now even if something changes, only the repository method will change and nowhere else do we have to modify anything.
Let us say someday we want to change the entire data store from SQL to Mongodb.
In legacy implementation, we would have to change everything everywhere drastically. But now, with repository pattern, we can simply replace the factory method and everything will remain the same throughout the app.
Facade Pattern
As the name states, the Facade pattern refers to creating a layer of abstraction to mask complex actions.
Let us again take an example.
Problem Statement: For a banking application, we need to create a money to transfer money in accounts.
Now the development team comes up with the following:
• Make sure accounts exist
• Make sure the first account has enough money
• Withdraw money from first a count
• Deposit money in second account
• Add event in account log
For the above events, we create the following methods:
Now here the client needs to implement all these methods and these are now error prone. A lot can go wrong here.
So the Facade patterns can solve this.
Here now we combine all these methods to a single method that can be exposed to the client.
This method TransferMoney, will now perform all the events internally and the client can still fulfill all the conditions needed to transfer the money.
The facade does not invent any new functionality. It simply packages the existing methods to make sure that the original goal is fulfilled.
Command Pattern
The Command Pattern states that all the action’s information is encapsulated within an object including the actioning parameters and the object on which the action is executed.
This sounds a bit complicated but let us take an example.
Problem: The team needs to implement an undo mechanism.
This sounds simple at first but if we break down the naive implementation, the Undo mechanism has several things it needs to do.
It should delete text, change the font, reapply other styling, bring back deleted files etc.
The implementation can look like:
Now this is bad. This would mean that we need to keep adding methods to do anything. This will eventually result in a huge codebase. This isn’t anything that we want.
So we need to rethink the approach.
Here the command pattern comes to our rescue.
Using this mechanism we have simply a list of commands that need to be executed. The pattern allows the freedom of not knowing what will these commands do but simply executing them when invoked.
So now the usage will look like below:
a) ICommand Interface – an interface for the commands to execute with just one method present.
b) Command classes – Now these classes represent the specific command that needs to be executed. Such as deleting, changing font etc.
c) These classes further will get a reference to the relevant objects to execute the command.
Above is a simple implementational example of this step. In real life, this can be further complicated.
d) Implement the interface – The final step is to simply implement the interface. The Class which implements is called a command Object and the reference to the relevant object is called a receiver.
e) Implement the Undo mechanism –
This mechanism holds a queue of commands for any action performed.
A new command object is added to the queue. When an undo is requested, the mechanism simply pops a command from the queue and calls its execution method.
The class here is known as the invoker and it has to simply invoke the undo method. At any point, it does not know which method is being called.
This method is not as popular as other types of architecture, but this is still something that can make our life way easier in case of situations like these.
Introduction to System Architecture
This is the most important section of our entire journey as Software architects.
We already talked about component architecture and design patterns. The difference between them and System architecture is that here we take a higher point of view. We try to answer the bigger questions like
- How will the system work under heavy load?
- What will happen if the system crashes at an exact moment in business flow?
- How complicated can be the update process?
- Etc.
The answers vary based on the types of systems we design.
For some systems, crash handling could be non-existent as they are highly tolerable to faults, like a static portfolio, while for others like a tsunami alert system, the margins of errors need to be very very low!
The system architecture includes:
a) Defining the Software components (Services)
b) Defining the way these components communicate
c) Designing the system’s capabilities ( Scalability, Redundancy, Performance, etc. )
To learn about these, we will learn about some techniques used in system architecture. These include:
a) Loose Coupling
b) Stateless
c) Caching
d) Messaging
e) Logging and Monitoring
Loose Coupling
When we talk about loose coupling in terms of software architecture it means to make sure the services are not strongly tied to the other services that are being implemented in a system.
The reason for this is quite simple actually.
If there is strong coupling within services, then each time one service is changed, then the other services will be affected accordingly.
With loose coupling we ensure that we create services in a manner that minimal impact occurs on change or modification of any such service.
Now there can be a question that since various services have a separate codebase altogether as we learnt in components architecture, why is there a need to loosely couple services?
This is because this is different than the loose coupling in components.
While components allow different parts of a codebase to stay independent of each other in loose coupling, in services when we talk about loose coupling, we mean that any exposed api, method or command does not force the system to use that service only.
In addition, this also means that changes in any config for the service, such as url has minimal to no impact on the platform.
Take an example below:
We have a service called Stock Quotes developed in Java that exposes a Java Remote method invocation api.
Now our portfolio service has to also use and depend on java to implement this method creating a strong coupling between two services. This is an example of strong coupling in services forcing a system to a particular method.
Let us take another example:
In the above scenario, now our Stock Quotes services exposes a REST API method.
While this removes our previous limitation, now we have a new problem where if the URL changes, the other services accessing stock quotes service will fail.
This becomes a particular problem when we have multiple services communicating and forming a spiderweb
If one service fails, we are looking at a catastrophic system failure.
A simple way to avoid above would be to use a directory.
Here the services will first query a directory for the url and then use that url for further communication. This limits the change to a url to a single directory only and can prevent collision of different services
Another way could be a gateway where the gateway would route the service further for communication.
Stateless
This is perhaps the single most important pattern that can be implemented in an application.
The stateless architecture pattern states that the application’s state is stored in only two places – the data store and the user interface.
State here refers to the application data.
Let us take an example of an application to understand that.
Here in the above scenario, the user’s login data is being stored within the log in service. This becomes stateful and becomes harmful!
So why this is problem?
Let us revise two other important concepts to answer this.
1) Scalability:
- Means growing and shrinking as and when needed
- Scaling up and Scaling out are the two ways to do this.
- Usually scaling out is preferred
2) Redundancy
- Allows the system to function properly when resource is not working
- E.g. a system with two or more servers can continue working even if one fails
Now to implement these concepts, the architecture of the app must look like this:
Here we can clearly see that the scalability and redundancy are tied together with each other.
The load balancer maintains both scalable and redundancy operations by adding or removing services when needed and routing them.
Now how does a stateful system fail here?
Let us consider the first scenario where the users data is stored in Log in service 1. So far so good. But what happens if the log in service 1 fails and the second log in service needs to be used to conduct user operations?
We now encounter the problem where the user will be shown a log in again message despite being logged in because the user’s data state is stored in service 1 and can not be accessed by service 2!
Thus statefulness of the code will make the application harder to scale and will disrupt user experience.
With a stateless architecture we make use of our database and query it to store and retrieve any user state as that would persist despite the scalable services being present.
Caching
Using caching we bring data closer to its consumer so that its retrieval will be faster.
This is fairly simple to explain using an example of our browsers.
Instead of serving the pages by requesting the server again and again, the browsers use a cache to allow for the faster retrieval of the pages.
In terms of an application, we can take an example of a cache service implemented between a Data access layer and data store so that there is no need to recalculate every query again and again.
There are some trade offs when we talk about the cache mechanism:
If the data is missing from the cache, the retrieval will fall back to the database with it being a single source of truth.
It is imperative to know that:
The cache should hold data that is frequently accessed and rarely modified.
The reason behind this is simple.
- If the data is frequently accessed, then we want it to be easily and speedily available.
- This minimises the load on the system and optimises the user experience.
- If the data is rarely modified, we overcome the challenge of syncing the data with the datastore.
- If the data is not in sync it can lead to data corruption and a bad user experience.
Generally, in-memory-in-process cache is preferred over distributed cache as it reduces the risk of making the system non-scalable or reducing redundancy.
The main advantage of distributed cache comes in the form of allowing for highly scaled systems to use a single caching mechanism without the need of hammering the database.
So in conclusion we have the following:
Messaging
Messaging refers to the means of communication between the various services.
Criteria for messaging
There can be several criteria for choosing the type of messaging architecture we can develop.
These include:
- Performance
- Message Size
- Execution Model
- Feedback and Reliability
- Complexity
Let us explore various types of messaging models.
Rest API
This is the De-Facto standard for HTTP-based systems.
The rest API is easy to understand and contains a predefined and well-accepted structure of an implementation.
The following key points can be understood for the REST API:
HTTP Push Notifications
The next method is the HTTP push notification method.
Here the client will subscribe to the service and once an event occurs the client will be notified.
We generally use HTTP push with advanced web techniques like web sockets. They are also very popular in chats.
Queue
In this model the message is placed within a queue and this message is further pulled from the service.
In a queue:
- Messages will be handled once and only once
- Messages will be handled in order
File and Database based
This is a traditionally understood method of messaging where the message is placed in a file or database and just like queues, the other service pulls from this.
This can be further explained below:
This method has a few problems such as files being locked while multiple services try to access the same file and duplicate files being processed.
Conclusion
So based on what we have learnt, we can conclude that:
So that is it for part 7! In part 8 we jump to Logging and Monitoring and External Considerations of a Software Architect. So keep reading!
Discover more in the next blog!!
I keep on coding something cool, visit ankush.tech to see what I am doing!
If you wish to read about my work, here is a book that I published recently – “Knowing Software Architects “
Get Started with CSS today: “CSS Bullets, a comprehensive guide to all the CSS you need!“
Interested in React? Learn react from scratch with my book, “REACT Bullets“.
Not subscribed to the newsletter? Subscribe now!!!
Thanks for sticking around!

Leave a Reply