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
- Host: GitHub
- URL: https://github.com/psavelis/goa-pos-poc
- Owner: psavelis
- License: mit
- Created: 2017-11-05T21:29:23.000Z (over 8 years ago)
- Default Branch: master
- Last Pushed: 2018-02-14T19:29:17.000Z (over 8 years ago)
- Last Synced: 2025-12-18T09:17:42.791Z (6 months ago)
- Topics: go, goa, golang, mgo, microservice, mongodb, openapi, openapi-specification, poc, pos, pubsub, rest, swagger
- Language: Go
- Homepage: https://psavelis.github.io/goa-pos-poc/
- Size: 7.84 MB
- Stars: 2
- Watchers: 1
- Forks: 1
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Point Of Sale (POS) POC
[](https://psavelis.herokuapp.com/pos/v1/purchases/5a07c7c3f44ead00043e5f96)
[](https://raw.githubusercontent.com/psavelis/goa-pos-poc/master/public/swagger/swagger.json)
[](https://img.shields.io/badge/language-go-blue.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`.