Skip to main content

How I solved design problems by using various design patterns in my Laravel Project

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.

#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 PATTERN
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.

#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

Nahuel said…
Really useful information! Thanks for sharing. I used several of this patterns but I couldn't classified as dessign pattern and giving a name. Really interesting the SMS Gateway.
Sandeep Rajoria said…
Thanks for the feedback Nahuel
Alok Kumar said…
Good job Sandeep, I also implemented, factory pattern for background process workers and API design. Observer to update sphinx data to improve search. There was a product interspire I had to use for mass mailing but singleton was a problem to decrease the latency in the mail sending. Singleton is implemented in interspire. There I had to use antipattern. :)
LOGICMOJO said…
Thank you for sharing your experience. Design pattern is an important concept to learn as they simplify the design and architecture of applications and systems. Besides that, they are important for cracking the coding interview. Great blog.

Popular posts from this blog

Multi Tenancy with Codeigniter

In this post I will show you how I converted my normal Codeigniter application into a multi-tenant system.(first step to a SaaS implementation) Note - This implementation is a separate DB multi-tenancy implementation. Lets say we have an up and running CI application name ci_app , with the directory structure like this ./application ./application/config ./application/...so many other important directories ./asset ./asset/js ./asset/images ./asset/css ./system ./index.php which is accessed through browser like http://localhost/ci_app So to implement the multi-tenant arch we are most concerned about the following files, and we will be editing them ./index.php ./application/config/config.php ./application/config/database.php And also we need to create a few new ones Create a tenant folder in your www root directory, lets say tenant_1 Cut the ./index.php from ci_app and paste it in tenant_1 directory  Create a blank file config.php in tenant_1 directory Crea

Profiling and checking PHP error_reporting in a Codeigniter App, without editing the config!!

Hi all, You must have definitely used the Profiling in Codeigniter and error_reporting many a times in Development and Testing environment, but I am sure you must have missed it on a real Production environment. As there are scenarios, where you want to quickly debug the Production application and find out what PHP errors is the application throwing, check the page profile, that too without putting the time and effort in replicating the whole production environment on your local machine, or perhaps a testing server. This small piece of code(we could perhaps call it a hack), which I have used in almost all of my CI applications, will make your life very easy, without losing anything on the security of the system. Following points, essentially sum up what exactly it does - Check for the dev(or root or admin, whichever name you use for the su access), if it is logged in, as we don't want others to see all the Profile data and other errors. Check for a specific query str

D3.js, the most powerful visualization library I came across

Hello Friends!! This post is dedicated to D3.js , which I came across a few months back. Prior to that, I always preferred Google Charts which catered to most of my charting needs. However, since I switched to D3.js I could not find a good reason to move back to Google Chart. If we take a very crude analogy, D3.js would be analogous to an Ubuntu of the OS world, while Google Charts could easily be a Windows 7(just that both of them are free in this case); in the sense that the control and the power the user(in our case the developer) gets from D3.js while working with it, is unmatchable . While Google Charts is simply too cool and simple for a free charting tool, but D3.js is a library which lets you create any chart that you could think and conceptualize, with so much of ease, and it lets you link any kind of interaction/control that you would want to put on events, on any of the elements of the chart. D3.js, like RaphaelJS and PaperJS is a library which is developed to