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
.