## Install Go - https://golang.org/doc/install ## Setup Environment 1. Create new directory outside of your `$GOPATH` called `mystore`. To lookup your path execute the following in a console window: ``` $ go env ... GOPATH="/Users/carlosgabaldon/go" ... $ cd $ mkdir mystore $ cd mystore ``` 2. Create a Go module ``` $ go mod init example.com/mystore go: creating new go.mod: module example.com/mystore $ cat go.mod module example.com/mystore go 1.14 ``` 4. Create new directory called `store` ``` $ mkdir store $ cd store ``` 5. Create a new file called `customer.go` which will manage your customer data. ``` $ touch customer.go ``` ## Write the Code 1. Using your editor of choice add the following to `customer.go`: ``` package store import "errors" // List is a customer shopping list type List map[string]float64 // Customer is a shopper at my store type Customer struct { Name string ShoppingList List } // AddItemToShoppingList adds an item to the shopping list, returns error if it already exists func (c *Customer) AddItemToShoppingList(item string, price float64) error { _, exists := c.ShoppingList[item] if exists { return errors.New("item already exists in your list") } c.ShoppingList[item] = price return nil } ``` ## Add a Test 1. Add a new file within the `store` directory called `customer_test.go` with the following: ``` package store import ( "testing" "github.com/stretchr/testify/assert" ) func TestAddItemToShoppingList(t *testing.T) { assert := assert.New(t) shopper := Customer{ Name: "John", ShoppingList: List{ "Eggs": 2.99, "Bread": 2.49, }, } err := shopper.AddItemToShoppingList("Milk", 3.99) assert.NoError(err) } ``` 2. Update your module with the new dependencies. ``` $ go mod tidy ``` 3. Run the tests ``` $ go test ./... ok example.com/mystore/store 0.374s ``` 4. Add a second test to `customer_test.go` ``` func TestAddItemToShoppingListExisting(t *testing.T) { assert := assert.New(t) shopper := Customer{ Name: "John", ShoppingList: List{ "Eggs": 2.99, "Bread": 2.49, "Milk": 3.99, }, } err := shopper.AddItemToShoppingList("Milk", 3.99) assert.NoError(err) } ``` 3. Run the tests again (will fail) ``` ? example.com/mystore [no test files] --- FAIL: TestAddItemToShoppingListExisting (0.00s) customer_test.go:35: Error Trace: customer_test.go:35 Error: Received unexpected error: item already exists in your list Test: TestAddItemToShoppingListExisting FAIL FAIL example.com/mystore/store 0.137s FAIL $ ``` 4. Tests failed because we are getting an expected error. Make the test pass by asserting that we expect an error. ``` func TestAddItemToShoppingListExisting(t *testing.T) { assert := assert.New(t) shopper := Customer{ Name: "John", ShoppingList: List{ "Eggs": 2.99, "Bread": 2.49, "Milk": 3.99, }, } err := shopper.AddItemToShoppingList("Milk", 3.99) assert.Error(err) } ``` 5. Run the tests again (will pass now) ``` $ go test ./... ok example.com/mystore/store 0.436s ``` ## Add Web Server UI 1. Add the following import to `customer.go` ``` import ( //.. "net/http" ) ``` 2. Add this method to `customer.go` ``` // List lists a customers shopping list. func (c *Customer) List(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "Welcome to my store %s\n", c.Name) fmt.Fprintf(w, "\n%s you are buying the following items:", c.Name) for item, price := range c.ShoppingList { fmt.Fprintf(w, "\n%s $%.2f", item, price) } fmt.Fprintf(w, "\n\nThanks for shopping with us %s\n", c.Name) } ``` 3. Create a new file called `main.go` which will be the entry point of your application. ``` $ touch main.go ``` 4. Add the following to `main.go` ``` package main import ( "net/http" "example.com/mystore/store" "github.com/prometheus/common/log" ) func main() { shopper := store.Customer{ Name: "John", ShoppingList: store.List{ "Eggs": 2.99, "Milk": 1.99, "Bread": 2.49, }, } http.HandleFunc("/list", shopper.List) log.Fatal(http.ListenAndServe("localhost:8000", nil)) } ``` 5. Update your module with the new dependencies. ``` $ go mod tidy ``` 6. From within the root of `mystore` enter the following into your console to start the web server: ``` $ go run main.go ``` 7. Open a web browser and navigate to http://localhost:8000/list 8. The following should be displayed in the browser ``` Welcome to my store John John you are buying the following items: Bread $2.49 Eggs $2.99 Milk $1.99 Thanks for shopping with us John ``` 9. One thing to notice is that when refreshing the page the order of the list changes. This is because we used a `map` type for our ShoppingList. When iterating over a map with a range loop, the iteration order is not specified and is not guaranteed to be the same from one iteration to the next. To get a stable iteration order we must maintain a separate data structure that specifies that order. Update `customer.go` with the following and then restart the server: ``` import ( //.. "sort" ) // .. func (c *Customer) List(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "Welcome to my store %s\n", c.Name) fmt.Fprintf(w, "\n%s you are buying the following items:", c.Name) var keys []string for k := range c.ShoppingList { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { fmt.Fprintf(w, "\n%s $%.2f", k, c.ShoppingList[k]) } fmt.Fprintf(w, "\n\nThanks for shopping with us %s\n", c.Name) } ``` # Complete Code Listing ## main.go ``` package main import ( "net/http" "example.com/mystore/store" "github.com/prometheus/common/log" ) func main() { shopper := store.Customer{ Name: "John", ShoppingList: store.List{ "Eggs": 2.99, "Milk": 1.99, "Bread": 2.49, }, } http.HandleFunc("/list", shopper.List) log.Fatal(http.ListenAndServe("localhost:8000", nil)) } ``` ## customer.go ``` package store import ( "errors" "fmt" "net/http" "sort" ) // List is a customer shopping list type List map[string]float64 // Customer is a shoper at my store type Customer struct { Name string ShoppingList List } // List lists a customers shopping list. func (c *Customer) List(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "Welcome to my store %s\n", c.Name) fmt.Fprintf(w, "\n%s you are buying the following items:", c.Name) // Not sorted approach // When iterating over a map with a range loop, // the iteration order is not specified and is // not guaranteed to be the same from one iteration // to the next. /* for item, price := range c.ShoppingList { fmt.Fprintf(w, "\n%s $%.2f", item, price) } */ // Sorted approach // If you require a stable iteration order you must maintain // a separate data structure that specifies that order. var keys []string for k := range c.ShoppingList { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { fmt.Fprintf(w, "\n%s $%.2f", k, c.ShoppingList[k]) } fmt.Fprintf(w, "\n\nThanks for shopping with us %s\n", c.Name) } // AddItemToShoppingList adds an item to the shopping list, returns error if it already exists func (c *Customer) AddItemToShoppingList(item string, price float64) error { _, exists := c.ShoppingList[item] if exists { return errors.New("item already exists in your list") } c.ShoppingList[item] = price return nil } ``` ## go.mod ``` module example.com/mystore go 1.14 require github.com/prometheus/common v0.9.1 ```