Producing clean code with design patterns

Vtakdeniz
6 min readJul 23, 2022

--

Hello everyone, I am Veli. In this article, I am aiming to give you some insight on how can complex application logic be handled with chain of responsibility and strategy patterns to produce readable, clean code that conforms solid principles by giving examples from my own project.

First of all, I would like to mention that I have been working as Associate software developer in Trendyol for the past 1.5 months. In this period, I have been tasked with my first project and got exposed to many new design patterns and technologies and learnt a lot. Even though it was a lot of information to digest, with the help of my mentors I was able to overcome any technical challenges I have encountered during the development.

The language I chose for the project was of course, king of all languages, go

Go Gopher — Golang mascot

The aim of the project was to create a bot that could diagnose the problem of a given payment and take necessary actions if needed (Hence the name payment-doctor was given to the project). After almost a week I have spent with my mentors to better understand the internal systems and the project, the project looked more and more daunting to my amateur eye. The reason is, to properly check all the possible problems a payment might had, I had to evaluate a big set of combination of the payment’s state. For example, a payment might be a provision payment that failed because we couldn’t block the amount of money required in user’s bank account. But it also might be a direct payment with no provision. Or maybe the user was just trying to deposit some money to their Trendyol wallet. Do i refund the payment now? But what if payment was already refunded to the user? Or what if the order is already created and is on its way to the user’s home? Well in that case I have to check order management system to confirm my suspicion. But wait, was the payment a deposit to the Trendyol wallet, then no order was created surely?

As you can see, even with a small set of combination we had here, we need a very long if else chain that no programmer would ever want to deal with. Hence why the project looked so scary to me in the first glance. But my mentors was again quick to give me really useful advices and tips on how to structure my project. They suggested design patterns I haven’t used before and gave me tips on applying them to my project. 2 design patterns was mainly suggested ; Chain of responsibility and Strategy pattern. Let’s see what they are first and how can they be implemented to our projects.

Chain of responsibility pattern, is basically a set of handlers chained to each other trying to handle incoming data and forward it to the next handler in the chain if it can’t handle it. This method, avoids coupling the sender of request to the receiver of the request, and gives more than one object a chance to handle it. If any handler in the chain can handle it, it returns the result and doesn’t forward it to the next chain.

Strategy pattern, is used when we have more than 1 algorithm to do a specific task and implementation could be changed at runtime. Here we don’t pass the data down the chain, but deciding which strategy should handle it based on the properties of the data.

The way I implemented them into my project goes like this ; Project is logically separated into two modules. First module is using chain of responsibility pattern to detect what is the problem with the payment and gets a payment id as the data, but doesn’t take any action like refunding the payment. Each handler in the chain tries to handle the payment according to the status of it. For example, first case checks the current situation of the payment, whether it is already refunded or if the payment was successful etc. Second case checks if the payment is a direct payment, if not forwards it to the next handler. Next handler checks if the payment was a deposit to the users Trendyol wallet and forwards it to the next handler if not. You get the idea. Once a handler detects it can process the data, it sends different requests to internal api’s to understand the problem of the payment. This approach saves us from a long if else chain hell and gives us a reusable, clean and neat looking code. That’s why it is very easy to implement another handler and insert it into the chain. After done writing your handler just insert a set next to the code below, and you are all set.

Second module is the one that takes the action and is implemented with strategy pattern. Based on the detected problem, we take necessary action by selecting the appropriate strategy. You can implement the function to choose the strategy any way you want here, but here is how I done it ;

So basically I map every status code returned from the first module to the corresponding strategy in my strategy context. Then in Match function, I return an appropriate handler for the given status code using closure. The reason I use closure here is instead of directly returning the strategy object, I return a function that executes the handle method of the object, so the receiver doesn’t know about underlying implementation and also doesn’t have to pass ‘code’ parameter again to the handle method after calling the match function. Since the stack of the encapsulating function is not destroyed in closures, variable ‘code’ will be accessible throughout the lifetime of the handler.

Here is how the general structure looks like now ;

But why not choose strategy pattern to detect the problem and chain pattern to take action? Let’s analyze it a little bit further. If I were to use strategy pattern to return the problem, well then I had to choose an appropriate strategy first for my data. To do that, I had to evaluate my payments state by sending queries to different internal api’s to learn if payment is refunded, or if it was a provision payment etc. This again leaves us with a long if else block, even though not as worse as it could be hadn’t we used the pattern. And if I were to use chain pattern to take action based on the status code, all of my handlers would have compared the same status code before taking action. Which turns the pattern into a glorified switch case statement and using the pattern doesn’t really add that much value. Of course this wouldn’t be the completely wrong use of the patterns, but it wouldn’t be as much readable and reliable.

And last but most importantly, our code is unit testable. I wrote unit tests for each of the strategies and handlers in my project to be sure each object fulfills their responsibility. It is obvious why that wouldn’t be the case hadn’t we used any design principles.

Thanks for taking your time and reading this article. Hope you find it useful.

Best Regards.

--

--