mirror of
https://github.com/rjNemo/go-microservices-tuto
synced 2026-06-12 13:26:45 +00:00
🧹 clean code structure
This commit is contained in:
parent
51520e2238
commit
7b50b02a04
5 changed files with 82 additions and 57 deletions
43
main.go
43
main.go
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
23
products/models/product.go
Normal file
23
products/models/product.go
Normal 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)
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue