Book Image

Building Microservices with Go

By : Nic Jackson
Book Image

Building Microservices with Go

By: Nic Jackson

Overview of this book

Microservice architecture is sweeping the world as the de facto pattern to build web-based applications. Golang is a language particularly well suited to building them. Its strong community, encouragement of idiomatic style, and statically-linked binary artifacts make integrating it with other technologies and managing microservices at scale consistent and intuitive. This book will teach you the common patterns and practices, showing you how to apply these using the Go programming language. It will teach you the fundamental concepts of architectural design and RESTful communication, and show you patterns that provide manageable code that is supportable in development and at scale in production. We will provide you with examples on how to put these concepts and patterns into practice with Go. Whether you are planning a new application or working in an existing monolith, this book will explain and illustrate with practical examples how teams of all sizes can start solving problems with microservices. It will help you understand Docker and Docker-Compose and how it can be used to isolate microservice dependencies and build environments. We finish off by showing you various techniques to monitor, test, and secure your microservices. By the end, you will know the benefits of system resilience of a microservice and the advantages of Go stack.
Table of Contents (18 chapters)
Title Page
Credits
About the Author
About the Reviewers
www.PacktPub.com
Customer Feedback
Preface
Index

Context


The problem with the previous pattern is that there is no way that you can pass the validated request from one handler to the next without breaking the http.Handler interface, but guess what Go has us covered. The context package was listed as experimental for several years before finally making it in to the standard package with Go 1.7. The Context type implements a safe method for accessing request-scoped data that is safe to use simultaneously by multiple Go routines. Let’s take a quick look at this package and then update our example to see it in use.

Background

The Background method returns an empty context that has no values; it is typically used by the main function and as the top-level Context:

func Background() Context 

WithCancel

The WithCancel method returns a copy of the parent context with a cancel function, calling the cancel function releases resources associated with the context and should be called as soon as operations running in the Context type are complete:

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) 

WithDeadline

The WithDeadline method returns a copy of the parent context that expires after the current time is greater than deadline. At this point, the context's Done channel is closed and the resources associated are released. It also passes back a CancelFunc method that allows manual cancellation of the context:

func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) 

WithTimeout

The WithTimeout method is similar to WithDeadline except you pass it a duration for which the Context type should exist. Once this duration has elapsed, the Done channel is closed and the resources associated with the context are released:

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) 

WithValue

The WithValue method returns a copy of the parent Context in which the val value is associated with the key. The Context values are perfect to be used for request-scoped data:

func WithValue(parent Context, key interface{}, val interface{}) Context 

Why not attempt to modify example 1.7 to implement a request scoped context. The key could be in the previous sentence; every request needs its own context.

Using contexts

You probably found that rather painful, especially if you come from a background in a framework such as Rails or Spring. Writing this kind of code is not really something you want to be spending your time on, building application features is far more important. One thing to note however is that neither Ruby or Java have anything more advanced in their base packages. Thankfully for us, over the seven years that Go has been in existence, many excellent people have done just that, and when looking at frameworks in Chapter 3, Introducing Docker, we will find that all of this complexity has been taken care of by some awesome open source authors.

In addition to the adoption of context into the main Go release version 1.7 implements an important update on the http.Request structure, we have the following additions:

func (r *Request) Context() context.Context

The Context() method gives us access to a context.Context structure which is always non nil as it is populated when the request is originally created. For inbound requests the http.Server manages the lifecycle of the context automatically cancelling it when the client connection closes. For outbound requests, Context controls cancellation, by this we mean that if we cancel the Context() method we can cancel the outgoing request. This concept is illustrated in the following example:

70 func fetchGoogle(t *testing.T) {
71   r, _ := http.NewRequest("GET", "https://google.com", nil)
72
73   timeoutRequest, cancelFunc := context.WithTimeout(r.Context(), 1*time.Millisecond)
74   defer cancelFunc()
75
76   r = r.WithContext(timeoutRequest)
77
78   _, err := http.DefaultClient.Do(r)
79   if err != nil {
80     fmt.Println("Error:", err)
81   }
82 }

 

In line 74, we are creating a timeout context from the original in the request, and unlike an inbound request where the context is automatically cancelled for you we must manually perform this step in an outbound request.

 

Line 77 implements the second of the two new context methods which have been added to the http.Request object:

func (r *Request) WithContext(ctx context.Context) *Request

The WithContext object returns a shallow copy of the original request which has the context changed to the given ctx context.

When we execute this function we will find that after 1 millisecond the request will complete with an error:

Error: Get https://google.com: context deadline exceeded

The context is timing out before the request has a change to complete and the do method immediately returns. This is an excellent technique to use for outbound connections and thanks to the changes in Go 1.7 is now incredibly easy to implement.

What about our inbound connection Let’s see how we can update our previous example. Example 1.9 updates our example to show how we can leverage the context package to implement Go routine safe access to objects. The full example can be found in reading_writing_json_8/reading_writing_json_8.go but all of the modification we need to make are in the two ServeHTTP methods for our handlers:

41 func (h validationHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
42   var request helloWorldRequest
43   decoder := json.NewDecoder(r.Body)
44
45   err := decoder.Decode(&request)
46   if err != nil {
47     http.Error(rw, "Bad request", http.StatusBadRequest)
48     return
49   }
50
51   c := context.WithValue(r.Context(), validationContextKey("name"), request.Name)
52   r = r.WithContext(c)
53
54   h.next.ServeHTTP(rw, r)
55 }

If we take a quick look at our validationHandler you will see that when we have a valid request, we are creating a new context for this request and then setting the value of the Name field in the request into the context. You might also wonder what is going on with line 51. When you add an item to a context such as with the WithValue call, the method returns a copy of the previous context, to save a little time and add a little confusion, we are holding a pointer to the context, so in order to pass this as a copy to WithValue, we must dereference it. To update our pointer, we must also set the returned value to the value referenced by the pointer hence again we need to dereference it. The other think we need to look at with this method call is the key, we are using validationContextKey this is an explicitly declared type of string:

13 type validationContextKey string

The reason we are not just using a simple string is that context often flows across packages and if we just used string then we could end up with a key clash where one package within your control is writing a name key and another package which is outside of your control is also using the context and writing a key called name, in this instance the second package would inadvertently overwrite your context value. By declaring a package level type validationContextKey and using this we can ensure that we avoid these collisions:

64 func (h helloWorldHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
65   name := r.Context().Value(validationContextKey("name")).(string)
66   response := helloWorldResponse{Message: "Hello " + name}
67
68   encoder := json.NewEncoder(rw)
69   encoder.Encode(response)
70 }

To retrieve the value, all we have to do is obtain the context and then call the Value method casting it into a string.