Design Patterns in Go: Chain of Responsibility: there is more than one way to do it

Introduction

The Chain of Responsibility (CoC) pattern describes a chain of command/request receivers. The client has no idea which one of the receivers will handle the request. The beauty of the pattern is again uncoupling: the sender and the receiver do not know about each other. A danger is that no receiver in the chain will handle the request, this has to be taken into account when implementing this pattern.

An excellent example of this could be the way that middleware is implemented in some web-frameworks (Gin, but also ASP.NET core are examples).

So, what does it look like?

Let us break this down:

  1. We have a Client who does a request to a Handler
  2. The Handler has a list of Receivers, and succesively sends the request to a receiver until one handles the request

As you can see, this is not a very complicated pattern

Implementation in Go

In an empty directory open your terminal or commandline, and type:

go mod init github.com/chain_of_responsibility

Then open this directory and add a main.go. We will need these two preliminaries:

package main

import "fmt"

The IHandler interface

Next we will define the IHandler interface:

type IHandler interface {
	SetNext(IHandler) IHandler
	Handle(string) string
}

There are two methods:

  1. The SetNext() method is used to define the next Handler in the chain
  2. In the Handle() method the request is handled (or not as the case may be)

The Handler struct

Next we need to define some basic Handler:

type Handler struct {
	next IHandler
}

func (h *Handler) SetNext(next IHandler) IHandler {
	h.next = next
	return next
}

func (h *Handler) Handle(request string) string {
	if h.next != nil {
		return h.next.Handle(request)
	}
	return ""
}

A short breakdown:

  • All the Handler struct contains is a reference to the next Handler in the chain
  • In the setNext() method we set this next Handler in the chain and return this next Handler. This allows for a more fluent API.
  • The Handle() method is used to iterate over the chain and call the Handle() method on the different receivers.

The Receivers

The chain contains zero or more receivers, so we need to define them:

type FirstReceiver struct {
	Handler
}

func (f *FirstReceiver) Handle(request string) string {
	// this one will not handle any requests
	return f.next.Handle(request)
}

func (f *FirstReceiver) setNext(next IHandler) IHandler {
	f.next = next
	return next
}

Again, let us break this down:

  1. The FirstReceiver struct embeds the Handler struct, which means that it can access the fields of the Handler struct
  2. In Handle() method we decide not to handle any request, and pass the request on to the next receiver in the chain.
  3. With the setNext() method we can set this next receiver in the chain.

The SecondReceiver looks more or less the same:

type SecondReceiver struct {
	Handler
}

func (s *SecondReceiver) Handle(request string) string {
	if request == "Hello" {
		return "World"
	}
	return s.next.Handle(request)
}

func (s *SecondReceiver) setNext(next IHandler) IHandler {
	s.next = next
	return next
}

The only difference is the fact that we decide to handle a request here.

Testing it

Now we can test it:

func main() {
	FirstReceiver := &FirstReceiver{}
	SecondReceiver := &SecondReceiver{}

	FirstReceiver.setNext(SecondReceiver)

	request := "Hello"
	response := FirstReceiver.Handle(request)
	fmt.Println(response)

}

A break down:

  1. We create two receivers
  2. After that we set the secondReceiver as following the first
  3. Next we create a new request, and send it to the FirstReceiver.
  4. And we print the response.

Conclusion

The CoC pattern is a very easy way to have different receivers handle the same request. In a further blog we will see how we make this async (under certain circumstances this is possible)

The implementation in Go was quite straightforward, and the use of embedding made it especially easy.

Leave a Reply

Your email address will not be published. Required fields are marked *