forked from gophercon/2016-talks
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request gophercon#17 from raphael/master
Add "Building Microservices From Design With goa" deck
- Loading branch information
Showing
120 changed files
with
36,005 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Building Microservices From Design With goa | ||
|
||
Slides for GopherCon 2016 talk. |
21 changes: 21 additions & 0 deletions
21
RaphaelSimon-BuildingMicroservicesWithgoa/code/connecting/client.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package acl | ||
|
||
import "public_service/clients/acl/client" // HL | ||
|
||
type Client struct { | ||
*client.Client // HL | ||
} | ||
|
||
func (c *Client) ListACLs(resource *Resource, user *User) ([]*ACL, error) { | ||
resourceHref := BuildResourceHref(resource) | ||
userHref := BuildUserHref(user) | ||
acls, err := c.Client.ListACLs(resourceHref, userHref) // HL | ||
if err != nil { | ||
return nil, ErrBadGateway(err) | ||
} | ||
res := make([]*ACL, len(acls)) | ||
for i, acl := range acls { | ||
res[i] = aclFromResponse(acl) | ||
} | ||
return res, nil | ||
} |
11 changes: 11 additions & 0 deletions
11
RaphaelSimon-BuildingMicroservicesWithgoa/code/connecting/share.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package design | ||
|
||
import . "github.com/goadesign/goa/design" | ||
import . "github.com/goadesign/goa/design/apidsl" | ||
import . "cellar/design/public" // HL | ||
|
||
var CellarPayload = Type("BottlePayload", func() { | ||
Attribute("bottles", CollectionOf(BottlePayload), "Cellar bottles", func() { // HL | ||
MinLength(1) | ||
}) | ||
}) |
19 changes: 19 additions & 0 deletions
19
RaphaelSimon-BuildingMicroservicesWithgoa/code/plugin/gorma.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package design | ||
|
||
import ( | ||
"github.com/goadesign/gorma" | ||
. "github.com/goadesign/gorma/dsl" | ||
) | ||
|
||
var _ = StorageGroup("MyAPIStorage", func() { | ||
Store("postgres", gorma.Postgres, func() { | ||
Model("User", func() { | ||
BuildsFrom(func() { Payload("user", "create") }) | ||
RendersTo(User) | ||
Field("id", gorma.Integer, func() { | ||
PrimaryKey() | ||
Description("This is the User Model PK field") | ||
}) | ||
}) | ||
}) | ||
}) |
50 changes: 50 additions & 0 deletions
50
RaphaelSimon-BuildingMicroservicesWithgoa/code/security/design/design.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package design | ||
|
||
import ( | ||
. "github.com/goadesign/goa/design" | ||
. "github.com/goadesign/goa/design/apidsl" | ||
) | ||
|
||
// JWT defines a security scheme using JWT. The scheme uses the "Authorization" header to lookup | ||
// the token. It also defines then scope "api". | ||
var JWT = JWTSecurity("jwt", func() { // HL | ||
Header("Authorization") // HL | ||
}) // HL | ||
|
||
// Resource jwt uses the JWTSecurity security scheme. | ||
var _ = Resource("jwt", func() { | ||
Description("This resource uses JWT to secure its endpoints") | ||
DefaultMedia(SuccessMedia) | ||
|
||
Security(JWT) // HL | ||
// ... | ||
// OMIT | ||
Action("signin", func() { | ||
Description("Creates a valid JWT") | ||
Security(SigninBasicAuth) | ||
Routing(POST("/jwt/signin")) | ||
Response(NoContent, func() { | ||
Headers(func() { | ||
Header("Authorization", String, "Generated JWT") | ||
}) | ||
}) | ||
Response(Unauthorized) | ||
}) | ||
|
||
Action("secure", func() { | ||
Description("This action is secured with the jwt scheme") | ||
Routing(GET("/jwt")) | ||
Params(func() { | ||
Param("fail", Boolean, "Force auth failure via JWT validation middleware") | ||
}) | ||
Response(OK) | ||
Response(Unauthorized) | ||
}) | ||
|
||
Action("unsecure", func() { | ||
Description("This action does not require auth") | ||
Routing(GET("/jwt/unsecure")) | ||
NoSecurity() // Override the need for auth | ||
Response(OK) | ||
}) | ||
}) |
169 changes: 169 additions & 0 deletions
169
RaphaelSimon-BuildingMicroservicesWithgoa/demos/1-design/app/contexts.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
//************************************************************************// | ||
// API "cellar": Application Contexts | ||
// | ||
// Generated with goagen v0.2.dev, command line: | ||
// $ goagen | ||
// --design=github.com/raphael/gophercon2016/demos/1-design/design | ||
// --out=$(GOPATH)/src/github.com/raphael/gophercon2016/demos/1-design | ||
// --version=v0.2.dev | ||
// | ||
// The content of this file is auto-generated, DO NOT MODIFY | ||
//************************************************************************// | ||
|
||
package app | ||
|
||
import ( | ||
"github.com/goadesign/goa" | ||
"golang.org/x/net/context" | ||
"strconv" | ||
) | ||
|
||
// CreateBottleContext provides the bottle create action context. | ||
type CreateBottleContext struct { | ||
context.Context | ||
*goa.ResponseData | ||
*goa.RequestData | ||
Payload *CreateBottlePayload | ||
} | ||
|
||
// NewCreateBottleContext parses the incoming request URL and body, performs validations and creates the | ||
// context used by the bottle controller create action. | ||
func NewCreateBottleContext(ctx context.Context, service *goa.Service) (*CreateBottleContext, error) { | ||
var err error | ||
resp := goa.ContextResponse(ctx) | ||
resp.Service = service | ||
req := goa.ContextRequest(ctx) | ||
rctx := CreateBottleContext{Context: ctx, ResponseData: resp, RequestData: req} | ||
return &rctx, err | ||
} | ||
|
||
// createBottlePayload is the bottle create action payload. | ||
type createBottlePayload struct { | ||
// Name of bottle | ||
Name *string `json:"name,omitempty" xml:"name,omitempty" form:"name,omitempty"` | ||
// Rating of bottle | ||
Rating *int `json:"rating,omitempty" xml:"rating,omitempty" form:"rating,omitempty"` | ||
// Vintage of bottle | ||
Vintage *int `json:"vintage,omitempty" xml:"vintage,omitempty" form:"vintage,omitempty"` | ||
} | ||
|
||
// Validate runs the validation rules defined in the design. | ||
func (payload *createBottlePayload) Validate() (err error) { | ||
if payload.Name == nil { | ||
err = goa.MergeErrors(err, goa.MissingAttributeError(`payload`, "name")) | ||
} | ||
if payload.Vintage == nil { | ||
err = goa.MergeErrors(err, goa.MissingAttributeError(`payload`, "vintage")) | ||
} | ||
if payload.Rating == nil { | ||
err = goa.MergeErrors(err, goa.MissingAttributeError(`payload`, "rating")) | ||
} | ||
|
||
if payload.Name != nil { | ||
if len(*payload.Name) < 1 { | ||
err = goa.MergeErrors(err, goa.InvalidLengthError(`payload.name`, *payload.Name, len(*payload.Name), 1, true)) | ||
} | ||
} | ||
if payload.Rating != nil { | ||
if *payload.Rating < 1 { | ||
err = goa.MergeErrors(err, goa.InvalidRangeError(`payload.rating`, *payload.Rating, 1, true)) | ||
} | ||
} | ||
if payload.Rating != nil { | ||
if *payload.Rating > 5 { | ||
err = goa.MergeErrors(err, goa.InvalidRangeError(`payload.rating`, *payload.Rating, 5, false)) | ||
} | ||
} | ||
if payload.Vintage != nil { | ||
if *payload.Vintage < 1900 { | ||
err = goa.MergeErrors(err, goa.InvalidRangeError(`payload.vintage`, *payload.Vintage, 1900, true)) | ||
} | ||
} | ||
return | ||
} | ||
|
||
// Publicize creates CreateBottlePayload from createBottlePayload | ||
func (payload *createBottlePayload) Publicize() *CreateBottlePayload { | ||
var pub CreateBottlePayload | ||
if payload.Name != nil { | ||
pub.Name = *payload.Name | ||
} | ||
if payload.Rating != nil { | ||
pub.Rating = *payload.Rating | ||
} | ||
if payload.Vintage != nil { | ||
pub.Vintage = *payload.Vintage | ||
} | ||
return &pub | ||
} | ||
|
||
// CreateBottlePayload is the bottle create action payload. | ||
type CreateBottlePayload struct { | ||
// Name of bottle | ||
Name string `json:"name" xml:"name" form:"name"` | ||
// Rating of bottle | ||
Rating int `json:"rating" xml:"rating" form:"rating"` | ||
// Vintage of bottle | ||
Vintage int `json:"vintage" xml:"vintage" form:"vintage"` | ||
} | ||
|
||
// Validate runs the validation rules defined in the design. | ||
func (payload *CreateBottlePayload) Validate() (err error) { | ||
if payload.Name == "" { | ||
err = goa.MergeErrors(err, goa.MissingAttributeError(`payload`, "name")) | ||
} | ||
|
||
if len(payload.Name) < 1 { | ||
err = goa.MergeErrors(err, goa.InvalidLengthError(`payload.name`, payload.Name, len(payload.Name), 1, true)) | ||
} | ||
if payload.Rating < 1 { | ||
err = goa.MergeErrors(err, goa.InvalidRangeError(`payload.rating`, payload.Rating, 1, true)) | ||
} | ||
if payload.Rating > 5 { | ||
err = goa.MergeErrors(err, goa.InvalidRangeError(`payload.rating`, payload.Rating, 5, false)) | ||
} | ||
if payload.Vintage < 1900 { | ||
err = goa.MergeErrors(err, goa.InvalidRangeError(`payload.vintage`, payload.Vintage, 1900, true)) | ||
} | ||
return | ||
} | ||
|
||
// Created sends a HTTP response with status code 201. | ||
func (ctx *CreateBottleContext) Created() error { | ||
ctx.ResponseData.WriteHeader(201) | ||
return nil | ||
} | ||
|
||
// ShowBottleContext provides the bottle show action context. | ||
type ShowBottleContext struct { | ||
context.Context | ||
*goa.ResponseData | ||
*goa.RequestData | ||
ID int | ||
} | ||
|
||
// NewShowBottleContext parses the incoming request URL and body, performs validations and creates the | ||
// context used by the bottle controller show action. | ||
func NewShowBottleContext(ctx context.Context, service *goa.Service) (*ShowBottleContext, error) { | ||
var err error | ||
resp := goa.ContextResponse(ctx) | ||
resp.Service = service | ||
req := goa.ContextRequest(ctx) | ||
rctx := ShowBottleContext{Context: ctx, ResponseData: resp, RequestData: req} | ||
paramID := req.Params["id"] | ||
if len(paramID) > 0 { | ||
rawID := paramID[0] | ||
if id, err2 := strconv.Atoi(rawID); err2 == nil { | ||
rctx.ID = id | ||
} else { | ||
err = goa.MergeErrors(err, goa.InvalidParamTypeError("id", rawID, "integer")) | ||
} | ||
} | ||
return &rctx, err | ||
} | ||
|
||
// OK sends a HTTP response with status code 200. | ||
func (ctx *ShowBottleContext) OK(r *Bottle) error { | ||
ctx.ResponseData.Header().Set("Content-Type", "application/vnd.gophercon.goa.bottle") | ||
return ctx.ResponseData.Service.Send(ctx.Context, 200, r) | ||
} |
96 changes: 96 additions & 0 deletions
96
RaphaelSimon-BuildingMicroservicesWithgoa/demos/1-design/app/controllers.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
//************************************************************************// | ||
// API "cellar": Application Controllers | ||
// | ||
// Generated with goagen v0.2.dev, command line: | ||
// $ goagen | ||
// --design=github.com/raphael/gophercon2016/demos/1-design/design | ||
// --out=$(GOPATH)/src/github.com/raphael/gophercon2016/demos/1-design | ||
// --version=v0.2.dev | ||
// | ||
// The content of this file is auto-generated, DO NOT MODIFY | ||
//************************************************************************// | ||
|
||
package app | ||
|
||
import ( | ||
"github.com/goadesign/goa" | ||
"golang.org/x/net/context" | ||
"net/http" | ||
) | ||
|
||
// initService sets up the service encoders, decoders and mux. | ||
func initService(service *goa.Service) { | ||
// Setup encoders and decoders | ||
service.Encoder.Register(goa.NewJSONEncoder, "application/json") | ||
service.Encoder.Register(goa.NewGobEncoder, "application/gob", "application/x-gob") | ||
service.Encoder.Register(goa.NewXMLEncoder, "application/xml") | ||
service.Decoder.Register(goa.NewJSONDecoder, "application/json") | ||
service.Decoder.Register(goa.NewGobDecoder, "application/gob", "application/x-gob") | ||
service.Decoder.Register(goa.NewXMLDecoder, "application/xml") | ||
|
||
// Setup default encoder and decoder | ||
service.Encoder.Register(goa.NewJSONEncoder, "*/*") | ||
service.Decoder.Register(goa.NewJSONDecoder, "*/*") | ||
} | ||
|
||
// BottleController is the controller interface for the Bottle actions. | ||
type BottleController interface { | ||
goa.Muxer | ||
Create(*CreateBottleContext) error | ||
Show(*ShowBottleContext) error | ||
} | ||
|
||
// MountBottleController "mounts" a Bottle resource controller on the given service. | ||
func MountBottleController(service *goa.Service, ctrl BottleController) { | ||
initService(service) | ||
var h goa.Handler | ||
|
||
h = func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { | ||
// Check if there was an error loading the request | ||
if err := goa.ContextError(ctx); err != nil { | ||
return err | ||
} | ||
// Build the context | ||
rctx, err := NewCreateBottleContext(ctx, service) | ||
if err != nil { | ||
return err | ||
} | ||
// Build the payload | ||
if rawPayload := goa.ContextRequest(ctx).Payload; rawPayload != nil { | ||
rctx.Payload = rawPayload.(*CreateBottlePayload) | ||
} else { | ||
return goa.MissingPayloadError() | ||
} | ||
return ctrl.Create(rctx) | ||
} | ||
service.Mux.Handle("POST", "/bottles", ctrl.MuxHandler("Create", h, unmarshalCreateBottlePayload)) | ||
service.LogInfo("mount", "ctrl", "Bottle", "action", "Create", "route", "POST /bottles") | ||
|
||
h = func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { | ||
// Check if there was an error loading the request | ||
if err := goa.ContextError(ctx); err != nil { | ||
return err | ||
} | ||
// Build the context | ||
rctx, err := NewShowBottleContext(ctx, service) | ||
if err != nil { | ||
return err | ||
} | ||
return ctrl.Show(rctx) | ||
} | ||
service.Mux.Handle("GET", "/bottles/:id", ctrl.MuxHandler("Show", h, nil)) | ||
service.LogInfo("mount", "ctrl", "Bottle", "action", "Show", "route", "GET /bottles/:id") | ||
} | ||
|
||
// unmarshalCreateBottlePayload unmarshals the request body into the context request data Payload field. | ||
func unmarshalCreateBottlePayload(ctx context.Context, service *goa.Service, req *http.Request) error { | ||
payload := &createBottlePayload{} | ||
if err := service.DecodeRequest(req, payload); err != nil { | ||
return err | ||
} | ||
if err := payload.Validate(); err != nil { | ||
return err | ||
} | ||
goa.ContextRequest(ctx).Payload = payload.Publicize() | ||
return nil | ||
} |
20 changes: 20 additions & 0 deletions
20
RaphaelSimon-BuildingMicroservicesWithgoa/demos/1-design/app/hrefs.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
//************************************************************************// | ||
// API "cellar": Application Resource Href Factories | ||
// | ||
// Generated with goagen v0.2.dev, command line: | ||
// $ goagen | ||
// --design=github.com/raphael/gophercon2016/demos/1-design/design | ||
// --out=$(GOPATH)/src/github.com/raphael/gophercon2016/demos/1-design | ||
// --version=v0.2.dev | ||
// | ||
// The content of this file is auto-generated, DO NOT MODIFY | ||
//************************************************************************// | ||
|
||
package app | ||
|
||
import "fmt" | ||
|
||
// BottleHref returns the resource href. | ||
func BottleHref(id interface{}) string { | ||
return fmt.Sprintf("/bottles/%v", id) | ||
} |
Oops, something went wrong.