Point Of Sale (POS) POC
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.