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 in 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 having no documentation, so adding simple comments in the correct format will keep it happy. To learn more about golint, see https://github.com/golang/lint.

Of course, this would likely 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.0.

Tip

We are using a slice of type []interface{} because we will later implement a general way of exposing public data regardless of 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 a new folder called cmd inside meander; this will house the actual command-line tool that exposes the meander package's capabilities via an HTTP endpoint.

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

package main
func main() {
  runtime.GOMAXPROCS(runtime.NumCPU())
  //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 the meander package you created earlier.

The runtime.GOMAXPROCS call sets the maximum number of CPUs that our program can use, and we tell it to use them all. We then set the value of APIKey in the meander package (which is commented out for now, since we have 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 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 notice 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, our API would change 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 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 Public function takes any object and checks to see whether it implements the Facade interface (does it have a Public() interface{} method?); and if it is implemented, 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.

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/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 folder again and run go run main.go before hitting http://localhost:8080/journeys again. Notice that the same data has now changed to a new structure:

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

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