From 7b50b02a047ba13fa0686665a7a2b8feaf78b4ff Mon Sep 17 00:00:00 2001 From: Ruidy Nemausat Date: Tue, 14 Jul 2020 13:33:17 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=B9=20clean=20code=20structure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.go | 43 +++++++++------------ products/data/mockProductList.go | 8 +++- products/data/product.go | 37 ++++++------------ {handlers => products/handlers}/products.go | 28 +++++++++++--- products/models/product.go | 23 +++++++++++ 5 files changed, 82 insertions(+), 57 deletions(-) rename {handlers => products/handlers}/products.go (72%) create mode 100644 products/models/product.go diff --git a/main.go b/main.go index 320cec1..ef5b44f 100644 --- a/main.go +++ b/main.go @@ -3,54 +3,49 @@ package main import ( "context" "log" - "net/http" "os" "os/signal" "time" "github.com/gorilla/mux" - "github.com/rjNemo/go-micro/handlers" + + "github.com/rjNemo/go-micro/products/handlers" "github.com/rjNemo/go-micro/server" ) const port = ":5000" func main() { + // create a logger to control application wide logging logger := log.New(os.Stdout, "Product API: ", log.LstdFlags|log.Lshortfile) - // create the handlers - productsHandler := handlers.NewProducts(logger) - // create a server mux and register the handlers + // create a router router := mux.NewRouter() - // GET - getRouter := router.Methods(http.MethodGet).Subrouter() - getRouter.HandleFunc("/", productsHandler.GetProducts) - // POST - postRouter := router.Methods(http.MethodPost).Subrouter() - postRouter.HandleFunc("/", productsHandler.AddProduct) - postRouter.Use(productsHandler.ProductValidationMiddleware) - // PUT - putRouter := router.Methods(http.MethodPut).Subrouter() - putRouter.HandleFunc("/{id:[0-9]+}", productsHandler.UpdateProduct) - putRouter.Use(productsHandler.ProductValidationMiddleware) - // creates a new server + // create the handler + productsHandler := handlers.New(logger) + + // register the handler method to the router + productsHandler.RegisterRoutes(router) + + // creates a production-ready server using the handler srv := server.New(router, port) - // non blocking application server + // start a non blocking application server go func() { - logger.Printf("Server started at address http://localhost%s...", port) - logger.Fatalf("Server failed: %v", srv.ListenAndServe()) + logger.Printf("Server started at address http://localhost%s ...", port) + logger.Fatalf("Server failed: %v", srv.ListenAndServe()) // TODO: use ListenAndServeTLS in production }() // catch sigterm or interrupt and gracefully terminates the server sigChan := make(chan os.Signal) - signal.Notify(sigChan, os.Interrupt) - signal.Notify(sigChan, os.Kill) - + signal.Notify(sigChan, os.Interrupt) // interrupt + signal.Notify(sigChan, os.Kill) // sigterm + // log received signal sig := <-sigChan logger.Printf("Received %v signal... graceful shutdown", sig) - toCtx, _ := context.WithTimeout(context.Background(), 30*time.Second) + toCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + cancel() srv.Shutdown(toCtx) } diff --git a/products/data/mockProductList.go b/products/data/mockProductList.go index 87471ee..8215a0c 100644 --- a/products/data/mockProductList.go +++ b/products/data/mockProductList.go @@ -1,9 +1,13 @@ package data -import "time" +import ( + "time" + + "github.com/rjNemo/go-micro/products/models" +) // dummy persistence layer -var productList = []*Product{ +var productList = []*models.Product{ { ID: 1, Name: "Latte", diff --git a/products/data/product.go b/products/data/product.go index 3ad8421..9d3ea5f 100644 --- a/products/data/product.go +++ b/products/data/product.go @@ -4,28 +4,18 @@ import ( "encoding/json" "fmt" "io" + + "github.com/rjNemo/go-micro/products/models" ) -// Product defines the structure of a product -type Product struct { - ID int `json:"id"` //TODO: use uuid - Name string `json:"name"` - Description string `json:"description"` - Price float32 `json:"price"` // TODO: use int - SKU string `json:"sku"` - CreatedOn string `json:"-"` - UpdatedOn string `json:"-"` - DeletedOn string `json:"-"` -} - -// FromJSON read JSON data to create a new product -func (p *Product) FromJSON(r io.Reader) error { - return json.NewDecoder(r).Decode(p) -} - // Products is the collection of products. // It encapsulates data access logic -type Products []*Product +type Products []*models.Product + +// ToJSON returns all existing product in JSON format +func (p *Products) ToJSON(w io.Writer) error { + return json.NewEncoder(w).Encode(p) // more efficient in memory and time than Marshal +} // AllProducts returns all existing products func AllProducts() Products { @@ -33,13 +23,13 @@ func AllProducts() Products { } // AddProduct add a Product to the dataStore -func AddProduct(p *Product) { +func AddProduct(p *models.Product) { p.ID = getNextID() productList = append(productList, p) } // UpdateProduct edits a Product identified by its id -func UpdateProduct(id int, p *Product) error { +func UpdateProduct(id int, p *models.Product) error { idx, _, err := findProduct(id) if err != nil { return err @@ -53,7 +43,7 @@ func UpdateProduct(id int, p *Product) error { var ErrorProductNotFound = fmt.Errorf("Product not found") // findProduct retrieves a product via its unique identifier -func findProduct(id int) (int, *Product, error) { +func findProduct(id int) (int, *models.Product, error) { for i, p := range productList { if p.ID == id { return i, p, nil @@ -66,8 +56,3 @@ func findProduct(id int) (int, *Product, error) { func getNextID() int { return productList[len(productList)-1].ID + 1 } - -// ToJSON returns all existing product in JSON format -func (p *Products) ToJSON(w io.Writer) error { - return json.NewEncoder(w).Encode(p) // more efficient in memory and time than Marshal -} diff --git a/handlers/products.go b/products/handlers/products.go similarity index 72% rename from handlers/products.go rename to products/handlers/products.go index 069d550..99fd51d 100644 --- a/handlers/products.go +++ b/products/handlers/products.go @@ -9,6 +9,7 @@ import ( "github.com/gorilla/mux" "github.com/rjNemo/go-micro/products/data" + "github.com/rjNemo/go-micro/products/models" ) // Products is a handler for Products API service @@ -16,8 +17,10 @@ type Products struct { logger *log.Logger } -// NewProducts creates a Products handler -func NewProducts(logger *log.Logger) *Products { return &Products{logger: logger} } +// New creates a Products handler +func New(logger *log.Logger) *Products { + return &Products{logger: logger} +} // GetProducts writes all products to response in JSON format func (p *Products) GetProducts(w http.ResponseWriter, r *http.Request) { @@ -37,7 +40,7 @@ func (p *Products) GetProducts(w http.ResponseWriter, r *http.Request) { func (p *Products) AddProduct(w http.ResponseWriter, r *http.Request) { p.logger.Println("Handle 'POST' request") // get product from the request - newProd := r.Context().Value(KeyProduct{}).(*data.Product) // cast into a Product + newProd := r.Context().Value(KeyProduct{}).(*models.Product) // cast into a Product p.logger.Printf("product: %#v", newProd) data.AddProduct(newProd) @@ -50,7 +53,7 @@ func (p *Products) UpdateProduct(w http.ResponseWriter, r *http.Request) { p.logger.Println("Handle 'PUT' request", id) // get product from the request - newProd := r.Context().Value(KeyProduct{}).(*data.Product) // cast into a Product + newProd := r.Context().Value(KeyProduct{}).(*models.Product) // cast into a Product p.logger.Printf("product: %#v", newProd) err := data.UpdateProduct(id, newProd) @@ -72,7 +75,7 @@ type KeyProduct struct{} func (p *Products) ProductValidationMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // create a new product - newProd := &data.Product{} + newProd := &models.Product{} // deserialize JSON to product err := newProd.FromJSON(r.Body) if err != nil { @@ -88,3 +91,18 @@ func (p *Products) ProductValidationMiddleware(next http.Handler) http.Handler { next.ServeHTTP(w, req) }) } + +// RegisterRoutes associates path to controller +func (p *Products) RegisterRoutes(r *mux.Router) { + // GET + getRouter := r.Methods(http.MethodGet).Subrouter() + getRouter.HandleFunc("/", p.GetProducts) + // POST + postRouter := r.Methods(http.MethodPost).Subrouter() + postRouter.HandleFunc("/", p.AddProduct) + postRouter.Use(p.ProductValidationMiddleware) + // PUT + putRouter := r.Methods(http.MethodPut).Subrouter() + putRouter.HandleFunc("/{id:[0-9]+}", p.UpdateProduct) + putRouter.Use(p.ProductValidationMiddleware) +} diff --git a/products/models/product.go b/products/models/product.go new file mode 100644 index 0000000..b9919dd --- /dev/null +++ b/products/models/product.go @@ -0,0 +1,23 @@ +package models + +import ( + "encoding/json" + "io" +) + +// Product defines the structure of a product +type Product struct { + ID int `json:"id"` //TODO: use uuid + Name string `json:"name"` + Description string `json:"description"` + Price float32 `json:"price"` // TODO: use int + SKU string `json:"sku"` + CreatedOn string `json:"-"` + UpdatedOn string `json:"-"` + DeletedOn string `json:"-"` +} + +// FromJSON read JSON data to create a new product +func (p *Product) FromJSON(r io.Reader) error { + return json.NewDecoder(r).Decode(p) +}