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