Point Of Sale (POS) POC

Heroku Swagger Golang MongoDB


Golang design-based REST API built with Goa.

Instructions:

$ dep ensure    // restore packages
$ go build      // build
$ goa-pos-poc   // start app...

Design-first code generation

Now that the design is done, let’s run goagen on the design package:

cd $GOPATH/src/goa-poc-pos
goagen bootstrap -d goa-poc-pos/design

Example: Creating a new Purchase resource (POST)

By sending a POST request to /purchases prior to create a new resource of type Purchase, a controller-bound function Create is assigned to handle this request. Please find below an example of a few operations available using Context’s HTTP pre-defined responses, Mgo (a MongoDB driver) and error logging within a Goa controller implementation.

// Create runs the create action.
func (c *PurchaseController) Create(ctx *app.CreatePurchaseContext) error {

	newID := bson.NewObjectId()

	ctx.Payload.ID = &newID

	// reuse from connection pool
	session := Database.Session.Copy()
	defer session.Close()

	// inserts the document into Purchase collection
	err := session.DB("services-pos").C("Purchase").Insert(ctx.Payload)

	if err != nil {
	
		// duplicated record?
		if mgo.IsDup(err) {
		
			// Then this purchase already exists. (HTTP 409 - Conflict)
			return ctx.Conflict()
		}
		
		// Ok, there is an error, log it ftw...
		Service.LogError(err.Error())

		// HTTP 500 - Internal Server Error
		return ctx.Err()
	}

	// indicates the new URI for the new resource (e.g. /purchases/{:id})
    ctx.ResponseData.Header().Set("Location", app.PurchaseHref(newID.Hex()))

	// HTTP 201 - Created
	return ctx.Created()
}

Note:

Since Purchase payload type has been defined on the api design at the very begining, field validation was automatically generated preventing the forwarding-request to be processed by a controller until all defined constraints matches the input. Please find below:

Auto-generated payload/media type validation:

Format, size, mandatory fields and even pattern checks are performed without getting swet:

// Code generated by goagen v1.3.0, DO NOT EDIT.
//
// Validate validates the Purchase media type instance.
func (mt *Purchase) Validate() (err error) {
	if mt.TransactionID == "" {
		err = goa.MergeErrors(err, goa.MissingAttributeError(`response`, "transaction_id"))
	}
	if mt.Locator == "" {
		err = goa.MergeErrors(err, goa.MissingAttributeError(`response`, "locator"))
	}

	if mt.Href == "" {
		err = goa.MergeErrors(err, goa.MissingAttributeError(`response`, "href"))
	}
	if utf8.RuneCountInString(mt.Locator) < 1 {
		err = goa.MergeErrors(err, goa.InvalidLengthError(`response.locator`, mt.Locator, utf8.RuneCountInString(mt.Locator), 1, true))
	}
	if utf8.RuneCountInString(mt.Locator) > 30 {
		err = goa.MergeErrors(err, goa.InvalidLengthError(`response.locator`, mt.Locator, utf8.RuneCountInString(mt.Locator), 30, false))
	}
	if mt.PurchaseValue < 0.010000 {
		err = goa.MergeErrors(err, goa.InvalidRangeError(`response.purchase_value`, mt.PurchaseValue, 0.010000, true))
	}
	if ok := goa.ValidatePattern(`^[0-9a-fA-F]{24}$`, mt.TransactionID); !ok {
		err = goa.MergeErrors(err, goa.InvalidPatternError(`response.transaction_id`, mt.TransactionID, `^[0-9a-fA-F]{24}$`))
	}
	return
}


Inconsistencies found in the ongoing request may be sent back to the initiator with a HTTP.400 status code and a auto-generated well readable description a how to fix it instruction in the response body.

{
	"id": "xuykdHvt",
	"code": "bad_request",
	"status": 400,
	"detail": "[Jfhx633K] 400 invalid_request: length of request.locator must be greater than or equal to 1 but got value \"\" (len=0)"
}

Heroku’s application log: server request info

2017-11-12T20:31:57.888292+00:00 app[web.1]: 2017/11/12 20:31:57 [INFO] started req_id=0f89abd1-20f2-44c2-92b5-df4c6f2343d7 POST=/pos/v1/purchases/ from=201.6.135.39 ctrl=PurchaseController action=create.