A design pattern is a tried and tested way to orchestrate one or more components or modules to achieve an objective. Design patterns offer many advantages from reusability and testability to easy maintenance, portability and scalability. Some of the common patterns used while designing a backend are the MVC pattern, Repository, Observer, Dependency injection and Decorator patterns. Let’s look at each of these in the sections to follow.
The Model-View-Controller or MVC pattern assigns clear responsibilities to each entity. So while the Model layer handles the data entities, their inter-relationships and how they are laid out in memory, the View layer addresses how a user perceives the application and the Controller layer concerns itself with serving as the intermediary between the two.
So this patterns enables a clear line of separation between the User interface, the application logic or middleware and the data layer which in turn affords a certain degree of independence to each layer. So changes can be made in any of these layers without affecting the other 2 layers. For instance in an Express application the data model for a MongoDB database can be defined using Mongoose and the User interface can be defined using a framework such as Handlerbars while the interactions between these 2 layers is handled by the Express middleware.
Following this pattern enables scalability since any of the layers can scale independently without affecting the functionality of the others. As can be expected maintainability also improves along with testability and performance.
This patterns abstracts the underlying data storage layer so that any application talking to the data storage layer has no knowledge of the internal implementation of the layer. So for instance you could start off by creating a database in SQL Server. You then create an abstraction layer over the database to handle all the CRUD operations which are used by the application layer. All the application layer knows is what the abstraction layer exposes or chooses to expose.
A well defined interface exists and this is what the application layer uses.
Sometime later you decide to replace the SQL Server database with a PostgreSQL one and the application is none the wiser to this change. It continues to carry out the same operations as before since it only uses the interface exposed by the Data Abstraction layer.
One added benefit of using this pattern is that it can improve performance as well by caching results and returning these as and when required.
This pattern recommends loose coupling between components and injecting dependencies into them without creating these dependencies within the components. There are 3 ways of achieving this : 1)constructor injection 2)property injection 3)method injection. It can be easily deducted that the dependencies are passed in the constructor, method or through a public property.
The advantages are obvious one of them being that each component can be independently worked upon with minimum impact on the others. And when it comes to testability and scalability the independent nature of these components enables them to be tested and scaled without fear of disrupting any of the other components. The microservices architecture is an extreme example of this pattern where each service component is so independent as to be completely oblivious of the existence of the other services.
Reliability is greatly enhanced with this design pattern since a failure in any component will not take down the other components. However given that the components are loosely coupled we also need an orchestrator to create a symphony with all these disparate services and components so that they can all come together and achieve the objective of the application.
Another popular pattern, this is used in tandem with Dependency injection to achieve a certain degree of independence among components. Typically you could have a publisher or any application which sends out notifications or results and a host of observers or subscribers who receive these results and then act upon them.
To give an example you could have a Kafka topic which is of interest to many subscribers and whenever a message arrives the subscribers to this topic would be notified.
This pattern is very heavily used in Distributed Systems today and finds application in Event streaming applications and even applications where components talk to each other using messages and events.
Besides the loose coupling which results from using this pattern, it also enhances performance since only observers who have subscribed to these topics receive notifications and act upon them. A meaningless broadcast is avoided thereby saving the CPU cycles which would have been expended in identifying and discarding these notifications or messages.
If you wanted to enhance or modify the functionality of one specific instance or object at runtime this is the pattern you would use.
Decorators work not on the class but on specific objects. A Decorator class wraps the original object and exposes the same interface as the original object. So you could decorate the object with added functionality during runtime which enhances or changes the capabilities it already has.
For example in Java you could define a Car base class and then create children classes which inherit from the parent Car class. So you could end up having a classes for a Sedan, Hatchback and more. Using the Decorator pattern a Sunroof feature class could be created to extend the functionality of each of these classes of cars. So at runtime you could have some plain Sedan objects and also some Sedan objects decorated with a Sunroof object.
Maintainability is greatly enhanced since you can work on only the objects of interest in isolation without disturbing all the objects of a class.
There are several other patterns out there and this article just scratches the surface. However it goes without saying that design patterns greatly enhance reusability, reliability, standardization, maintenance and performance of an application.