• Backend Weekly
  • Posts
  • Part 9: Chain of Responsibility and State Design Patterns (GoF)

Part 9: Chain of Responsibility and State Design Patterns (GoF)

We will explore the Chain of Responsibility Pattern and State Design Pattern under the Behavioral Design Patterns.

In partnership with

Hello “👋

Welcome to another week, another opportunity to become a Great Backend Engineer.

Today’s issue is brought to you by Masteringbackend → A great resource for backend engineers. We offer next-level backend engineering training and exclusive resources.

Before we get started, I have a few announcements:

MBProjects is a project-based learning platform designed specifically for backend engineers. It’s your opportunity to work on 100+ real-world projects that mirror actual problems you’d encounter in the industry.

With MBProjects, you’ll get access to:

  • Pre-built Frontend: Skip the hassle of designing the Frontend and focus purely on building powerful backends.

  • Comprehensive Project Requirement Documents (PRDs): Know exactly what to build and why, with clear goals and specifications.

  • Starter Files & Complete Setup Guides: Jump right into coding with everything set up for you.

  • Task Breakdowns & Progress Tracking: Step-by-step instructions to guide you from start to finish.

  • Community Support: Connect with a group of builders like yourself to share progress, get feedback, and collaborate on projects.

  • Certificates of Completion: Showcase your skills to employers or on LinkedIn as you complete each project.

All of this is available for just $12/month—but only for a limited time! After launch, the price will go up to $24/month.

Interested? Reply “Interested” or 👉 Claim Your Special Launch Price Now 👈

When you log in, click on “Unlock Pro Now”

Streamline your development process with Pinata’s easy File API

  • Easy file uploads and retrieval in minutes

  • No complex setup or infrastructure needed

  • Focus on building, not configurations

Now, back to the business of today.

In this series, I will explore Design Patterns, their types, the GoF design patterns, drawbacks, and benefits for backend engineers.

This comes from my new Vue.js book on “Vue Design Patterns”. However, I’m only transferring the knowledge to backend engineers in this series.

Today, we will explore the Chain of Responsibility Pattern and State Design Pattern under the Behavioral Design Patterns.

Let’s get started quickly.

What is a Chain of Responsibility Pattern?

The chain of responsibility pattern allows an object to pass a request along a chain of potential handlers until the request is handled.

The Chain of Responsibility Pattern is useful because it allows multiple objects to handle a request in a flexible and loosely coupled way.

Instead of sending a request to a single handler, the request is passed along a chain of handlers, where each handler either processes the request or passes it to the next handler in the chain.

This pattern is particularly beneficial when you need to decouple the sender of a request from the receivers, making it easier to extend, modify, or reorder the handlers without changing the client code.

Let’s take an example, In a web application, user requests often need to pass through a series of validation checks (e.g., authentication, authorization, input validation). Each validation check can be represented as a handler in the chain, and new validation rules can be added without modifying the existing system.

// Handler Interface
class RequestHandler {
    setNext(handler) {
        this.nextHandler = handler;
    }

    handle(request) {
        if (this.nextHandler) {
            return this.nextHandler.handle(request);
        }
        return null;
    }
}

To handle the validation pipeline with the chain of responsibility pattern, we created a RequestHandler class and add two methods called setNext and handle respectively.

The simply shows the implementation of a LinkedList data structure.

// Concrete Handlers
class AuthenticationHandler extends RequestHandler {
    handle(request) {
        if (request.isAuthenticated) {
            console.log("Authentication passed.");
            return super.handle(request);
        } else {
            console.log("Authentication failed.");
        }
    }
}

class AuthorizationHandler extends RequestHandler {
    handle(request) {
        if (request.isAuthorized) {
            console.log("Authorization passed.");
            return super.handle(request);
        } else {
            console.log("Authorization failed.");
        }
    }
}

class InputValidationHandler extends RequestHandler {
    handle(request) {
        if (request.isValidInput) {
            console.log("Input validation passed.");
            return super.handle(request);
        } else {
            console.log("Input validation failed.");
        }
    }
}

Next, we created the three validation pipelines that we need, you can add more pipelines as you need but in our example, these three are great. Each pipeline handles a specific type of validation and responds to the result accordingly.


// Client code
const authHandler = new AuthenticationHandler();
const authzHandler = new AuthorizationHandler();
const validationHandler = new InputValidationHandler();

authHandler.setNext(authzHandler);
authzHandler.setNext(validationHandler);

const request = { isAuthenticated: true, isAuthorized: true, isValidInput: true };

authHandler.handle(request);  // Output: Authentication passed. Authorization passed. Input validation passed.

Lastly, the code snippet above shows how the chain of responsibility pattern will be used in your code.

We instantiated the three pipelines and used the setNext method to add the pipeline to be processed after each one.

Next, we sent a request that requires all the pipelines to be processed before passing the result.

Lastly, we use the handle method to handle all the validations that we specified.

The Chain of Responsibility Pattern makes the validation process easier to manage and extend. You can add new validation steps (e.g., rate limiting, and input sanitization) without changing the existing handlers. Each handler has a single responsibility, making the system more maintainable.

Why the Chain of Responsibility Pattern is Useful

The Chain of Responsibility Pattern is particularly beneficial when you need to decouple the sender of a request from the receiver and when multiple handlers might need to process the request. Its main advantages include:

  • Decouples sender from receiver: The sender doesn’t need to know which handler will process the request, promoting loose coupling.

  • Provides flexibility: The chain of handlers can be dynamically configured, reordered, or extended without affecting the client code.

  • Enhances maintainability and extensibility: Each handler focuses on a specific responsibility, making the system more maintainable and easier to extend.

  • Handles different requests in different ways: Different types of requests can be processed by different handlers, ensuring that each request is handled appropriately.

  • Supports dynamic changes: You can modify the chain of handlers at runtime, providing flexibility in how requests are processed.

By using the Chain of Responsibility Pattern, you can build systems that are flexible, maintainable, and easy to extend, especially when multiple objects may need to process a request in different ways.

What is State Pattern?

The state pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class.

This pattern is particularly beneficial when you have an object that can be in different states and its behavior changes depending on its current state.

The State Pattern promotes flexibility and maintainability by encapsulating state-specific behavior into separate classes, avoiding complex conditional statements, and adhering to the Open/Closed Principle.

Let's take an example to help us understand better, consider a document workflow system where a document can be in different states: draft, submitted for review, and approved. Using the State Pattern, each state’s behavior (e.g., editing, submitting, or approving) is encapsulated in its own class.

Let’s start with creating the document state in a class called DocumentState . Here we have all the states that we want such as edit, submit, and approve. Make sure to implement these methods.

// State Interface
class DocumentState {
    edit() {
        throw new Error("Method 'edit()' must be implemented.");
    }

    submit() {
        throw new Error("Method 'submit()' must be implemented.");
    }

    approve() {
        throw new Error("Method 'approve()' must be implemented.");
    }
}

Next, we have to create a concrete state for all the actions we want like Draft, Submitted, and Approved. These states implement the DocumentState and implement all the methods individually.

// Concrete State: Draft
class DraftState extends DocumentState {
    constructor(document) {
        super();
        this.document = document;
    }

    edit() {
        console.log("Editing document in draft state.");
    }

    submit() {
        console.log("Document submitted for review.");
        this.document.setState(this.document.getSubmittedState());
    }

    approve() {
        console.log("Cannot approve document in draft state.");
    }
}

// Concrete State: Submitted Her

// Concrete State: Approved Here

Next, we create our document concrete file and all the methods we need to manipulate a document such as setState, edit, getDraftState, etc. The constructor instantiates all the different states that we can represent our document.

/ Context: Document
class Document {
    constructor() {
        this.draftState = new DraftState(this);
        this.submittedState = new SubmittedState(this);
        this.approvedState = new ApprovedState();
        this.state = this.draftState;
    }

    setState(state) {
        this.state = state;
    }

    getDraftState() {
        return this.draftState;
    }

    getSubmittedState() {
        return this.submittedState;
    }

    getApprovedState() {
        return this.approvedState;
    }

    edit() {
        this.state.edit();
    }

    submit() {
        this.state.submit();
    }

    approve() {
        this.state.approve();
    }
}

Lastly, here’s in our client code, we instantiated the Document object and called the different states that we need to perform on the document.

// Client code
const document = new Document();

document.edit();     // Output: Editing document in draft state.
document.submit();   // Output: Document submitted for review.
document.edit();     // Output: Cannot edit document in submitted state.
document.approve();  // Output: Document approved.
document.submit();   // Output: Cannot submit approved document.

The State Pattern eliminates the need for complex conditional logic in the Document class by delegating state-specific behavior to individual state classes. Each state class handles behavior for that specific state, making the system easier to maintain and modify.

Why the State Pattern is Useful

The State Pattern is particularly beneficial in scenarios where an object’s behavior changes depending on its internal state. Its main advantages include:

  • Encapsulates state-specific behavior: Each state’s behavior is encapsulated in its own class, making the system easier to manage and extend.

  • Eliminates complex conditional logic: The pattern replaces conditional statements with polymorphism, where each state class handles its own behavior.

  • Improves maintainability and extensibility: New states can be added easily without modifying the existing system, adhering to the Open/Closed Principle.

  • Supports state transitions: Each state knows how to transition to other states, simplifying the management of state transitions.

  • Enhances flexibility: You can easily modify, add, or remove states without affecting the rest of the system.

By using the State Pattern, you can build systems that are more flexible, maintainable, and easier to extend, especially when dealing with objects that have multiple, changing states.

That will be all for today. I like to keep this newsletter short.

Today, I discussed the Chain of Responsibility and State Patterns, you learned how to implement them, their similarities, and when to use which.

Next week, I will explore the Mediator and Iterator Patterns under Behavioural Design Patterns.

Don’t miss it. Share with a friend

Did you learn any new things from this newsletter this week? Please reply to this email and let me know. Feedback like this encourages me to keep going.

See you on Next Week.

Remember to start learning backend engineering from our courses:

Top 5 Remote Backend Jobs this week

Here are the top 5 Backend Jobs you can apply to now.

👨‍💻 Prisma
✍️ Remote Senior Backend Software Engineer (Rust)
📍Remote
💰 Click on Apply for salary details
Click here to Apply for this role.

👨‍💻 Varo
✍️ Sr. Software Engineer, Data (Backend)
📍Remote, Worldwide
💰 Click on Apply for salary details
Click here to Apply for this role.

👨‍💻 Coursera
✍️ Staff Software Engineer - Backend
📍Remote, Worldwide
💰 Click on Apply for salary details
Click here to Apply for this role.

👨‍💻 Zoox
✍️ Backend Engineer (f/m/d)
📍Remote
💰 Click on Apply for salary details
Click here to Apply for this role.

Want more Remote Backend Jobs? Visit GetBackendJobs.com

Backend Engineering Resources

Whenever you're ready

There are 4 ways I can help you become a great backend engineer:

1. The MB Platform: Join 1000+ backend engineers learning backend engineering on the MB platform. Build real-world backend projects, track your learnings and set schedules, learn from expert-vetted courses and roadmaps, and solve backend engineering tasks, exercises, and challenges.

2. The MB Academy:​ The “MB Academy” is a 6-month intensive Advanced Backend Engineering BootCamp to produce great backend engineers.

3. MB Video-Based Courses: Join 1000+ backend engineers who learn from our meticulously crafted courses designed to empower you with the knowledge and skills you need to excel in backend development.

4. GetBackendJobs: Access 1000+ tailored backend engineering jobs, manage and track all your job applications, create a job streak, and never miss applying. Lastly, you can hire backend engineers anywhere in the world.

LAST WORD 👋 

How am I doing?

I love hearing from readers, and I'm always looking for feedback. How am I doing with The Backend Weekly? Is there anything you'd like to see more or less of? Which aspects of the newsletter do you enjoy the most?

Hit reply and say hello - I'd love to hear from you!

Stay awesome,
Solomon

I moved my newsletter from Substack to Beehiiv, and it's been an amazing journey. Start yours here.

Reply

or to participate.