An open API service indexing awesome lists of open source software.

https://github.com/psavelis/goa-pos-poc

Design-first POC using GOA
https://github.com/psavelis/goa-pos-poc

go goa golang mgo microservice mongodb openapi openapi-specification poc pos pubsub rest swagger

Last synced: about 2 months ago
JSON representation

Design-first POC using GOA

Awesome Lists containing this project

README

          

# Point Of Sale (POS) POC
[![Heroku](https://heroku-badge.herokuapp.com/?app=psavelis&root=ui&svg=1)](https://psavelis.herokuapp.com/pos/v1/purchases/5a07c7c3f44ead00043e5f96)
[![Swagger](https://img.shields.io/swagger/valid/2.0/https/raw.githubusercontent.com/psavelis/goa-pos-poc/master/public/swagger/swagger.json.svg)](https://raw.githubusercontent.com/psavelis/goa-pos-poc/master/public/swagger/swagger.json)
[![Golang](https://img.shields.io/badge/language-go-blue.svg)](https://img.shields.io/badge/language-go-blue.svg)
[![MongoDB](https://img.shields.io/badge/dbengine-mongodb%203.2-yellow.svg)](https://img.shields.io/badge/dbengine-mongodb%203.2-yellow.svg)

---
Golang design-based REST API built with [Goa](https://goa.design/).

## 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](https://goa.design/) controller implementation.

````go
// 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:
````go
// 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.

````json
{
"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`.