├── .editorconfig ├── .gitattributes ├── .gitignore ├── .vscode ├── settings.json └── tasks.json ├── LICENSE ├── README.md ├── app └── storefront-api │ ├── main.go │ ├── middleware │ ├── authentication.go │ └── authentication_test.go │ ├── pipeline.go │ ├── routes │ ├── makes │ │ ├── get.go │ │ ├── get_test.go │ │ └── models │ │ │ └── get.go │ └── ping.go │ └── webserver │ ├── config.go │ ├── mock │ └── mock.go │ └── webserver.go ├── go.mod ├── go.sum ├── internal └── storefront │ ├── config.go │ ├── make.go │ ├── mock │ └── mock.go │ ├── model.go │ └── storefront.go └── pkg ├── authentication ├── authentication.go ├── config.go └── mock │ └── mock.go └── dtos ├── make.go └── model.go /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_size = 4 8 | indent_style = space 9 | trim_trailing_whitespace = true 10 | 11 | [*.go] 12 | indent_size = 8 13 | indent_style = tab -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf encoding=utf-8 2 | 3 | *.jpg binary 4 | *.png binary 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore binaries that might get built (files without extension on Linux and Mac OS) 2 | /app/*/* 3 | !/app/**/*.* 4 | !/app/**/ 5 | /cmd/*/* 6 | !/cmd/**/*.* 7 | !/cmd/**/ 8 | 9 | *.log 10 | 11 | ### Go ### 12 | *.dll 13 | *.dylib 14 | *.exe 15 | *.exe~ 16 | *.out 17 | *.so 18 | *.test 19 | /Godeps/ 20 | 21 | ### Linux Cruft ### 22 | *~ 23 | .directory 24 | .fuse_hidden* 25 | .nfs* 26 | .Trash-* 27 | 28 | ### macOS Cruft ### 29 | __MACOSX 30 | ._* 31 | .apdisk 32 | .AppleDB 33 | .AppleDesktop 34 | .AppleDouble 35 | .com.apple.timemachine.donotpresent 36 | .DS_Store 37 | .DocumentRevisions-V100 38 | .fseventsd 39 | .LSOverride 40 | .Spotlight-V100 41 | .TemporaryItems 42 | .Trashes 43 | .VolumeIcon.icns 44 | Icon 45 | Network Trash Folder 46 | Temporary Items 47 | 48 | ### VisualStudioCode ### 49 | .vscode/* 50 | !.vscode/settings.json 51 | !.vscode/tasks.json 52 | !.vscode/launch.json 53 | !.vscode/extensions.json 54 | 55 | ### VisualStudioCode Patch ### 56 | # Ignore all local history of files 57 | .history 58 | 59 | ### Windows Cruft ### 60 | [Dd]esktop.ini 61 | ehthumbs.db 62 | ehthumbs_vista.db 63 | Thumbs.db 64 | $RECYCLE.BIN/ 65 | *.lnk 66 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "dtos", 4 | "kubernetes", 5 | "microservice", 6 | "msgf" 7 | ], 8 | "go.enableCodeLens":{ 9 | "runtest": true 10 | }, 11 | "files.encoding": "utf8", 12 | "[go]": { 13 | "editor.rulers": [140], 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version":"2.0.0", 3 | "tasks":[ 4 | { 5 | "label":"Upgrade GO dependencies", 6 | "type":"shell", 7 | "command": "go get -u -t ./... && go mod tidy", 8 | "windows": { 9 | "command": "go get -u -t ./...; go mod tidy" 10 | }, 11 | "group":"none", 12 | "presentation": { 13 | "reveal": "always", 14 | "panel": "new" 15 | }, 16 | "problemMatcher": [], 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # golang-microservice-structure 2 | 3 | This is an example repo of the patterns discussed in [How I Structure Web Servers in Go](https://www.dudley.codes/posts/2020.05.19-golang-structure-web-servers/); please view the post for more information. 4 | -------------------------------------------------------------------------------- /app/storefront-api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "github.com/dudleycodes/golang-microservice-structure/app/storefront-api/webserver" 10 | 11 | "github.com/rs/zerolog/log" 12 | ) 13 | 14 | func main() { 15 | hydratedConfig := webserver.Config{} 16 | 17 | srv, err := webserver.New(hydratedConfig) 18 | 19 | if err != nil { 20 | fmt.Printf("Invalid configuration: %s\n", err) 21 | os.Exit(1) 22 | } 23 | 24 | subCommand := flag.String("start", "", "start the webserver") 25 | 26 | switch strings.ToLower(*subCommand) { 27 | case "ping": 28 | err := srv.PingDependencies(false) 29 | if err != nil { 30 | log.Fatal().Err(err).Msg("Ping failed; exiting") 31 | } 32 | 33 | fmt.Println("Ping succeeded") 34 | case "start": 35 | srv.Start(BuildPipeline) 36 | default: 37 | log.Fatal().Msgf("Unrecognized command %q, exiting.\n", *subCommand) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/storefront-api/middleware/authentication.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/dudleycodes/golang-microservice-structure/app/storefront-api/webserver" 7 | 8 | "github.com/rs/zerolog/log" 9 | ) 10 | 11 | // Authentication middleware 12 | func Authentication(srv webserver.Server) func(h http.Handler) http.Handler { 13 | if srv == nil { 14 | log.Fatal().Msg("a nil dependency was passed to auth middleware") 15 | } 16 | 17 | return func(next http.Handler) http.Handler { 18 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 19 | token := r.Header.Get("Authentication") 20 | 21 | if !srv.ValidateJWT(token) { 22 | w.WriteHeader(http.StatusUnauthorized) 23 | w.Write([]byte("401 Unauthorized")) 24 | 25 | return 26 | } 27 | 28 | next.ServeHTTP(w, r) 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/storefront-api/middleware/authentication_test.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/dudleycodes/golang-microservice-structure/app/storefront-api/routes" 9 | 10 | mockserver "github.com/dudleycodes/golang-microservice-structure/app/storefront-api/webserver/mock" 11 | mockauth "github.com/dudleycodes/golang-microservice-structure/pkg/authentication/mock" 12 | 13 | "github.com/gorilla/mux" 14 | ) 15 | 16 | func Test_Authentication(t *testing.T) { 17 | t.Parallel() 18 | 19 | tests := map[string]struct { 20 | authResult mockauth.Result 21 | expectedStatus int 22 | }{ 23 | "Auth Passes": { 24 | expectedStatus: http.StatusOK, 25 | }, 26 | "Auth Fails": { 27 | 28 | authResult: mockauth.ValidateJWTFail(), 29 | expectedStatus: http.StatusUnauthorized, 30 | }, 31 | } 32 | 33 | for name, test := range tests { 34 | t.Run(name, func(t *testing.T) { 35 | srv := mockserver.New().WithAuthentication(test.authResult) 36 | 37 | req, err := http.NewRequest(http.MethodGet, "/ping", nil) 38 | if err != nil { 39 | t.Errorf("test failed while creating new HTTP request, %w", err) 40 | } 41 | 42 | r := mux.NewRouter() 43 | r.Use(Authentication(srv)) 44 | 45 | r.HandleFunc("/ping", routes.Ping(srv)).Methods(http.MethodGet) 46 | 47 | rr := httptest.NewRecorder() 48 | r.ServeHTTP(rr, req) 49 | 50 | if rr.Code != test.expectedStatus { 51 | t.Errorf("Expected status code `%d` but got `%03d`.", test.expectedStatus, rr.Code) 52 | } 53 | }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/storefront-api/pipeline.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/dudleycodes/golang-microservice-structure/app/storefront-api/middleware" 7 | "github.com/dudleycodes/golang-microservice-structure/app/storefront-api/routes" 8 | "github.com/dudleycodes/golang-microservice-structure/app/storefront-api/routes/makes" 9 | "github.com/dudleycodes/golang-microservice-structure/app/storefront-api/routes/makes/models" 10 | "github.com/dudleycodes/golang-microservice-structure/app/storefront-api/webserver" 11 | "github.com/gorilla/mux" 12 | ) 13 | 14 | // BuildPipeline builds the HTTP pipeline 15 | func BuildPipeline(srv webserver.Server, r *mux.Router) { 16 | r.HandleFunc("/ping", routes.Ping(srv)).Methods(http.MethodGet) 17 | 18 | r.Use(middleware.Authentication(srv)) 19 | 20 | r.HandleFunc("`/makes/{makeID}", makes.Get(srv)).Methods(http.MethodGet) 21 | r.HandleFunc("`/makes/{makeID}/models/{modelID}", models.Get(srv)).Methods(http.MethodGet) 22 | } 23 | -------------------------------------------------------------------------------- /app/storefront-api/routes/makes/get.go: -------------------------------------------------------------------------------- 1 | package makes 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strings" 7 | 8 | "github.com/dudleycodes/golang-microservice-structure/app/storefront-api/webserver" 9 | "github.com/gorilla/mux" 10 | 11 | "github.com/rs/zerolog/log" 12 | ) 13 | 14 | // Get a make 15 | func Get(srv webserver.Server) http.HandlerFunc { 16 | if srv == nil { 17 | log.Fatal().Msg("a nil dependency was passed to the `/makes/{makeID}` route") 18 | } 19 | 20 | return func(w http.ResponseWriter, r *http.Request) { 21 | params := mux.Vars(r) 22 | makeID := strings.TrimSpace(params["makeID"]) 23 | 24 | makeDTO, err := srv.GetMake(makeID) 25 | if err != nil { 26 | 27 | w.WriteHeader(http.StatusNotFound) 28 | w.Write([]byte("404 Not Found")) 29 | return 30 | } 31 | 32 | w.WriteHeader(http.StatusOK) 33 | w.Write([]byte(fmt.Sprintf("got make: %s", makeDTO.Value))) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/storefront-api/routes/makes/get_test.go: -------------------------------------------------------------------------------- 1 | package makes 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | mockserver "github.com/dudleycodes/golang-microservice-structure/app/storefront-api/webserver/mock" 10 | mockstore "github.com/dudleycodes/golang-microservice-structure/internal/storefront/mock" 11 | 12 | "github.com/gorilla/mux" 13 | ) 14 | 15 | func TestGet(t *testing.T) { 16 | t.Parallel() 17 | 18 | tests := map[string]struct { 19 | expectedCode int 20 | storefrontResult mockstore.Result 21 | }{ 22 | "found": { 23 | expectedCode: 200, 24 | }, 25 | "not found": { 26 | expectedCode: 404, 27 | storefrontResult: mockstore.GetMakeResult(errors.New("it wasn't found")), 28 | }, 29 | } 30 | 31 | for name, test := range tests { 32 | t.Run(name, func(t *testing.T) { 33 | srv := mockserver.New().WithStorefront(test.storefrontResult) 34 | 35 | r := mux.NewRouter() 36 | r.HandleFunc("/makes/{makeID}", Get(srv)).Methods(http.MethodGet) 37 | 38 | req, err := http.NewRequest(http.MethodGet, "/makes/some-id", nil) 39 | if err != nil { 40 | t.Fatalf("couldn't create test HTTP request: %s", err.Error()) 41 | } 42 | 43 | rr := httptest.NewRecorder() 44 | r.ServeHTTP(rr, req) 45 | 46 | if rr.Code != test.expectedCode { 47 | t.Fatalf("expected status code %03d but got %03d (body: %s)", test.expectedCode, rr.Code, rr.Body) 48 | } 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/storefront-api/routes/makes/models/get.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strings" 7 | 8 | "github.com/dudleycodes/golang-microservice-structure/app/storefront-api/webserver" 9 | "github.com/gorilla/mux" 10 | "github.com/rs/zerolog/log" 11 | ) 12 | 13 | // Get a model 14 | func Get(srv webserver.Server) http.HandlerFunc { 15 | if srv == nil { 16 | log.Fatal().Msg("a nil dependency was passed to the `/makes/{makeID}/models/{modelID}` route") 17 | } 18 | 19 | return func(w http.ResponseWriter, r *http.Request) { 20 | params := mux.Vars(r) 21 | modelID := strings.TrimSpace(params["modelID"]) 22 | 23 | modelDTO, err := srv.GetMake(modelID) 24 | if err != nil { 25 | 26 | w.WriteHeader(http.StatusNotFound) 27 | w.Write([]byte("404 Not Found")) 28 | return 29 | } 30 | 31 | w.WriteHeader(http.StatusOK) 32 | w.Write([]byte(fmt.Sprintf("got model: %s", modelDTO.Value))) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/storefront-api/routes/ping.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/dudleycodes/golang-microservice-structure/app/storefront-api/webserver" 7 | "github.com/rs/zerolog/log" 8 | ) 9 | 10 | // Ping is for the Kubernetes liveness probe 11 | func Ping(srv webserver.Server) http.HandlerFunc { 12 | return func(w http.ResponseWriter, r *http.Request) { 13 | w.WriteHeader(http.StatusOK) 14 | 15 | if _, err := w.Write([]byte(`"PONG"`)); err != nil { 16 | log.Fatal().Msg("Something went very, very wrong") 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/storefront-api/webserver/config.go: -------------------------------------------------------------------------------- 1 | package webserver 2 | 3 | import ( 4 | "github.com/dudleycodes/golang-microservice-structure/internal/storefront" 5 | "github.com/dudleycodes/golang-microservice-structure/pkg/authentication" 6 | ) 7 | 8 | // Config for the storefront API. 9 | type Config struct { 10 | Auth authentication.Config 11 | Storefront storefront.Config 12 | 13 | Port int 14 | } 15 | 16 | func validateConfig(cfg Config) error { 17 | // This function would be used to validate a hydrated configuration; return an error if its invalid. 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /app/storefront-api/webserver/mock/mock.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "github.com/dudleycodes/golang-microservice-structure/internal/storefront" 5 | "github.com/dudleycodes/golang-microservice-structure/pkg/authentication" 6 | 7 | mockstore "github.com/dudleycodes/golang-microservice-structure/internal/storefront/mock" 8 | mockauth "github.com/dudleycodes/golang-microservice-structure/pkg/authentication/mock" 9 | ) 10 | 11 | // Result tells the webserver Mock how to return a specific result 12 | type Result func(c *mockConfig) 13 | 14 | type mockConfig struct { 15 | } 16 | 17 | // Mock the webserver 18 | type Mock struct { 19 | authentication.Authentication 20 | storefront.Storefront 21 | 22 | cfg mockConfig 23 | } 24 | 25 | // New create a new Mock webserver. 26 | func New(opts ...Result) Mock { 27 | r := Mock{ 28 | Authentication: mockauth.New(), 29 | Storefront: mockstore.New(), 30 | } 31 | 32 | for _, o := range opts { 33 | if o != nil { 34 | o(&r.cfg) 35 | } 36 | } 37 | 38 | return r 39 | } 40 | 41 | // WithAuthentication attaches a customized authentication mock 42 | func (m Mock) WithAuthentication(opts ...mockauth.Result) Mock { 43 | m.Authentication = mockauth.New(opts...) 44 | 45 | return m 46 | } 47 | 48 | // WithStorefront attaches a customized storefront mock 49 | func (m Mock) WithStorefront(opts ...mockstore.Result) Mock { 50 | m.Storefront = mockstore.New(opts...) 51 | 52 | return m 53 | } 54 | -------------------------------------------------------------------------------- /app/storefront-api/webserver/webserver.go: -------------------------------------------------------------------------------- 1 | package webserver 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "net/http" 8 | "os" 9 | "strconv" 10 | 11 | "github.com/dudleycodes/golang-microservice-structure/internal/storefront" 12 | "github.com/dudleycodes/golang-microservice-structure/pkg/authentication" 13 | 14 | "github.com/gorilla/mux" 15 | "github.com/rs/zerolog/log" 16 | ) 17 | 18 | // Server exposes all functionalities of the Storefront API. 19 | type Server interface { 20 | authentication.Authentication 21 | storefront.Storefront 22 | } 23 | 24 | // Broker manages the internal state of the Storefront API. 25 | type Broker struct { 26 | authentication.Authentication 27 | storefront.Storefront 28 | 29 | cfg Config // the api service's configuration 30 | router *mux.Router // the api service's route collection 31 | } 32 | 33 | // New initializes a new Storefront API. 34 | func New(cfg Config) (*Broker, error) { 35 | r := &Broker{} 36 | 37 | err := validateConfig(cfg) 38 | if err != nil { 39 | return nil, fmt.Errorf("invalid configuration: %w", err) 40 | } 41 | r.cfg = cfg 42 | 43 | r.Authentication, err = authentication.New(cfg.Auth) 44 | if err != nil { 45 | return nil, fmt.Errorf("invalid auth configuration: %w", err) 46 | } 47 | 48 | r.Storefront, err = storefront.New(cfg.Storefront) 49 | if err != nil { 50 | return nil, fmt.Errorf("invalid storefront configuration: %w", err) 51 | } 52 | 53 | // Do other setup work here... 54 | 55 | return r, nil 56 | } 57 | 58 | // Start the Storefront service 59 | func (bkr *Broker) Start(binder func(s Server, r *mux.Router)) { 60 | bkr.router = mux.NewRouter().StrictSlash(true) 61 | binder(bkr, bkr.router) 62 | 63 | // Do other startup work here... 64 | l, err := net.Listen("tcp", ":"+strconv.Itoa(bkr.cfg.Port)) 65 | if err != nil { 66 | log.Fatal().Err(err).Msgf("Failed to bind to TCP port %d for listening.", bkr.cfg.Port) 67 | os.Exit(13) 68 | } else { 69 | log.Info().Msgf("Starting webserver on TCP port %04d", bkr.cfg.Port) 70 | } 71 | 72 | if err := http.Serve(l, bkr.router); errors.Is(err, http.ErrServerClosed) { 73 | log.Warn().Err(err).Msg("Web server has shut down") 74 | } else { 75 | log.Fatal().Err(err).Msg("Web server has shut down unexpectedly") 76 | } 77 | } 78 | 79 | // PingDependencies ping all Storefront API dependencies. 80 | func (bkr *Broker) PingDependencies(failFast bool) error { 81 | if !bkr.Storefront.Ping() { 82 | return fmt.Errorf("Couldn't ping storefront dependencies") 83 | } 84 | 85 | return nil 86 | } 87 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dudleycodes/golang-microservice-structure 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/gorilla/mux v1.8.0 7 | github.com/rs/zerolog v1.19.0 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 2 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 3 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 4 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 5 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= 6 | github.com/rs/zerolog v1.19.0 h1:hYz4ZVdUgjXTBUmrkrw55j1nHx68LfOKIQk5IYtyScg= 7 | github.com/rs/zerolog v1.19.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo= 8 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 9 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 10 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 11 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 12 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 13 | golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 14 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 15 | -------------------------------------------------------------------------------- /internal/storefront/config.go: -------------------------------------------------------------------------------- 1 | package storefront 2 | 3 | type Config struct { 4 | } 5 | 6 | func validateConfig(cfg Config) error { 7 | // This function would be used to validate a hydrated configuration; return an error if its invalid. 8 | return nil 9 | } 10 | -------------------------------------------------------------------------------- /internal/storefront/make.go: -------------------------------------------------------------------------------- 1 | package storefront 2 | 3 | import ( 4 | "github.com/dudleycodes/golang-microservice-structure/pkg/dtos" 5 | ) 6 | 7 | // GetMake returns a Make DTO 8 | func (bkr Broker) GetMake(id string) (dtos.Make, error) { 9 | return dtos.Make{ 10 | Value: "Some Make", 11 | }, nil 12 | } 13 | -------------------------------------------------------------------------------- /internal/storefront/mock/mock.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import "github.com/dudleycodes/golang-microservice-structure/pkg/dtos" 4 | 5 | // Result sets the result of a mock storefront function 6 | type Result func(c *mockConfig) 7 | 8 | type mockConfig struct { 9 | pingShouldFail bool 10 | getMake error 11 | getModel error 12 | } 13 | 14 | // Mock for mocking Storefront service 15 | type Mock struct { 16 | cfg mockConfig 17 | } 18 | 19 | // New creates a new, mock storefront service 20 | func New(opts ...Result) Mock { 21 | r := Mock{} 22 | 23 | for _, o := range opts { 24 | if o != nil { 25 | o(&r.cfg) 26 | } 27 | } 28 | 29 | return r 30 | } 31 | 32 | // Ping mocks the storefront's Ping() function 33 | func (m Mock) Ping() bool { 34 | return !m.cfg.pingShouldFail 35 | } 36 | 37 | // GetMake mocks a Storefront GetMake() call 38 | func (m Mock) GetMake(string) (dtos.Make, error) { 39 | if m.cfg.getMake != nil { 40 | return dtos.Make{}, m.cfg.getMake 41 | } 42 | 43 | return dtos.Make{ 44 | Value: "mock make", 45 | }, nil 46 | } 47 | 48 | // GetMakeResult sets the result of the mock GetMake() 49 | func GetMakeResult(e error) Result { 50 | return func(c *mockConfig) { 51 | c.getMake = e 52 | } 53 | } 54 | 55 | // GetModel mocks a Storefront GetModel() call 56 | func (m Mock) GetModel(string) (dtos.Model, error) { 57 | if m.cfg.getModel != nil { 58 | return dtos.Model{}, m.cfg.getModel 59 | } 60 | 61 | return dtos.Model{ 62 | Value: "mock model", 63 | }, nil 64 | } 65 | 66 | // GetModelResult sets the result of the mock GetMake() 67 | func GetModelResult(e error) Result { 68 | return func(c *mockConfig) { 69 | c.getModel = e 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /internal/storefront/model.go: -------------------------------------------------------------------------------- 1 | package storefront 2 | 3 | import ( 4 | "github.com/dudleycodes/golang-microservice-structure/pkg/dtos" 5 | ) 6 | 7 | // GetModel returns a Model DTO 8 | func (bkr Broker) GetModel(id string) (dtos.Model, error) { 9 | return dtos.Model{ 10 | Value: "some model", 11 | }, 12 | nil 13 | } 14 | -------------------------------------------------------------------------------- /internal/storefront/storefront.go: -------------------------------------------------------------------------------- 1 | package storefront 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/dudleycodes/golang-microservice-structure/pkg/dtos" 7 | ) 8 | 9 | // Storefront exposes all functionalities of the Storefront service. 10 | type Storefront interface { 11 | GetMake(string) (dtos.Make, error) 12 | GetModel(string) (dtos.Model, error) 13 | Ping() bool 14 | } 15 | 16 | // Broker manages the internal state of the Storefront service. 17 | type Broker struct { 18 | cfg Config // the storefront's configuration 19 | } 20 | 21 | // New initializes a new Storefront service. 22 | func New(cfg Config) (*Broker, error) { 23 | r := &Broker{} 24 | 25 | if err := validateConfig(cfg); err != nil { 26 | return nil, fmt.Errorf("invalid configuration: %w", err) 27 | } 28 | 29 | return r, nil 30 | } 31 | 32 | // Ping checks to see if the storefront's database is responding. 33 | func (brk *Broker) Ping() bool { 34 | // This function would check the storefront's dependencies (datastores and whatnot); useful for Kubernetes probes 35 | return true 36 | } 37 | -------------------------------------------------------------------------------- /pkg/authentication/authentication.go: -------------------------------------------------------------------------------- 1 | package authentication 2 | 3 | // 4 | // This would normal be a package in another repo... 5 | // 6 | 7 | // Auth exposes all functionalities of the Auth agent 8 | type Authentication interface { 9 | ValidateJWT(token string) bool 10 | } 11 | 12 | // Broker manages the internal state of the Auth agent. 13 | type Broker struct{} 14 | 15 | // New create a new authorization agent. 16 | func New(cfg Config) (Authentication, error) { 17 | return &Broker{}, nil 18 | } 19 | 20 | // ValidateJWT validates the JWT token against the remote authorization service. 21 | func (bkr *Broker) ValidateJWT(token string) bool { 22 | // Do JWT validation stuff here 23 | return true 24 | } 25 | -------------------------------------------------------------------------------- /pkg/authentication/config.go: -------------------------------------------------------------------------------- 1 | package authentication 2 | 3 | type Config struct { 4 | URL string 5 | } 6 | -------------------------------------------------------------------------------- /pkg/authentication/mock/mock.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | // Result tells the Authentication Mock how to return a specific result 4 | type Result func(c *mockConfig) 5 | 6 | type mockConfig struct { 7 | validateJWTShouldFail bool 8 | } 9 | 10 | // Mock the Authentication agent 11 | type Mock struct { 12 | cfg mockConfig 13 | } 14 | 15 | // New create a new Mock authorization agent. 16 | func New(opts ...Result) *Mock { 17 | r := &Mock{} 18 | 19 | for _, o := range opts { 20 | if o != nil { 21 | o(&r.cfg) 22 | } 23 | } 24 | 25 | return r 26 | } 27 | 28 | // ValidateJWTFail sets the result for the mock ValidateJWT() function 29 | func ValidateJWTFail() Result { 30 | return func(c *mockConfig) { 31 | c.validateJWTShouldFail = true 32 | } 33 | } 34 | 35 | // ValidateJWT mocks JWT authentication results 36 | func (m Mock) ValidateJWT(token string) bool { 37 | return !m.cfg.validateJWTShouldFail 38 | } 39 | -------------------------------------------------------------------------------- /pkg/dtos/make.go: -------------------------------------------------------------------------------- 1 | package dtos 2 | 3 | // Make DTO 4 | type Make struct { 5 | Value string 6 | } 7 | -------------------------------------------------------------------------------- /pkg/dtos/model.go: -------------------------------------------------------------------------------- 1 | package dtos 2 | 3 | // Model DTO 4 | type Model struct { 5 | Value string 6 | } 7 | --------------------------------------------------------------------------------