DEV Community

Cover image for Master the Chain of Responsibility Pattern in Go with This Real-World Example
Archit Agarwal
Archit Agarwal

Posted on

Master the Chain of Responsibility Pattern in Go with This Real-World Example

🧢 Intro: From Spaghetti to Structured

Ever written a function that starts by fetching user data, dumps it into a file, zips the file, then emails it outβ€Š-β€Šall in one go?
It feels like this:

func exportEverything() {
  fetchUsers()
  parseUsers()
  zipFile()
  emailZip()
}
Enter fullscreen mode Exit fullscreen mode

And sure, it works. Until the day something changes.

Suddenly, you need to skip zipping for certain users, or plug in an S3 upload. You end up sprinkling conditionals everywhere. Before you know it, you've summoned a micro-monolith. And worse? It's not even testable.

So… what do we do?

🧩 Enter: Chain of Responsibility Pattern

The Chain of Responsibility is a behavioral design pattern that lets you pass requests down a chain of handlers. Each handler does its job and optionally passes the request forward.
Think of it like a tech support escalation:

  • Level 1 takes your call
  • If they can't help, they pass it to Level 2
  • If that fails, well… the manager steps in

In Go, we can use this pattern to cleanly break up our export logic into small, reusable, testable chunks.

πŸ› οΈ The Real-World Example: Exporting User Data

Let's say we have a process that:

  1. Fetches users from the DB
  2. Parses them into a file
  3. Zips that file
  4. Emails the zip to the requester

Using the Chain of Responsibility, each step becomes a handler:

type exportStepHandler interface {
 execute(req *exportRequest) error
 setNext(next exportStepHandler)
}
Enter fullscreen mode Exit fullscreen mode

Each handler implements this interface and decides what to do with the request. Here's a quick peek:

πŸƒ Fetch Users

type exportFetchUsers struct {
 next exportStepHandler
}

func (h *exportFetchUsers) execute(req *exportRequest) error {
 fmt.Printf("RequestId(%d): Users fetched from DB\n", req.requestid)
 return h.callNext(req)
}
Enter fullscreen mode Exit fullscreen mode

🧻 Parse Into File

type exportParseIntoFile struct {
 next exportStepHandler
}

func (h *exportParseIntoFile) execute(req *exportRequest) error {
 fmt.Printf("RequestId(%d): Users parsed into file\n", req.requestid)
 return h.callNext(req)
}
Enter fullscreen mode Exit fullscreen mode

πŸ“¦ Zip It Up

type exportZipFiles struct {
 next exportStepHandler
}

func (h *exportZipFiles) execute(req *exportRequest) error {
 fmt.Printf("RequestId(%d): file zipped\n", req.requestid)
 return h.callNext(req)
}
Enter fullscreen mode Exit fullscreen mode

βœ‰οΈ Email the Zip

type exportEmailZip struct {
 next exportStepHandler
}

func (h *exportEmailZip) execute(req *exportRequest) error {
 fmt.Printf("RequestId(%d): Zip emailed\n", req.requestid)
 return h.callNext(req)
}
Enter fullscreen mode Exit fullscreen mode

🧩 Connecting the Chain

We then wire everything like a good old-fashioned factory:

func InitExportData() exportStepHandler {
 emailStep := &exportEmailZip{}

 zipStep := &exportZipFiles{}
 zipStep.setNext(emailStep)

 parseStep := &exportParseIntoFile{}
 parseStep.setNext(zipStep)

 fetchStep := &exportFetchUsers{}
 fetchStep.setNext(parseStep)

 return fetchStep
}
Enter fullscreen mode Exit fullscreen mode

And then… πŸ§™ magic:

func main() {
 exportChain := InitExportData()
 exportChain.execute(&exportRequest{
  requestid: 1,
 })
}
Enter fullscreen mode Exit fullscreen mode

🧠 Why This Pattern Rocks

βœ… Readable: Each handler does one thingβ€Š-β€Šclean and simple.

βœ… Maintainable: Change one step without touching others.

βœ… Flexible: Easily rewire or skip steps depending on context.

βœ… Testable: Unit test each handler in isolation.

βœ… Extensible: Want to log something or upload to S3? Just add a new handler!

🧡 Final Thoughts

If you've ever struggled with long, procedural workflows in Go, this pattern is a game-changer.
The Chain of Responsibility gives you a structure that scales. It turns a rigid flow into a flexible, testable, and modular pipeline.
So next time you think:

"Hmm, maybe I'll just add another if…"

Take a deep breath. Then chain it up. πŸͺ’

Stay Connected!

πŸ’‘ Follow me on LinkedIn: Archit Agarwal 
πŸŽ₯ Subscribe to my YouTube: The Exception Handler
 πŸ“¬ Sign up for my newsletter: The Weekly Golang Journal
✍️ Follow me on Medium: @architagr
πŸ‘¨β€πŸ’» Join my subreddit: r/GolangJournal

Heroku

Build AI apps faster with Heroku.

Heroku makes it easy to build with AI, without the complexity of managing your own AI services. Access leading AI models and build faster with Managed Inference and Agents, and extend your AI with MCP.

Get Started

Top comments (0)

Neon image

Set up a Neon project in seconds and connect from a Go application

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Get started β†’

AWS GenAI LIVE!

GenAI LIVE! is a dynamic live-streamed show exploring how AWS and our partners are helping organizations unlock real value with generative AI.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❀️