Blog

Part 2: Go Meets Different Patterns

Category
Software development
Part 2: Go Meets Different Patterns

As mentioned in the first part of the blog, I am a lifetime Java developer switching to Go programming language to implement a simple container-based K8s API client. Exploring Go features was fun, and I decided to implement a simple message processor.

And what’s the best way to explore it but to use some of the most common design patterns implemented with the Go standard library?

In the previous blog, I covered Singleton and Publish-subscribe. In this one, I’ll cover the implementation of post-factory and command patterns.

Factory pattern

Factory pattern is a creational pattern and more important one of the most commonly used ones.

Using the factory pattern, objects are created without exposing the creation logic to the client. This refers to newly created objects using their common interfaces. For this, we use JSON marshaling and unmarshalling.

Built-in JSON serialization and deserialization

As a modern programming language, Go has first-class support for JSON serialization in its standard library.

It uses the Go json package and its underlying Marshal and Unmarshal functions to serialize and deserialize byte slices to structs or simple types (e.g. maps) and vice versa.

From JSON to Objects

As mentioned earlier, there is a JSON from the HTTP request that is sent down the event bus and received for processing on inputChannel.

This is where the transformation of JSON into a meaningful Go type happens. One could say – to use the json package and Unmarshal whatever is in JSON to a structure. That would be the simplest way to put it. 

However, in this case, JSON contains a field that indicates a type or kind of payload.

In general, there can be a few different payloads that are unmarshaled to different types, as you will see below.

Different payloads

{"kind": "Temperature", "value": -20}
{"kind": "Humidity", "value": 78}
{"kind": "Wind", "gust": 10, "lull": 10, "direction": 10}
{"kind": "Lightning", "power": 1000}Code language: JSON / JSON with Comments (json)

All of the payloads contain a kind field that indicates the corresponding type of event. So, straightforward unmarshaling won’t work in this case.

Factory pattern gives a simple approach to tackling this.

Using the kind field as a discriminant for input parameter in the creation method. It then creates a corresponding structure and returns a reference to it as a type of interface.

First, take a look at the structures which represent each of the above payloads.

type TopicHolder struct {
  NotificationTopics []string
  AlertTopics        []string
}

type TemperatureReading struct {
  TopicHolder
  Reading float32 `json:"value"`
}
type HumidityReading struct {
  TopicHolder
  Reading float32 `json:"value"`
}
type WindReading struct {
  TopicHolder
  Gust      int32   `json:"gust"`
  Lull      int32   `json:"lull"`
  Direction float32 `json:"direction"`
}
type LightningReading struct {
  TopicHolder
  power int32 `json:"power"`
}Code language: JavaScript (javascript)

Notice that these structures do not have a field for mapping the kind since they represent the type of event with their definition.

The factory itself uses a kind field to spawn new events. What does the factory look like, then?

Simply put, the factory is just a method CreateReading on a WeatherEventFactory struct.

Methods in Go, unlike normal functions, are defined using the so-called receiver type which is indicated in front of the method name.

Take a look at the code below.

type WeatherEventFactory struct {
  NotificationTopics []string
  AlertTopics        []string
}
func (factory WeatherEventFactory) CreateReading(rawEvent []byte) (Executor, error) {
  var eventWrapper Wrapper
  if err := deser(rawEvent, &eventWrapper); err != nil {
     return nil, err
  }
  if spawnEvent, present := eventHatchery[eventWrapper.EventType]; present {
     fmt.Printf("Building event [%s]\n", eventWrapper.EventType)
     e := spawnEvent(factory.NotificationTopics, factory.AlertTopics)
     if err := populate(rawEvent, e); err != nil {
        return nil, err
     }
     return e, nil
  } else {
     return nil, fmt.Errorf("unknown event type [%s]", eventWrapper.EventType)
  }
}Code language: JavaScript (javascript)

The CreateReading the method has a receiver type of factory struct and a byte slice parameter that contains the whole JSON payload. It returns either an interface Executor or an error.

JSON slice is deserialized into a wrapper type which contains just a kind field mapping variable, the EventType.

type Type string
type Wrapper struct {
  EventType Type `json:"kind"`
}Code language: JavaScript (javascript)

A reference of the wrapper is sent to the deser function to use it with json.Unmarshal.

And deserialization is simple as this:

func deser(rawEvent []byte, eventWrapper *Wrapper) error {
  err := json.Unmarshal(rawEvent, eventWrapper)
  if err != nil {
     return err
  }
  return nil
}Code language: CSS (css)

Next, in the CreateReading method, we use a deserialized type from the wrapper and call the function for spawning a new Executor.

Executors are created from functions defined inside of the eventHatchery map. Each type of event has its own creational function in this map:

var eventHatchery = map[Type]func(notify []string, alert []string) Executor{
  Temperature: func(notificationTopics []string, alertTopics []string) Executor {
     return &TemperatureReading{TopicHolder: TopicHolder{NotificationTopics: notificationTopics, AlertTopics: alertTopics}}
  },
  Humidity: func(topics []string, _ []string) Executor {
     return &HumidityReading{TopicHolder: TopicHolder{NotificationTopics: topics}}
  },
  Wind: func(notificationTopics []string, alertTopics []string) Executor {
     return &WindReading{TopicHolder: TopicHolder{NotificationTopics: notificationTopics, AlertTopics: alertTopics}}
  },
  Lightning: func(notificationTopics []string, alertTopics []string) Executor {
     return &LightningReading{TopicHolder: TopicHolder{NotificationTopics: notificationTopics, AlertTopics: alertTopics}}
  },
}Code language: JavaScript (javascript)

Finally, a call to the creation function for the event and a call to the populate method which uses the original byte slice and unmarshals the rest of the JSON payload to an actual type, similar to how the wrapper was unmarshalled.

unc populate(rawEvent []byte, event Executor) error {
  err := json.Unmarshal(rawEvent, event)
  if err != nil {
     return err
  }
  return nil
}Code language: CSS (css)

Command pattern

At last, a command pattern, a behavioral pattern where an object (command) encapsulates all parameters to perform some action.

In this way implementation of actual action is hidden from the caller, and the action and implementation of the caller are decoupled.

Interface

Interfaces in Go programming language are at the same time type and, more importantly, a set of methods assigned to a type. Maybe it is harder to explain it with words than with a good example. 

For a nice and simple example, look at the below implementation of the command pattern.

What does this type do?

Back to the code example, JSON is now a Go type of:

  • TemperatureReading,
  • HumidityReading,
  • WindReading,
  • LightningReading.

Naturally, for each of the readings, a notification is sent to the EventBus, along with an additional Alert – if the readings’ value is alert-worthy. 

Action logic is implemented with an Execute method defined inside of the Executor interface. Each of the above readings has that method associated with using receiver argument like the one shown here:

type Executor interface {
  Execute() error
}
func (reading TemperatureReading) Execute() error {
  ...
}
func (reading HumidityReading) Execute() error {
  ...
}
func (reading WindReading) Execute() error {
    ...
}
func (reading LightningReading) Execute() error {
  ...
}Code language: PHP (php)

Each of the methods has its own implementation on how the reading data triggers either notification or alert sending. In case of a failure, the method simply returns an error leaving it to the invoker to handle it.

Looking at the example of WindReading the logic, we can see that we decide if the wind gust is strong enough to send critical, high, or there is no need for an alert at all. Alerts and notifications are sent using the already explained mechanism of the publish-subscribe pattern through the event bus.

Notice the use of reading instance, it is defined as a so-called receiving argument. It provides an instance of the struct on which the call is made. We check the reading parameters and use the injected topics used for alert and notification sending. And the call of Execute function on a structure instance looks like this:

if err := e.Execute(); err != nil {
  fmt.Printf("Error executing event: %s\n", err)
}Code language: JavaScript (javascript)

That’s it; we do the error handling there. Imagine there could be a good use of the event bus for error handling and processing. The whole process function used for processing the event came from the input channel with its JSON payload to the sending notification or alert based on the reading. That would be the following:

func Process(event bus.Event) {
  factory := command.GetFactoryInstance()
  reading, err := factory.CreateReading([]byte(event.Data.(string)))
  if err != nil {
     fmt.Printf("Error deserializing event `%s`: %s\n", event.Data, err)
     return
  }
  if err := reading.Execute(); err != nil {
     fmt.Printf("Error executing event: %s\n", err)
  }
}Code language: JavaScript (javascript)

In other words, simple as fetching the factory instance, deserializing JSON payload using the factory, and executing the reading action on the deserialized reading.

Quick project for a quick conclusion

As you can see in the example project, Go is really simple to learn and use language. There are so many cool features and things you can implement that require so little coding just by using Go programming language or its standard library features. Not to mention that it’s lightweight and quick because of the runtime being part of each binary. 

On the other hand, coming from the world of Java where I have used Spring Framework to a great extent in the past 16 years, and inversion of control and dependency injection. Do I miss it? Of course. If you are developing something a bit bigger or more robust and testable, you need it. 

Fortunately, there are few modules to answer these needs and I guess the best I saw is Google’s Wire.

Overall, if you are developing something from scratch and it needs to be spun up on the cloud or in a container this is a way to Go.

CONTACT US

Exceptional ideas need experienced partners.