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 anEvent
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
andSubscribe
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.