Chain of Responsibility Design Pattern with Real-World Use Case

Yasin Kızılkaya
Trendyol Tech
Published in
4 min readApr 3, 2024

In this article, we will explore the one of behavioral design pattern which is Chain of Responsibility design pattern and see how we have implemented it for a real-world use case.

Overview of the Chain of Responsibility Design Pattern

It can be visualized as a series of decoupled handlers linked together like a chain. When an object or request is passed into this chain, each handler processes the input based on its own logic. The handler may either handle the request and produce a response or pass the request along to the next handler in the chain.

This design promotes a flexible and independent way of handling requests, with each handler responsible for its own processing logic.

Chain of Responsibility Pattern in Our Case

At GlobalPlatforms, our catalog comprises millions of product along with their prices and stock availability.

As GlobalPlatforms, we collaborate with numerous external platforms like Amazon, AliExpress, and others. Due to various factors such as limits, costs, and business restrictions etc. It’s unfeasible to sell all Trendyol products on these platforms. Hence, we require a mechanism for prioritizing products based on platform. For one platform, one of the prioritization rules involves feeding “non-broken” products as a top priority.

Recently, we’ve been tasked with tagging products as Broken according to predefined rules. These rules encompass various scenarios, including:

  • If the percentage of main sizes (predefined for each category) with stock quantity lower than a certain threshold (N) is below a specified limit (M).
  • If the stock quantities for three consecutive sizes (predefined for each category) fall below a specified quantity threshold (N)
  • If the stock quantity of a single-size product(such as “Carpet”) is below a certain threshold (N)

As you can observe, each of these rules operates independently. So each rule can be can be considered like a handler. By constructing a chain with these handlers, we can efficiently process product tagging based on these rules.

In our chain, when a request received with product parameter, the first handler executes its logic. If the product does not meet the conditions specified by the rule, we immediately return false. However, if the product meets the conditions, we can pass it as a parameter while invoking the next rule in the chain. This is how we implemented it:

type Rule interface {
SetNextRule(rule Rule)
IsValid(product Product) bool
}

The “Rule” interface serves as the base handler and is implemented by concrete rules(handlers). Each concrete rule must implement SetNextRule and IsValid methods. SetNextRule is used for linking rules during chain construction, while IsValid is used during rule execution. Here’s an example:

type MoreThanNStocksForMoreThanMPercentMainSizes struct {
NextRule *Rule
stockThreshold int64
percentageThreshold float64
}

func NewMoreThanNStocksForMoreThanMPercentMainSizesRule(stockThreshold int64, percentageThreshold float64) Rule {
return &MoreThanNStocksForMoreThanMPercentMainSizes{stockThreshold: stockThreshold, percentageThreshold: percentageThreshold}
}

func (r *MoreThanNStocksForMoreThanMPercentMainSizes) SetNextRule(rule Rule) {
r.NextRule = &rule
}

func (r *MoreThanNStocksForMoreThanMPercentMainSizes) IsValid(product Product) bool {
if !r.isRuleValid(product) {
return false
}

if r.NextRule != nil {
return (*r.NextRule).IsValid(product)
}

return true
}

func (r *MoreThanNStocksForMoreThanMPercentMainSizes) isRuleValid(product Product) bool {
// logics...
}

For this rule if it is not valid for a given product, we directly return false to the client without iterating over the rest of chain. However, if this rule is valid and we have a next rule in the chain, we execute the next rule with the product parameter; otherwise, we return true.

After receiving a false as response from the chain, we directly tag a product as Broken.

Conclusion

This pattern is a powerful solution for managing request handling in software applications. It offers scalability, flexibility, extensibility and maintainability, making it ideal for building robust and adaptable systems. One of its key advantages is the ease of adding, removing, or modifying handlers without disrupting the overall system structure.

As our project evolved, we encountered new requirements such as adding and modifying rules dynamically. The pattern allowed us to integrate these changes without impacting the system’s architecture. Additionally, we leveraged the pattern to create multiple chains that execute based on different product properties.

Now, our application has multiple tags and chains that can run concurrently, providing efficient and customizable handling of various requests. This demonstrates the pattern’s ability to support complex business logic and adapt to evolving requirements, making it a valuable pattern in our application.

Special thanks to Yusuf , Vlad and Mehmet for their valuable contributions to the project.

We’re building a team of the brightest minds in our industry. Interested in joining us? Visit the pages below to learn more about our open positions.

--

--