diff --git a/.gitignore b/.gitignore index adf8f72..993df3b 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ # Test binary, built with `go test -c` *.test +gen # Output of the go coverage tool, specifically when used with LiteIDE *.out @@ -20,4 +21,3 @@ # Go workspace file go.work - diff --git a/README.md b/README.md index c47fb6a..ea2137d 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,9 @@ Build a microservice application that stores characters and their item inventori - An HTTP/JSON front service which provides an API to manipulate the characters, their inventories, and the items that exist - A GRPC back service that handles CRUD operations for the items that exist and their attributes - A GRPC back service that handles CRUD operations for the characters and their attributes -- A GRPC back service that handles CRUD operations for the characters’ inventories The front service should not have any state of its own and should call the appropriate back services via GRPC to implement its operations. The back services may store their state in memory. CRUD operations mean create, read, update, and delete; however, read should include both listing and showing an individual record. +- A GRPC back service that handles CRUD operations for the characters’ inventories. + +The front service should not have any state of its own and should call the appropriate back services via GRPC to implement its operations. The back services may store their state in memory. CRUD operations mean create, read, update, and delete; however, read should include both listing and showing an individual record. The microservices should be written in Go using the Goa framework and provided as a GitHub repository. diff --git a/design/design.go b/design/design.go new file mode 100644 index 0000000..3195981 --- /dev/null +++ b/design/design.go @@ -0,0 +1,253 @@ +package design + +import ( + . "goa.design/goa/v3/dsl" +) + +var _ = API("api", func() { + Title("API Service") + Description("An HTTP/JSON front service which provides an API to manipulate the characters, their inventories, and the items that exist") + Server("api", func() { + Host("localhost", func() { + URI("http://localhost:8000") + URI("grpc://localhost:8080") + }) + }) +}) + +var empty = Type("empty") + +var item = Type("item", func() { + Field(1, "id", Int) + Field(2, "name", String) + Field(3, "description", String) + Field(4, "multiplier", String) + Field(5, "type", String) + Required("name", "description", "multiplier", "type") +}) + +var character = Type("character", func() { + Field(1, "id", Int) + Field(2, "name", String) + Field(3, "description", String) + Field(4, "class", String) + Required("name", "description", "class") +}) + +var _ = Service("item", func() { + Description("A GRPC back service that handles CRUD operations for the items that exist and their attributes") + + Method("getItem", func() { + Payload(Int) + Result(item) + Error("NotFound") + + HTTP(func() { + GET("/item/{id}") + Response(StatusOK) + Response(StatusBadRequest) + Response(StatusNotFound) + }) + + GRPC(func() { + Response(CodeOK) + }) + }) + + // Method("listItems", func() { + // }) + + Method("createItem", func() { + Payload(item) + Result(item) + Error("BadRequest") + + HTTP(func() { + POST("/item") + Body(item) + Response(StatusOK) + Response(StatusBadRequest) + }) + + GRPC(func() { + Response(CodeOK) + }) + }) + + Method("updateItem", func() { + Payload(item) + Result(item) + Error("NotFound") + Error("BadRequest") + + HTTP(func() { + PUT("/item/{id}") + Body(item) + Response(StatusOK) + Response(StatusBadRequest) + Response(StatusNotFound) + }) + + GRPC(func() { + Response(CodeOK) + }) + }) + + Method("deleteItem", func() { + Payload(Int) + Result(empty) + Error("NotFound") + Error("BadRequest") + + HTTP(func() { + POST("/item/{id}") + Response(StatusOK) + Response(StatusBadRequest) + Response(StatusNotFound) + }) + + GRPC(func() { + Response(CodeOK) + Response("NotFound", CodeNotFound) + Response("BadRequest", CodeInvalidArgument) + }) + }) + + Files("/openapi.json", "./gen/http/openapi.json") +}) + +var _ = Service("character", func() { + Description("A GRPC back service that handles CRUD operations for the characters and their attributes") + + Method("getCharacter", func() { + Payload(Int) + Result(character) + Error("NotFound") + Error("BadRequest") + + HTTP(func() { + GET("/character/{id}") + Response(StatusOK) + Response(StatusBadRequest) + Response(StatusNotFound) + }) + + GRPC(func() { + Response(CodeOK) + Response("NotFound", CodeNotFound) + Response("BadRequest", CodeInvalidArgument) + }) + }) + + // Method("listCharacters", func() { + // }) + + Method("createCharacter", func() { + Payload(character) + Result(character) + Error("BadRequest") + Error("NotFound") + + HTTP(func() { + POST("/character") + Body(character) + Response(StatusBadRequest) + Response(StatusOK) + Response(StatusInternalServerError) + }) + + GRPC(func() { + Response(CodeOK) + Response("NotFound", CodeNotFound) + Response("BadRequest", CodeInvalidArgument) + }) + }) + + Method("updateCharacter", func() { + Payload(character) + Result(character) + Error("NotFound") + Error("BadRequest") + + HTTP(func() { + PUT("/character/{id}") + Body(character) + Response(StatusOK) + Response(StatusBadRequest) + Response(StatusNotFound) + }) + + GRPC(func() { + Response(CodeOK) + Response("NotFound", CodeNotFound) + Response("BadRequest", CodeInvalidArgument) + }) + }) + + Method("deleteCharacter", func() { + Payload(Int) + Result(empty) + Error("NotFound") + Error("BadRequest") + + HTTP(func() { + POST("/character/{id}") + Response(StatusOK) + Response(StatusBadRequest) + Response(StatusNotFound) + }) + + GRPC(func() { + Response(CodeOK) + Response("NotFound", CodeNotFound) + Response("BadRequest", CodeInvalidArgument) + }) + }) + + Files("/openapi.json", "./gen/http/openapi.json") +}) + +var _ = Service("inventory", func() { + Description("A GRPC back service that handles CRUD operations for the characters’ inventories") + + // Method("listItems", func() { + // }) + + Method("addItem", func() { + Payload(Int) + Result(empty) + Error("NotFound") + Error("BadRequest") + + HTTP(func() { + POST("/inventory/{characterId}") + Response(StatusOK) + Response(StatusBadRequest) + }) + + GRPC(func() { + Response(CodeOK) + }) + }) + + Method("removeItem", func() { + Payload(Int) + Result(empty) + Error("NotFound") + Error("BadRequest") + + HTTP(func() { + PUT("/inventory/{characterId}") + Response(StatusOK) + Response(StatusBadRequest) + }) + + GRPC(func() { + Response(CodeOK) + Response("NotFound", CodeNotFound) + Response("BadRequest", CodeInvalidArgument) + }) + }) + + Files("/openapi.json", "./gen/http/openapi.json") +}) \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..220a267 --- /dev/null +++ b/go.mod @@ -0,0 +1,18 @@ +module crossnokaye-interview-assignment + +go 1.20 + +require goa.design/goa/v3 v3.12.3 + +require ( + github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d // indirect + github.com/sergi/go-diff v1.3.1 // indirect + github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/text v0.11.0 // indirect + golang.org/x/tools v0.11.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d16147e --- /dev/null +++ b/go.sum @@ -0,0 +1,34 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 h1:MGKhKyiYrvMDZsmLR/+RGffQSXwEkXgfLSA08qDn9AI= +github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598/go.mod h1:0FpDmbrt36utu8jEmeU05dPC9AB5tsLYVVi+ZHfyuwI= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d h1:Zj+PHjnhRYWBK6RqCDBcAhLXoi3TzC27Zad/Vn+gnVQ= +github.com/manveru/faker v0.0.0-20171103152722-9fbc68a78c4d/go.mod h1:WZy8Q5coAB1zhY9AOBJP0O6J4BuDfbupUDavKY+I3+s= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea h1:CyhwejzVGvZ3Q2PSbQ4NRRYn+ZWv5eS1vlaEusT+bAI= +github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea/go.mod h1:eNr558nEUjP8acGw8FFjTeWvSgU1stO7FAO6eknhHe4= +goa.design/goa/v3 v3.12.3 h1:LHQDUp7t67Ml8pyQc1ywSa14eQ3JaTUBESMgOKF19bI= +goa.design/goa/v3 v3.12.3/go.mod h1:y78cWNxip293j/ut0fvu8FH+s61ojHKyLbTQumk+BB4= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= +golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=