Hey guys,
Lately I have been working on a Virtual marketplace application using Laravel and PostgreSQL. So, when I was asked to build this huge application, the biggest challenge I faced was the design.
Having a fair bit of prior experience in Laravel and upon following the current community trend, I decided to go with Laravel. And I hoped and expected that this, somewhat opinionated framework, would take care of my design to a large extent.
When I actually started designing it, I realized that for a small/medium application Laravel already has things in place, you, as a developer just need to follow the guidelines set in place by the framework and use the features its providing out-of-box.
However, for a larger application, with lot of interdependent modules and complex business flows, you need to make your own design decisions as well along with the existing features.
This gave me an opportunity to take a look into the various existing design patterns to solve my design problems which I encountered while designing different modules of the application.
What follows are the design challenges I faced and which design pattern I felt fit the most for this specific problem and how I implemented it.
I have made of list of the 5 most easily understandable ones here and hope these real world examples of design patterns implementation in a PHP & Laravel based application will help you taking your own design decisions.
I went straight ahead choosing the FACTORY PATTERN.
I created an SMSGatewayInterface class, which all the SMSGatewayProvider classes would implement and created one more SMSGatewayFactory class which would have the logic to choose the most suitable SMSGatewayProvider class on the fly.
The logic inside the SMSGatewayFactory class was mostly to do with fetching the data from the sms_provider_gateways table from the DB and a bunch of switch-case statements which would check the country of the mobile number and the priority of the SMS to choose the best SMSGatewayProvider class and returned its object.
So in the SendSMS class I would instantiate the SMSGatewayFactory class, pass the details of the SMS to be sent and then would use the Gateway this factory class returned.
Great work by Laravel guys, this is available in Laravel out-of-the-box. I just had to create table specific ObserverClasses.
For example, to sync data of the user_comments table into Elasticsearch's user_comments "type"(in Elasticsearch terminology)
I would say, that Facade Pattern is by far the most used pattern, we use it all the time and never realize that we are actually using a recognized Design Pattern. At-least in my case I never realized.
So I made this wrapper class UpdateWorkingTimings with only one function which actually - checked if the new timings were okay as per the business constraints or not, and updated it. However, internally I was invoking multiple other classes, collaborating with different other modules and calling multiple functions of various classes in some specific order to return a mere true or false. Any fellow-programmer who used this function & class wouldn't know and in most cases was not required to know also the internal complexities of this wrapper class.
I wanted it to be such that we could easily add-on any new kind of filter in the future to the existing functionality.
So every function in the ProviderSearch class was adding function specific filter to the initial object and returning it, in a way that the any other function can be chained on to it until I finally required the result through a simple get method.
This way if in future I require a new filter to be added, I would just be required to add another function and chain it accordingly.
However I also wanted that in case Redis was not available the application should fall back to PostgreSQL and continue the operation.
For instance the "User Session" data was being saved in the main DB and Redis cache both, and to get user info the application should check the Redis Cache's specific key, and so on.
I created an interface CacheInterface with the required functions and created two different classes one each for PostgreSQL and Redis namely PgsqlCacheAdapter and RedisCacheAdapter both extending the CacheInterface.
And whenever an Object of the CacheInterface was required it would check if Redis was functioning properly then return the object of RedisCacheAdapter instead return the object of PgsqlCacheAdapter.
This way we could utilize the speed of Redis and reliability of PostgreSQL as a fail-over.
Thats all for this time folks.
Happy coding,
Sandeep Rajoria
P.S. - Get quick data insights and explanatory visualizations via various charting options through my charting App
Lately I have been working on a Virtual marketplace application using Laravel and PostgreSQL. So, when I was asked to build this huge application, the biggest challenge I faced was the design.
Having a fair bit of prior experience in Laravel and upon following the current community trend, I decided to go with Laravel. And I hoped and expected that this, somewhat opinionated framework, would take care of my design to a large extent.
When I actually started designing it, I realized that for a small/medium application Laravel already has things in place, you, as a developer just need to follow the guidelines set in place by the framework and use the features its providing out-of-box.
However, for a larger application, with lot of interdependent modules and complex business flows, you need to make your own design decisions as well along with the existing features.
This gave me an opportunity to take a look into the various existing design patterns to solve my design problems which I encountered while designing different modules of the application.
What follows are the design challenges I faced and which design pattern I felt fit the most for this specific problem and how I implemented it.
I have made of list of the 5 most easily understandable ones here and hope these real world examples of design patterns implementation in a PHP & Laravel based application will help you taking your own design decisions.
#1 - PRIORITY GATEWAYS - SMS
PROBLEM
This was, by far the least complex of the design challenges. This was a module which would select an SMS Gateway based on the priority of SMS. For instance, there should be HIGH priority gateway for the OTP SMSes and should use the fastest Gateway available; and summary SMS should be of priority MEDIUM and use the normal gateway available. Then there were country specific Gateways(had to use this approach so that we could use the country's most affordable provider) depending on the Country the SMS was being sent.
SOLUTION
I went straight ahead choosing the FACTORY PATTERN.I created an SMSGatewayInterface class, which all the SMSGatewayProvider classes would implement and created one more SMSGatewayFactory class which would have the logic to choose the most suitable SMSGatewayProvider class on the fly.
The logic inside the SMSGatewayFactory class was mostly to do with fetching the data from the sms_provider_gateways table from the DB and a bunch of switch-case statements which would check the country of the mobile number and the priority of the SMS to choose the best SMSGatewayProvider class and returned its object.
So in the SendSMS class I would instantiate the SMSGatewayFactory class, pass the details of the SMS to be sent and then would use the Gateway this factory class returned.
#2 - Syncing Elasticsearch DB with the changes in the PostgreSQL tables
PROBLEM
We planned on using text search on the user comments to return more appropriate results for Provider searches. For this I choose Elasticsearch where all the comments will be saved(along with in PostgreSql which was our System of Records) and based on relevancy of the full text search result from Elasticsearch on the comments, the most appropriate Service Provider would be returned. Now we just required a mechanism where any add/update/delete was synced with the data in Elasticsearch.SOLUTION
This seemed like a classical case of OBSERVER PATTERN to me.Great work by Laravel guys, this is available in Laravel out-of-the-box. I just had to create table specific ObserverClasses.
For example, to sync data of the user_comments table into Elasticsearch's user_comments "type"(in Elasticsearch terminology)
- I created ElasticsearchUserCommentsObserver with created, updated, deleted functions in the observer.
- These functions would expect the UserCommentsModel object in the argument and call the Elasticsearch driver's resp functions, like delete for delete and index for add/update.
- Registered it in the ObserverServiceProvider class provided by Laravel to observe changes in the UserCommentsModel which can be seen analogous to "subscribing" to UserCommentsModel
- This would internally listen to any changes that happened to data in the user_comments table
#3 - Updating the Working Timings of the Professionals which required scores of checks and lot of data in the DB to be updated
PROBLEM
As you would expect, updating the work timings of a service professional would require multiple checks into current and scheduled jobs and also checking of other business constraints.SOLUTION
Although I didn't realize till the time I finally implemented it, I actually had solved this by implementing FACADE PATTERNI would say, that Facade Pattern is by far the most used pattern, we use it all the time and never realize that we are actually using a recognized Design Pattern. At-least in my case I never realized.
So I made this wrapper class UpdateWorkingTimings with only one function which actually - checked if the new timings were okay as per the business constraints or not, and updated it. However, internally I was invoking multiple other classes, collaborating with different other modules and calling multiple functions of various classes in some specific order to return a mere true or false. Any fellow-programmer who used this function & class wouldn't know and in most cases was not required to know also the internal complexities of this wrapper class.
#4 - Search based on various filters the user has passed
PROBLEM
I was looking for a way to implement Provider Search based on the different filters passed such that it takes care of Extensibility without loosing on Modularity, thus a Maintainable way to design the Search Module.I wanted it to be such that we could easily add-on any new kind of filter in the future to the existing functionality.
SOLUTION
I looked at Query Builders and thought, yes, thats how I need to implement it. And thus I used the Query Object(INTERPRETER) Pattern for my Search module.So every function in the ProviderSearch class was adding function specific filter to the initial object and returning it, in a way that the any other function can be chained on to it until I finally required the result through a simple get method.
This way if in future I require a new filter to be added, I would just be required to add another function and chain it accordingly.
#5 - Use of Redis as Cache layer with PostgreSQL fallback
PROBLEM
We wanted to keep some data in Redis considering it being an in-memory database and wanted it to serve as the cache engine for some specific kind of data for the application.However I also wanted that in case Redis was not available the application should fall back to PostgreSQL and continue the operation.
For instance the "User Session" data was being saved in the main DB and Redis cache both, and to get user info the application should check the Redis Cache's specific key, and so on.
SOLUTION
ADAPTER PATTERN is what was required here.I created an interface CacheInterface with the required functions and created two different classes one each for PostgreSQL and Redis namely PgsqlCacheAdapter and RedisCacheAdapter both extending the CacheInterface.
And whenever an Object of the CacheInterface was required it would check if Redis was functioning properly then return the object of RedisCacheAdapter instead return the object of PgsqlCacheAdapter.
This way we could utilize the speed of Redis and reliability of PostgreSQL as a fail-over.
Thats all for this time folks.
Happy coding,
Sandeep Rajoria
P.S. - Get quick data insights and explanatory visualizations via various charting options through my charting App
Comments