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.
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.
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.
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.
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" }, ...]