🧹 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 (
"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)
}

View file

@ -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",

View file

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

View file

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

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