Taking Orders

We start building our Café by developing the Servitør API that will accept orders and putting them in the MySQL database.

The Servitør will be developed in Go using the Gin Web Framework and the GORM library.

Getting started

Start the project with

go mod init schildcafe.servitor

and install the gin framework

go get github.com/gin-gonic/gin

Now it’s time for the first code

package main

import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
)

func setupRouter() *gin.Engine {
	r := gin.New()

	r.GET("/", func(c *gin.Context) {
		c.String(http.StatusOK, "Welcome to the SchildCafé!")
	})

	return r
}
func main() {
	fmt.Println("vim-go")

	r := setupRouter()
	r.Run(":1333")
}

Try running it

go run .

and check

curl http://localhost:1333

Healthcheck

Implement the /healthcheck endpoint.

As per specs, the port must be configurable, so let’s check for an overwrite value from the environment variables. We need to import the "os" module and then define

func getEnv(name string, defaultValue string) string {
	value, exists := os.LookupEnv(name)
	if exists {
		return value
	}
	return defaultValue
}

and then we have

runPort := getEnv("SERVITOR_PORT", "1333")
r := setupRouter()
r.Run(":" + runPort)

Test it out

Try a serving from a different port.

Modelling the orders

Let’s install the gorm library

go get gorm.io/gorm
go get gorm.io/driver/mysql

We’ll be having orders of the form

type order struct {
	ID             string    `json:"orderId" gorm:"type:varchar(50);primaryKey"`
	OrderReceived  time.Time `json:"orderReceived"`
	OrderReady     time.Time `json:"orderReady" gorm:"default:null"`
	OrderRetrieved time.Time `json:"orderRetrieved" gorm:"default:null"`
	OrderSize      int       `json:"orderSize"`
	OrderBrewed    int       `json:"orderBrewed"`
}

Note that the ID is a string, as we are using UUIDs. Thus we’ll need a uuid library

go get github.com/gofrs/uuid

We also need to store the individual coffee jobs

type coffeeListItem struct {
	ID            string `json:"jobID" gorm:"type:varchar(50);primary_key"`
	Product       string `json:"coffeeProduct"`
	OrderID       string `json:"orderId"`
	Order         order
	OrderReceived time.Time `json:"orderReceived"`
	Machine       string    `json:"machine" gorm:"default:null"`
	JobStarted    time.Time `json:"jobStarted" gorm:"default:null"`
	JobReady      time.Time `json:"jobReady" gorm:"default:null"`
	JobRetrieved  time.Time `json:"jobRetrieved" gorm:"default:null"`
}

Now, in order to talk to the database, we need to establish a connection. Let’s import "gorm.io/gorm" and "gorm.io/driver/mysql" and add the following to the start of main()

dsn := getEnv("MYSQL_USER", "root") + ":" + getEnv("MYSQL_PASS", "root") +
    "@tcp(" + getEnv("MYSQL_HOST", "localhost") + ":" + getEnv("MYSQL_PORT", "3306") + ")/" +
    getEnv("MYSQL_DB", "cafe") + "?charset=utf8mb4&parseTime=True&loc=Local"
db, dbErr := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if dbErr != nil {
    panic("failed to connect database")
}
db.Debug().AutoMigrate(&coffeeListItem{})
db.Debug().AutoMigrate(&order{})

The last two lines will ensure the tables exist, but we’ll assume the database is created externally.

We’ll be running mysql in a docker container for the purpose of this exercise.

docker run --name=mysqlserver -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -d mysql:5.6

Don’t for get to delete the container later:

docker stop mysqlserver && docker rm mysqlserver

Exercise

Create the database.

Receiving orders

Let’s model the order payload

type orderSubmission struct {
	ID      string       `json:"orderId" gorm:"primaryKey"`
	Coffees []orderEntry `json:"coffeeOrder"`
}
type orderEntry struct {
	Product string `json:"product"`
	Count   int    `json:"count"`
}

The implementation of the endpoint is part of our setupRouter() function.

r.POST("/submit-order", func(c *gin.Context) {
    var incomingOrder orderSubmission
    c.BindJSON(&incomingOrder)

    resultID := newOrder(incomingOrder.Coffees)

    fmt.Println("Order " + resultID + " accepted")
    c.JSON(http.StatusOK, resultID)
})

While the actual work will be done by

func newOrder(orderedCoffees []orderEntry) string {
}

Let’s build this function one step at a time. We first need to create the order

var newOrder order
myOrderIDUUID, _ := uuid.NewV4()
newOrder.ID = myOrderIDUUID.String()
newOrder.OrderReceived = time.Now().UTC()

We also need to store it’s size for later use

var newOrderSize int = 0
for _, item := range orderedCoffees {
    newOrderSize += item.Count
}
newOrder.OrderSize = newOrderSize

and that’s the order done.

db.Create(&newOrder)

For the individual jobs, we just loop over the items and their counts.

for _, item := range orderedCoffees {
    for i := 0; i < item.Count; i++ {
        var newCoffee coffeeListItem
        myCoffeeIDUUID, _ := uuid.NewV4()
        newCoffee.ID = myCoffeeIDUUID.String()
        newCoffee.OrderID = newOrder.ID
        newCoffee.Product = item.Product
        newCoffee.OrderReceived = newOrder.OrderReceived
        db.Create(&newCoffee)
        fmt.Println("New Job: " + newCoffee.Product + " with ID: " + newCoffee.ID +
            ", part of order " + newCoffee.OrderID)
    }
}

Finally, let’s return the order’s UUID as that’s needed later

return newOrder.ID

Exercise

Refactor the code so as not to loop over orderedCoffees twice.