Skip to content

Instantly share code, notes, and snippets.

@CarlosGabaldon
Last active May 14, 2020 16:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CarlosGabaldon/33acb56527598600bc316bdfa5016a08 to your computer and use it in GitHub Desktop.
Save CarlosGabaldon/33acb56527598600bc316bdfa5016a08 to your computer and use it in GitHub Desktop.
Go to the store!

Install Go

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
    
  3. Create new directory called store
    $ mkdir store
    $ cd store
    
  4. 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)
    }
    
  5. 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
    $ 
    
  6. 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)
    }
    
  7. 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment