🧹 clean code structure

This commit is contained in:
Ruidy Nemausat 2020-07-14 13:33:17 +02:00
parent 51520e2238
commit 7b50b02a04
5 changed files with 82 additions and 57 deletions

43
main.go
View file

@ -3,54 +3,49 @@ package main
import ( import (
"context" "context"
"log" "log"
"net/http"
"os" "os"
"os/signal" "os/signal"
"time" "time"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/rjNemo/go-micro/handlers"
"github.com/rjNemo/go-micro/products/handlers"
"github.com/rjNemo/go-micro/server" "github.com/rjNemo/go-micro/server"
) )
const port = ":5000" const port = ":5000"
func main() { func main() {
// create a logger to control application wide logging
logger := log.New(os.Stdout, "Product API: ", log.LstdFlags|log.Lshortfile) logger := log.New(os.Stdout, "Product API: ", log.LstdFlags|log.Lshortfile)
// create the handlers // create a router
productsHandler := handlers.NewProducts(logger)
// create a server mux and register the handlers
router := mux.NewRouter() 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) srv := server.New(router, port)
// non blocking application server // start a non blocking application server
go func() { go func() {
logger.Printf("Server started at address http://localhost%s...", port) logger.Printf("Server started at address http://localhost%s ...", port)
logger.Fatalf("Server failed: %v", srv.ListenAndServe()) logger.Fatalf("Server failed: %v", srv.ListenAndServe()) // TODO: use ListenAndServeTLS in production
}() }()
// catch sigterm or interrupt and gracefully terminates the server // catch sigterm or interrupt and gracefully terminates the server
sigChan := make(chan os.Signal) sigChan := make(chan os.Signal)
signal.Notify(sigChan, os.Interrupt) signal.Notify(sigChan, os.Interrupt) // interrupt
signal.Notify(sigChan, os.Kill) signal.Notify(sigChan, os.Kill) // sigterm
// log received signal
sig := <-sigChan sig := <-sigChan
logger.Printf("Received %v signal... graceful shutdown", sig) 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) srv.Shutdown(toCtx)
} }

View file

@ -1,9 +1,13 @@
package data package data
import "time" import (
"time"
"github.com/rjNemo/go-micro/products/models"
)
// dummy persistence layer // dummy persistence layer
var productList = []*Product{ var productList = []*models.Product{
{ {
ID: 1, ID: 1,
Name: "Latte", Name: "Latte",

View file

@ -4,28 +4,18 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "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. // Products is the collection of products.
// It encapsulates data access logic // 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 // AllProducts returns all existing products
func AllProducts() Products { func AllProducts() Products {
@ -33,13 +23,13 @@ func AllProducts() Products {
} }
// AddProduct add a Product to the dataStore // AddProduct add a Product to the dataStore
func AddProduct(p *Product) { func AddProduct(p *models.Product) {
p.ID = getNextID() p.ID = getNextID()
productList = append(productList, p) productList = append(productList, p)
} }
// UpdateProduct edits a Product identified by its id // 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) idx, _, err := findProduct(id)
if err != nil { if err != nil {
return err return err
@ -53,7 +43,7 @@ func UpdateProduct(id int, p *Product) error {
var ErrorProductNotFound = fmt.Errorf("Product not found") var ErrorProductNotFound = fmt.Errorf("Product not found")
// findProduct retrieves a product via its unique identifier // 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 { for i, p := range productList {
if p.ID == id { if p.ID == id {
return i, p, nil return i, p, nil
@ -66,8 +56,3 @@ func findProduct(id int) (int, *Product, error) {
func getNextID() int { func getNextID() int {
return productList[len(productList)-1].ID + 1 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
}

View file

@ -9,6 +9,7 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/rjNemo/go-micro/products/data" "github.com/rjNemo/go-micro/products/data"
"github.com/rjNemo/go-micro/products/models"
) )
// Products is a handler for Products API service // Products is a handler for Products API service
@ -16,8 +17,10 @@ type Products struct {
logger *log.Logger logger *log.Logger
} }
// NewProducts creates a Products handler // New creates a Products handler
func NewProducts(logger *log.Logger) *Products { return &Products{logger: logger} } func New(logger *log.Logger) *Products {
return &Products{logger: logger}
}
// GetProducts writes all products to response in JSON format // GetProducts writes all products to response in JSON format
func (p *Products) GetProducts(w http.ResponseWriter, r *http.Request) { 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) { func (p *Products) AddProduct(w http.ResponseWriter, r *http.Request) {
p.logger.Println("Handle 'POST' request") p.logger.Println("Handle 'POST' request")
// get product from the 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) p.logger.Printf("product: %#v", newProd)
data.AddProduct(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) p.logger.Println("Handle 'PUT' request", id)
// get product from the 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) p.logger.Printf("product: %#v", newProd)
err := data.UpdateProduct(id, newProd) err := data.UpdateProduct(id, newProd)
@ -72,7 +75,7 @@ type KeyProduct struct{}
func (p *Products) ProductValidationMiddleware(next http.Handler) http.Handler { func (p *Products) ProductValidationMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// create a new product // create a new product
newProd := &data.Product{} newProd := &models.Product{}
// deserialize JSON to product // deserialize JSON to product
err := newProd.FromJSON(r.Body) err := newProd.FromJSON(r.Body)
if err != nil { if err != nil {
@ -88,3 +91,18 @@ func (p *Products) ProductValidationMiddleware(next http.Handler) http.Handler {
next.ServeHTTP(w, req) 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)
}

View file

@ -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)
}