Blogs

Part 1: Go Meets Different Patterns

Category
Software development
Part 1: Go Meets Different Patterns

I’ve been a long-time, or maybe better to say, a lifetime Java and Scala developer. Switching to Go for the past few months made me wonder about the easiest way to learn and digest different Go features and concepts.

The answer was quite simple for me.

A single thing that should be an universal truth in the universe of software development is a design pattern.

So, I’ve decided to show you a few popular design patterns to demonstrate neat Go features.

Go features and me…

I remember first looking at the Go programming language maybe less than ten years ago. Back then, it was in one of the early releases. It was interesting to give it a quick try back then. Quite refreshing, but it still felt a little immature.

I only read about it for a long time without having a chance to use it in something that would run in production. 

In the meantime, Go has gone through 19 releases and became popular. To be exact: a way to go for cloud computing

Recently, on a project I am working on, Go was introduced as a tool for developing lightweight container or cloud-run pluggable service. Finally, I’ve had an opportunity to try it out for something more serious than just another hello world example.

Cool things about Go and its features

On a journey from class-based, object-oriented JVM languages and the whole Java and Scala universe to the Go galaxy, I found a few things that impacted me

Before I jump to the first outstanding one, I want to provide a ground zero: Go runtime is compiled into every Go binary. 

Go runtime provides services like concurrency support, memory allocation, garbage collection, and networking,… 

There are no need for JVM, providing an incredibly fast start-up

1. Dependencies

Except for that ground zero, the first cool thing about Go features is that there is no centrally hosted dependency service. Everything is distributed.

Providing modules to the world is merely publishing to a version control system. Using someone’s module is just a single command away (go install), and including it as an import in your module. Thus, there is no need for a centralized Maven or NPM server or something that might hide your implementation.

Of course, it is not a wild wild west for Go dependency tracking out there. Dependencies can be tracked in your project using Go module tools (go mod), which track your module dependencies and maintain version control using the go.mod file and go.sum file. The former is used for tracking modules and versions, and the latter for verifying the integrity of downloaded modules (verifying the checksums). 

2. Program to an interface, not an implementation

The second thing is bringing the phrase Composition over inheritance to its true meaning.

Go interfaces are abstract types that are implicitly implemented. This means there is no type implemented to the interface, but you implement all interface methods for Go to proclaim your type implementation of the interface.

Moreover, the method can be defined anywhere, meaning if there is a need for some type implements an interface, a simple implementation of a method makes that type an interface implementation. For example, you can group all interface implementations for multiple types in one place. Or you can have it closer to the type definition or define it wherever you need it for GO features.

This is decoupling and type-safety at work in its brightest light. A SOLID design is in place.

3. Lightweight threads

The third big thing, and probably what Go features is most famous for, is concurrency. Goroutines bring straightforward and almost careless ways of executing your functions in parallel. Not just that, channels are used to prevent race conditions in accessing shared memory while using goroutines. 

Unlike threads, goroutines are cheap memory vise, yet a single thread can run thousands of goroutines.

4. Error handling

The fourth thing is error handling, something I have used in Scala. To explain it in one sentence:

Errors are handled by returning a value of type error as the last return value for a function.

All Go error types implement an error interface that has a single Error() string method, meaning you can either define or wrap existing errors using fat.Errorf(…) function, or define your error type and return it.

Go encourages you to check for errors. Unlike some other languages, which throw expectations of you catching them (or not). For me, Go’s idiomatic and verbose error checking can get overwhelming. But it forces developers to think about errors and error handling properly. 

5. Many standard library features

I want to mention many things about the standard library. But first and foremost is the open-source code, which is the best example of good Go practices. 

Go Standard library indicates that it’s a young language offering support for everything you need in nowadays applications. For example, networking (specifically HTTP/2) and file management, native JSON encoding, and decoding.

All this gives you REST HTTP Web services implementation support on a plate.

The rest…

I will list the rest that makes Go cool and important:

  • type identities
  • closures
  • anonymous functions
  • functions as values
  • defer keywords
  • the fact that it’s call-by-value (pointers)…

Design patterns in examples

For the sake of example and simplicity, I have created a repository on GitHub so you can try it out. It’s a simple showcase application that processes JSON messages received on an HTTP listener and sent for deserialization using the event bus. After another deserialization, the event bus is used to notify or alert different parts of the system which are processed.

Singleton pattern

Not to complicate, this one line defines singleton pattern: a creational design pattern that ensures that the class is instantiated only once and it provides access to that instance.

Tools that are used to have singleton implementation are these:

Pointers

Pointers are well known to all those acquainted with C or C++. However, unlike C-based pointers, Go pointers do not support arithmetic operations on them.

A pointer holds a memory address of a value for a particular type and is denoted by the operator *. The pointer can hold zero value nil.

The & operator generates a pointer to its operand or operation of dereferencing.

Locks

The locks or mutex (mutual exclusion) mechanism is used to prevent access to the same variable by multiple threads

Use Go standard library sync.Mutex and its methods Lock and Unlock around code, blocks to ensure that pieces of code are excluded from parallel execution.

Defer

Another thing used is the defer statement which forces function calls to be deferred for the moment the surrounding function returns, while calling parameters are evaluated immediately.

Singleton code example

var lock = &sync.Mutex{}

type EventBus struct {
  ...
}
var eventBusSingleton *EventBus
func GetInstance() *EventBus {
  lock.Lock()
  defer lock.Unlock()
  if eventBusSingleton == nil {
      fmt.Println("Creating EventBus singleton")
      eventBusSingleton = &EventBus{
          topicChannels: map[string]ChannelSlice{},
      }
  }
  return eventBusSingleton
}

In the code above, the GetInstance function returns a pointer to the created EventBus instance and dereferences in a thread-safe way using a block of locks in consequential if statements. Ensuring that only one instance is created at a time.

The defer lock.Unlock() ensures that unlock will happen eventually once the function exists or returns.

The method itself is something that you would do in most programming languages when implementing singleton.

Notice that the variable eventBusSingleton starts with a small case. This indicates that this variable is private and can’t be reached from anywhere outside the definition package. In general, to achieve private or public accessibility in Go, you can use lower or upper case for the variable, functions, or methods.

Just a little note here, creating mutex locks can be a bit expensive if the code is used extensively. You can look at the implementation of WeatherEventFactory, in the GitHub repo. In this case, we added a nil check for the singleton reference, to avoid creating a lock if the instance is already defined. This gives a bit of a performance boost, and for the sake of simplicity, we omitted an additional nil check in the case of EventBus singleton. 

Publish-subscribe pattern

Publish–subscribe is a messaging pattern where senders of messages, called publishers, publish messages without any knowledge of their subscribers if there are any. In contrast to publishers, subscribers have interest in one or more messages. They only receive those that are of interest, without knowledge of which publishers, if there are any.

In this example, topic-based implementation is used.

Let’s put that cool Go tooling to use to do so.

Goroutines

As mentioned earlier, a goroutine is a Go runtime feature that starts a lightweight thread using a single keyword go in your code. A call to go doSomething(x) will start a new goroutine that runs doSomething(x). Keep in mind that doSomething and x are evaluated in the current goroutine and execution of doSomething is happening in the new one.

Channels

Channels, simple built-in tooling for sending and receiving values in goroutines using the channel operator <-. The operator direction denotes the direction of the data flow, thus

channel <- value // sends a value to the channel

value := <- channel // receive from the channel and assign it to the value

Channels are created using make

Publish-subscribe by EventBus

type Event struct {
  Data  interface{}
  Topic string
}
type Channel chan Event
type ChannelSlice []Channel
type EventBus struct {
  topicChannels map[string]ChannelSlice
  mutex         sync.RWMutex
}
var eventBusSingleton *EventBus
func (bus *EventBus) Publish(topic string, data interface{}) {
  bus.mutex.RLock()
  defer bus.mutex.RUnlock()
  if slices, found := bus.topicChannels[topic]; found {
     channels := append(ChannelSlice{}, slices...)
     go func(event Event, dataChannelSlices ChannelSlice) {
        for _, ch := range dataChannelSlices {
           ch <- event
        }
     }(Event{Data: data, Topic: topic}, channels)
  }
}
func (bus *EventBus) Subscribe(topic string, chanel Channel) {
  bus.mutex.Lock()
  defer bus.mutex.Unlock()
  if residual, found := bus.topicChannels[topic]; found {
     bus.topicChannels[topic] = append(residual, chanel)
  } else {
     bus.topicChannels[topic] = append([]Channel{}, chanel)
  }
}

An earlier singleton instance of EventBus was created. Now, take a closer look at the actual implementation.

  • EventBus holds information on topic names mapping to channel slices. Channel slices are defined as a channel of events. 
  • In this implementation, a message is a Data field in an Event type.
  • Channel slices in the map to topic names, providing a list of subscribers to which the message is sent.
  • EventBus holds another type of lock available in the Go standard library.
  • The bus lock is used in Publish and Subscribe to ensure no concurrent access to the map of channel slices to topic names. 
  • Subscribe the method assigns channels to the topics by simply adding new entries to a map.
  • Publish the method takes all assigned channels to selected topic loops through and using the goroutine, and an anonymous function sends an event to a topic-assigned channel.

The following part of the code shows how to create channels, create a singleton EventBus, and subscribe channels to topics.

inputChannel := make(chan bus.Event)
simpleStoutChannel := make(chan bus.Event)
rainbowStoutChannel := make(chan bus.Event)
busInstance := bus.GetInstance()
// we can have topics that send to multiple channels
busInstance.Subscribe("input", inputChannel)
busInstance.Subscribe("input", simpleStoutChannel)
busInstance.Subscribe("notify", simpleStoutChannel)
busInstance.Subscribe("alert", rainbowStoutChannel)

Together with receiving a message shown below in an infinite loop, we call different processors for each of the created channels. 

The topic used is input, which is subscribed to inputChannel which will be doing the deserialization of the JSON message, and simpleStdoutChannel, which prints the event to standard output. 

To keep it simple, we will add two more topics for notifications and alerts (notify and alert topics) which are simply writing differently formatted messages to the standard output. You’ll probably want to process different message types in different ways in real-life examples.

for {
  select {
  case d := <-inputChannel:
     go input.Process(d)
  case d := <-simpleStdoutChannel:
     go notification.Process(d)
  case d := <-shinyStdoutChannel:
     go alert.Process(d)
  }
}

As you can see, the use of, the earlier mentioned, <- channel operator, to assign the value of new messages that eventually get from the channel in this select clause and then run a goroutine for processing each of them.

Channels and goroutines are a simple way of implementing lightweight and simple implementation of the publish-subscribe pattern in a multithreading environment. 

Two patterns down, two more to go

We’re now halfway through having a simple JSON message processor written in Go. Until now, you could see the usage of pointers, locks, goroutines, and channels to implement Singleton and Publish-subscribe patterns. 

Now it’s time for you to see how to implement factory and command patterns using Go interfaces and neat JSON (de)serialization that Go provides out of the box. 

Next

Blog

Try it Out: Math Riddle Game in Flutter

Notch Math riddle game in Flutter

Company

Our people really love it here

Evolution of expertise

The agency was founded in 2014 by seasoned industry veterans with experience from large enterprises. A group of Java engineers then evolved into a full-service company that builds complex web and mobile applications. 

Flexibility is our strong suit — both large enterprises and startups use our services. We are now widely recognized as the leading regional experts in the Java platform and Agile approach.

We make big things happen and we are proud of that.

Personal development

If you join our ranks you’ll be able hone your coding skills on relevant projects for international clients.

Our diverse client portfolio enables our engineers to work on complex long term projects like core technologies for Delivery Hero, Blockchain and NFTs for Fantasy Football or cockpit interface for giants like Strabag. 

Constant education and knowledge sharing are part of our company culture. Highly experienced mentors will help you out and provide real-time feedback and support.

Contact

We’d love to hear from you