Book Image

Go: Design Patterns for Real-World Projects

By : Vladimir Vivien, Mario Castro Contreras, Mat Ryer
Book Image

Go: Design Patterns for Real-World Projects

By: Vladimir Vivien, Mario Castro Contreras, Mat Ryer

Overview of this book

The Go programming language has firmly established itself as a favorite for building complex and scalable system applications. Go offers a direct and practical approach to programming that lets programmers write correct and predictable code using concurrency idioms and a full-featured standard library. This practical guide is full of real-world examples to help you get started with Go in no time at all. You’ll start by understanding the fundamentals of Go, then get a detailed description of the Go data types, program structures, and Maps. After that, you’ll learn how to use Go concurrency idioms to avoid pitfalls and create programs that are exact in expected behavior. Next, you will get familiar with the tools and libraries that are available in Go to write and exercise tests, benchmarking, and code coverage. After that, you will be able to utilize some of the most important features of GO such as Network Programming and OS integration to build efficient applications. Then you’ll start applying your skills to build some amazing projects in Go. You will learn to develop high-quality command-line tools that utilize the powerful shell capabilities and perform well using Go’s built-in concurrency mechanisms. Scale, performance, and high availability lie at the heart of our projects, and the lessons learned throughout the sections will arm you with everything you need to build world-class solutions. You will get a feel for app deployment using Docker and Google App Engine. Each project could form the basis of a start-up, which means they are directly applicable to modern software markets. With these skills in hand, you will be able to conquer all your fears of application development and go on to build large, robust and succinct apps in Go. This Learning Path combines some of the best that Packt has to offer in one complete, curated package. It includes content from the following Packt products: 1. Learning Go Programming 2. Go Design Patterns 3. Go Programming Blueprints, Second Edition
Table of Contents (38 chapters)
Go: Design Patterns for Real-World Projects
Credits
Preface
Bibliography

Go in a nutshell


By design, Go has a simple syntax. Its designers wanted to create a language that is clear, concise, and consistent with few syntactic surprises. When reading Go code, keep this mantra in mind: what you see is what it is. Go shies away from a clever and terse coding style in favor of code that is clear and readable as exemplified by the following program:

// This program prints molecular information for known metalloids 
// including atomic number, mass, and atom count found 
// in 100 grams of each element using the mole unit. 
// See http://en.wikipedia.org/wiki/Mole_(unit) 
package main 
 
import "fmt" 
 
const avogadro float64 = 6.0221413e+23 
const grams = 100.0 
 
type amu float64 
 
func (mass amu) float() float64 { 
  return float64(mass) 
} 
 
type metalloid struct { 
  name   string 
  number int32 
  weight amu 
} 
 
var metalloids = []metalloid{ 
  metalloid{"Boron", 5, 10.81}, 
  metalloid{"Silicon", 14, 28.085}, 
  metalloid{"Germanium", 32, 74.63}, 
  metalloid{"Arsenic", 33, 74.921}, 
  metalloid{"Antimony", 51, 121.760}, 
  metalloid{"Tellerium", 52, 127.60}, 
  metalloid{"Polonium", 84, 209.0}, 
} 
 
// finds # of moles 
func moles(mass amu) float64 { 
  return grams / float64(mass) 
} 
 
// returns # of atoms moles 
func atoms(moles float64) float64 { 
  return moles * avogadro 
} 
 
// return column headers 
func headers() string { 
  return fmt.Sprintf( 
    "%-10s %-10s %-10s Atoms in %.2f Grams\n", 
    "Element", "Number", "AMU", grams, 
  ) 
} 
 
func main() { 
  fmt.Print(headers()) 
 
    for _, m := range metalloids { 
      fmt.Printf( 
    "%-10s %-10d %-10.3f %e\n", 
      m.name, m.number, m.weight.float(), atoms(moles(m.weight)), 
      ) 
    } 
}

golang.fyi/ch01/metalloids.go

When the code is executed, it will give the following output:

$> go run metalloids.go 
Element    Number     AMU        Atoms in 100.00 Grams 
Boron      5          10.810     6.509935e+22 
Silicon    14         28.085     1.691318e+23 
Germanium  32         74.630     4.494324e+23 
Arsenic    33         74.921     4.511848e+23 
Antimony   51         121.760    7.332559e+23 
Tellerium  52         127.600    7.684252e+23 
Polonium   84         209.000    1.258628e+24

If you have never seen Go before, you may not understand some of the details of the syntax and idioms used in the previous program. Nevertheless, when you read the code, there is a good chance you will be able to follow the logic and form a mental model of the program's flow. That is the beauty of Go's simplicity and the reason why so many programmers use it. If you are completely lost, no need to worry, as the subsequent chapters will cover all aspects of the language to get you going.

Functions

Go programs are composed of functions, the smallest callable code unit in the language. In Go, functions are typed entities that can either be named (as shown in the previous example) or be assigned to a variable as a value:

// a simple Go function 
func moles(mass amu) float64 { 
    return float64(mass) / grams 
} 

Another interesting feature about Go functions is their ability to return multiple values as a result of a call. For instance, the previous function could be re-written to return a value of type error in addition to the calculated float64 value:

func moles(mass amu) (float64, error) { 
    if mass < 0 { 
        return 0, error.New("invalid mass") 
    } 
    return (float64(mass) / grams), nil 
}

The previous code uses the multi-return capabilities of Go functions to return both the mass and an error value. You will encounter this idiom throughout the book used as a mean to properly signal errors to the caller of a function. There will be further discussion on multi-return value functions covered in Chapter 5, Functions in Go.

Packages

Source files containing Go functions can be further organized into directory structures known as a package. Packages are logical modules that are used to share code in Go as libraries. You can create your own local packages or use tools provided by Go to automatically pull and use remote packages from a source code repository. You will learn more about Go packages in Chapter 6, Go Packages and Programs.

The workspace

Go follows a simple code layout convention to reliably organize source code packages and to manage their dependencies. Your local Go source code is stored in the workspace, which is a directory convention that contains the source code and runtime artifacts. This makes it easy for Go tools to automatically find, build, and install compiled binaries. Additionally, Go tools rely on the workspace setup to pull source code packages from remote repositories, such as Git, Mercurial, and Subversion, and satisfy their dependencies.

Strongly typed

All values in Go are statically typed. However, the language offers a simple but expressive type system that can have the feel of a dynamic language. For instance, types can be safely inferred as shown in the following code snippet:

const grams = 100.0 

As you would expect, constant grams would be assigned a numeric type, float64, to be precise, by the Go type system. This is true not only for constants, but any variable can use a short-hand form of declaration and assignment as shown in the following example:

package main  
import "fmt"  
func main() { 
  var name = "Metalloids" 
  var triple = [3]int{5,14,84} 
  elements := []string{"Boron","Silicon", "Polonium"} 
  isMetal := false 
  fmt.Println(name, triple, elements, isMetal) 
 
} 

Notice that the variables, in the previous code snippet, are not explicitly assigned a type. Instead, the type system assigns each variable a type based on the literal value in the assignment. Chapter 2, Go Language Essentials and Chapter 4, Data Types, go into more details regarding Go types.

Composite types

Besides the types for simple values, Go also supports composite types such as array, slice, and map. These types are designed to store indexed elements of values of a specified type. For instance, the metalloid example shown previously makes use of a slice, which is a variable-sized array. The variable metalloid is declared as a slice to store a collection of the type metalloid. The code uses the literal syntax to combine the declaration and assignment of a slice of type metalloid:

var metalloids = []metalloid{ 
    metalloid{"Boron", 5, 10.81}, 
    metalloid{"Silicon", 14, 28.085}, 
    metalloid{"Germanium", 32, 74.63}, 
    metalloid{"Arsenic", 33, 74.921}, 
    metalloid{"Antimony", 51, 121.760}, 
    metalloid{"Tellerium", 52, 127.60}, 
    metalloid{"Polonium", 84, 209.0}, 
} 

Go also supports a struct type which is a composite that stores named elements called fields as shown in the following code:

func main() { 
  planet := struct { 
      name string 
      diameter int  
  }{"earth", 12742} 
} 

The previous example uses the literal syntax to declare struct{name string; diameter int} with the value {"earth", 12742}. You can read all about composite types in Chapter 7, Composite Types.

The named type

As discussed, Go provides a healthy set of built-in types, both simple and composite. Go programmers can also define new named types based on an existing underlying type as shown in the following snippet extracted from metalloid in the earlier example:

type amu float64 
 
type metalloid struct { 
  name string 
  number int32 
  weight amu 
} 

The previous snippet shows the definition of two named types, one called amu, which uses type float64 as its underlying type. Type metalloid, on the other hand, uses a struct composite type as its underlying type, allowing it to store values in an indexed data structure. You can read more about declaring new named types in Chapter 4, Data Types.

Methods and objects

Go is not an object-oriented language in a classical sense. Go types do not use a class hierarchy to model the world as is the case with other object-oriented languages. However, Go can support the object-based development idiom, allowing data to receive behaviors. This is done by attaching functions, known as methods, to named types.

The following snippet, extracted from the metalloid example, shows the type amu receiving a method called float() that returns the mass as a float64 value:

type amu float64 
 
func (mass amu) float() float64 { 
    return float64(mass) 
} 

The power of this concept is explored in detail in Chapter 8, Methods, Interfaces, and Objects.

Interfaces

Go supports the notion of a programmatic interface. However, as you will see in Chapter 8, Methods, Interfaces, and Objects, the Go interface is itself a type that aggregates a set of methods that can project capabilities onto values of other types. Staying true to its simplistic nature, implementing a Go interface does not require a keyword to explicitly declare an interface. Instead, the type system implicitly resolves implemented interfaces using the methods attached to a type.

For instance, Go includes the built-in interface called Stringer, defined as follows:

type Stringer interface { 
    String() string 
} 

Any type that has the method String() attached, automatically implements the Stringer interface. So, modifying the definition of the type metalloid, from the previous program, to attach the method String() will automatically implement the Stringer interface:

type metalloid struct { 
    name string 
    number int32 
    weight amu 
} 
func (m metalloid) String() string { 
  return fmt.Sprintf( 
    "%-10s %-10d %-10.3f %e", 
    m.name, m.number, m.weight.float(), atoms(moles(m.weight)), 
  ) 
}  

golang.fyi/ch01/metalloids2.go

The String() methods return a pre-formatted string that represents the value of a metalloid. The function Print(), from the standard library package fmt, will automatically call the method String(), if its parameter implements stringer. So, we can use this fact to print metalloid values as follow:

func main() { 
  fmt.Print(headers()) 
  for _, m := range metalloids { 
    fmt.Print(m, "\n") 
  } 
} 

Again, refer to Chapter 8, Methods, Interfaces, and Objects, for a thorough treatment of the topic of interfaces.

Concurrency and channels

One of the main features that has rocketed Go to its current level of adoption is its inherent support for simple concurrency idioms. The language uses a unit of concurrency known as a goroutine, which lets programmers structure programs with independent and highly concurrent code.

As you will see in the following example, Go also relies on a construct known as a channel used for both communication and coordination among independently running goroutines. This approach avoids the perilous and (sometimes brittle) traditional approach of thread communicating by sharing memory. Instead, Go facilitates the approach of sharing by communicating using channels. This is illustrated in the following example that uses both goroutines and channels as processing and communication primitives:

// Calculates sum of all multiple of 3 and 5 less than MAX value. 
// See https://projecteuler.net/problem=1 
package main 
 
import ( 
  "fmt" 
) 
 
const MAX = 1000 
 
func main() { 
  work := make(chan int, MAX) 
  result := make(chan int) 
 
  // 1. Create channel of multiples of 3 and 5 
  // concurrently using goroutine 
  go func(){ 
    for i := 1; i < MAX; i++ { 
      if (i % 3) == 0 || (i % 5) == 0 { 
        work <- i // push for work 
      } 
    } 
    close(work)  
  }() 
 
  // 2. Concurrently sum up work and put result 
  //    in channel result  
  go func(){ 
    r := 0 
    for i := range work { 
      r = r + i 
    } 
    result <- r 
  }() 
 
  // 3. Wait for result, then print 
  fmt.Println("Total:", <- result) 
} 

golang.fyi/ch01/euler1.go

The code in the previous example splits the work to be done between two concurrently running goroutines (declared with the go keyword) as annotated in the code comment. Each goroutine runs independently and uses the Go channels, work and result, to communicate and coordinate the calculation of the final result. Again, if this code does not make sense at all, rest assured, concurrency has the whole of Chapter 9, Concurrency, dedicated to it.

Memory management and safety

Similar to other compiled and statically-typed languages such as C and C++, Go lets developers have direct influence on memory allocation and layout. When a developer creates a slice (think array) of bytes, for instance, there is a direct representation of those bytes in the underlying physical memory of the machine. Furthermore, Go borrows the notion of pointers to represent the memory addresses of stored values giving Go programs the support of passing function parameters by both value and reference.

Go asserts a highly opinionated safety barrier around memory management with little to no configurable parameters. Go automatically handles the drudgery of bookkeeping for memory allocation and release using a runtime garbage collector. Pointer arithmetic is not permitted at runtime; therefore, developers cannot traverse memory blocks by adding to or subtracting from a base memory address.

Fast compilation

Another one of Go's attractions is its millisecond build-time for moderately-sized projects. This is made possible with features such as a simple syntax, conflict-free grammar, and a strict identifier resolution that forbids unused declared resources such as imported packages or variables. Furthermore, the build system resolves packages using transitivity information stored in the closest source node in the dependency tree. Again, this reduces the code-compile-run cycle to feel more like a dynamic language instead of a compiled language.

Testing and code coverage

While other languages usually rely on third-party tools for testing, Go includes both a built-in API and tools designed specifically for automated testing, benchmarking, and code coverage. Similar to other features in Go, the test tools use simple conventions to automatically inspect and instrument the test functions found in your code.

The following function is a simplistic implementation of the Euclidean division algorithm that returns a quotient and a remainder value (as variables q and r) for positive integers:

func DivMod(dvdn, dvsr int) (q, r int) { 
  r = dvdn 
  for r >= dvsr { 
    q += 1 
    r = r - dvsr 
  } 
  return 
} 

golang.fyi/ch01/testexample/divide.go

In a separate source file, we can write a test function to validate the algorithm by checking the remainder value returned by the tested function using the Go test API as shown in the following code:

package testexample 
import "testing" 
func TestDivide(t *testing.T) { 
  dvnd := 40 
    for dvsor := 1; dvsor < dvnd; dvsor++ { 
      q, r := DivMod(dvnd, dvsor) 
  if (dvnd % dvsor) != r { 
    t.Fatalf("%d/%d q=%d, r=%d, bad remainder.", dvnd, dvsor, q, r) 
    } 
  } 
}  

golang.fyi/ch01/testexample/divide_test.go

To exercise the test source code, simply run Go's test tool as shown in the following example:

$> go test . 
ok   github.com/vladimirvivien/learning-go/ch01/testexample  0.003s

The test tool reports a summary of the test result indicating the package that was tested and its pass/fail outcome. The Go Toolchain comes with many more features designed to help programmers create testable code, including:

  • Automatically instrument code to gather coverage statistics during tests

  • Generating HTML reports for covered code and tested paths

  • A benchmark API that lets developers collect performance metrics from tests

  • Benchmark reports with valuable metrics for detecting performance issues

You can read all about testing and its related tools in Chapter 12, Code Testing.

Documentation

Documentation is a first-class component in Go. Arguably, the language's popularity is in part due to its extensive documentation (see http://golang.org/pkg). Go comes with the Godoc tool, which makes it easy to extract documentation from comment text embedded directly in the source code. For example, to document the function from the previous section, we simply add comment lines directly above the DivMod function as shown in the following example:

// DivMod performs a Eucledan division producing a quotient and remainder. 
// This version only works if dividend and divisor > 0. 
func DivMod(dvdn, dvsr int) (q, r int) { 
... 
}

The Go documentation tool can automatically extract and create HTML-formatted pages. For instance, the following command will start the Godoc tool as a server on localhost port 6000:

$> godoc -http=":6001"

You can then access the documentation of your code directly from your web browser. For instance, the following figure shows the generated documentation snippet for the previous function located at http://localhost:6001/pkg/github.com/vladimirvivien/learning-go/ch01/testexample/:

An extensive library

For its short existence, Go rapidly grew a collection of high-quality APIs as part of its standard library that are comparable to other popular and more established languages. The following, by no means exhaustive, lists some of the core APIs that programmers get out-of-the-box:

  • Complete support for regular expressions with search and replace

  • Powerful IO primitives for reading and writing bytes

  • Full support for networking from socket, TCP/UDP, IPv4, and IPv6

  • APIs for writing production-ready HTTP services and clients

  • Support for traditional synchronization primitives (mutex, atomic, and so on)

  • General-purpose template framework with HTML support

  • Support for JSON/XML serializations

  • RPC with multiple wire formats

  • APIs for archive and compression algorithms: tar, zip/gzip, zlib, and so on

  • Cryptography support for most major algorithms and hash functions

  • Access to OS-level processes, environment info, signaling, and much more

The Go Toolchain

Before we end the chapter, one last aspect of Go that should be highlighted is its collection of tools. While some of these tools were already mentioned in previous sections, others are listed here for your awareness:

  • fmt: Reformats source code to adhere to the standard

  • vet: Reports improper usage of source code constructs

  • lint: Another source code tool that reports flagrant style infractions

  • goimports: Analyzes and fixes package import references in source code

  • godoc: Generates and organizes source code documentation

  • generate: Generates Go source code from directives stored in source code

  • get: Remotely retrieves and installs packages and their dependencies

  • build: Compiles code in a specified package and its dependencies

  • run: Provides the convenience of compiling and running your Go program

  • test: Performs unit tests with support for benchmark and coverage reports

  • oracle static analysis tool: Queries source code structures and elements

  • cgo: Generates source code for interoperability between Go and C