Representing data in code

We are first going to expose the journeys that users can select from; so, create a new folder called meander in GOPATH and add the following journeys.go code:

package meander 
type j struct { 
  Name       string 
  PlaceTypes []string 
} 
var Journeys = []interface{}{ 
  j{Name: "Romantic", PlaceTypes: []string{"park", "bar",  
   "movie_theater", "restaurant", "florist", "taxi_stand"}}, 
  j{Name: "Shopping", PlaceTypes: []string{"department_store",  "cafe", 
   "clothing_store", "jewelry_store", "shoe_store"}}, 
  j{Name: "Night Out", PlaceTypes: []string{"bar", "casino", "food", 
   "bar", "night_club", "bar", "bar", "hospital"}}, 
  j{Name: "Culture", PlaceTypes: []string{"museum", "cafe", "cemetery", 
   "library", "art_gallery"}}, 
  j{Name: "Pamper", PlaceTypes: []string{"hair_care",  "beauty_salon", 
   "cafe", "spa"}}, 
} 

Here, we define an internal type called j inside the meander package, which we then use to describe the journeys by creating instances of them inside the Journeys slice. This approach is an ultra-simple way of representing data in the code without building a dependency on an external data store.

Tip

As an additional assignment, why not see if you can keep golint happy throughout this process? Every time you add some code, run golint for the packages and satisfy any suggestions that emerge. It cares a lot about exported items that have no documentation; so adding simple comments in the correct format will keep it happy. To learn more about golint, refer to https://github.com/golang/lint.

Of course, this is likely to evolve into just that later, maybe even with the ability for users to create and share their own journeys. Since we are exposing our data via an API, we are free to change the internal implementation without affecting the interface, so this approach is great for a version 1.

Tip

We are using a slice of type []interface{} because we will later implement a general way of exposing public data regardless of the actual types.

A romantic journey consists of a visit first to a park, then a bar, a movie theater, then a restaurant before a visit to a florist, and finally, a taxi ride home; you get the general idea. Feel free to get creative and add others by consulting the supported types in the Google Places API.

You might have noticed that since we are containing our code inside a package called meander (rather than main), our code can never be run as a tool like the other APIs we have written so far. Create two new folders inside meander so that you have a path that looks like meander/cmd/meander; this will house the actual command-line tool that exposes the meander package's capabilities via an HTTP endpoint.

Since we are primarily building a package for our meandering project (something that other tools can import and make use of), the code in the root folder is the meander package, and we nest our command (the main package) inside the cmd folder. We include the additional final meander folder to follow good practices where the command name is the same as the folder if we omitted it, our command would be called cmd instead of meander, which would get confusing.

Inside the cmd/meander folder, add the following code to the main.go file:

package main 
func main() { 
  //meander.APIKey = "TODO" 
  http.HandleFunc("/journeys", func(w http.ResponseWriter,
  r *http.Request) { 
    respond(w, r, meander.Journeys) 
  }) 
  http.ListenAndServe(":8080", http.DefaultServeMux) 
} 
func respond(w http.ResponseWriter, r *http.Request, data  []interface{}) error { 
  return json.NewEncoder(w).Encode(data) 
} 

You will recognize this as a simple API endpoint program, mapping to the /journeys endpoint.

Tip

You'll have to import the encoding/json, net/http, and runtime packages, along with your own meander package you created earlier.

We set the value of APIKey in the meander package (which is commented out for now, since we are yet to implement it) before calling the familiar HandleFunc function on the net/http package to bind our endpoint, which then just responds with the meander.Journeys variable. We borrow the abstract responding concept from the previous chapter by providing a respond function that encodes the specified data to the http.ResponseWriter type.

Let's run our API program by navigating to the cmd/meander folder in a terminal and using go run. We don't need to build this into an executable file at this stage since it's just a single file:

go run main.go

Hit the http://localhost:8080/journeys endpoint, and note that our Journeys data payload is served, which looks like this:

[{ 
  Name: "Romantic", 
  PlaceTypes: [ 
    "park", 
    "bar", 
    "movie_theater", 
    "restaurant", 
    "florist", 
    "taxi_stand" 
  ] 
}, ...] 

This is perfectly acceptable, but there is one major flaw: it exposes internals about our implementation. If we changed the PlaceTypes field name to Types, promises made in our API would break, and it's important that we avoid this.

Projects evolve and change over time, especially successful ones, and as developers, we should do what we can to protect our customers from the impact of the evolution. Abstracting interfaces is a great way to do this, as is taking ownership of the public-facing view of our data objects.

Public views of Go structs

In order to control the public view of structs in Go, we need to invent a way to allow individual journey types to tell us how they want to be exposed. In the root meander folder, create a new file called public.go and add the following code:

package meander 
type Facade interface { 
  Public() interface{} 
} 
func Public(o interface{}) interface{} { 
  if p, ok := o.(Facade); ok { 
    return p.Public() 
  } 
  return o 
} 

The Facade interface exposes a single Public method, which will return the public view of a struct. The exported Public function takes any object and checks whether it implements the Facade interface (does it have a Public() interface{} method?); if it is implemented, it calls the method and returns the result otherwise, it just returns the original object untouched. This allows us to pass anything through the Public function before writing the result to the ResponseWriter object, allowing individual structs to control their public appearance.

Tip

Normally, single method interfaces such as our Facade are named after the method they describe, such as Reader and Writer. However, Publicer is just confusing, so I deliberately broke the rule.

Let's implement a Public method for our j type by adding the following code to journeys.go:

func (j j) Public() interface{} { 
  return map[string]interface{}{ 
    "name":    j.Name, 
    "journey": strings.Join(j.PlaceTypes, "|"), 
  } 
} 

The public view of our j type joins the PlaceTypes field into a single string separated by the pipe character as per our API design.

Head back to cmd/meander/main.go and replace the respond method with one that makes use of our new Public function:

func respond(w http.ResponseWriter, r *http.Request, data []interface{}) error { 
  publicData := make([]interface{}, len(data)) 
  for i, d := range data { 
    publicData[i] = meander.Public(d) 
  } 
  return json.NewEncoder(w).Encode(publicData) 
} 

Here, we iterate over the data slice calling the meander.Public function for each item, building the results into a new slice of the same size. In the case of our j type, its Public method will be called to serve the public view of the data rather than the default view. In a terminal, navigate to the cmd/meander folder again and run go run main.go before hitting http://localhost:8080/journeys. Note that the same data has now changed to a new structure:

[{ 
  journey: "park|bar|movie_theater|restaurant|florist|taxi_stand", 
  name: "Romantic" 
}, ...] 

Note

An alternative way of achieving the same result would be to use tags to control the field names, as we have done in previous chapters, and implement your own []string type that provides a MarshalJSON method which tells the encoder how to marshal your type. Both are perfectly acceptable, but the Facade interface and Public method are probably more expressive (if someone reads the code, isn't it obvious what's going on?) and give us more control.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.254.151