Browse Source

refactoring

Refactored customercontroller to handler
added readme
added docker compose sample
Luis Figueiredo 8 years ago
parent
commit
d65f9c9702

+ 4 - 0
.gitignore

@@ -1 +1,5 @@
 dist.tar.gz
+deps
+bin
+pkg
+

+ 15 - 3
Makefile

@@ -3,17 +3,29 @@ DIST=dist.tar.gz
 
 all:  $(DIST)
 
+local:
+	GOPATH=$(CURDIR)/deps:$(CURDIR) go get sampleapp/cmd/server
+	GOPATH=$(CURDIR)/deps:$(CURDIR) go build -o bin/sampleapp  sampleapp/cmd/server
+
+#builder
 $(DIST):
-	docker build --rm -t sampleapp-build -f Dockerfile.build .
+	docker build --rm -t sampleapp-build -f docker/Dockerfile.build .
 	docker run sampleapp-build > $(DIST)
 
+# Distribution docker, small, clean
 dist: $(DIST)
-	docker build -t sampleapp -f Dockerfile.dist .
+	docker build -t sampleapp -f docker/Dockerfile.dist .
 
 clean:
 	rm $(DIST)
 
+distclean:
+	rm -rf bin
+	rm -rf pkg
+	rm -rf deps
+
+
 
-.PHONY: clean dist
+.PHONY: clean dist all
 
 

+ 57 - 0
README.md

@@ -0,0 +1,57 @@
+Sample app
+==================
+
+## Requirements
+* Golang  (tested on 1.8+)
+* Docker  (tested on 17.04.0-ce)
+* Mongodb (tested on 3.4.2+)
+
+## Building
+```
+git clone http://dev.hexasoftware.com/stdio/sampleapp.git
+cd sampleapp
+make
+```
+
+
+
+## Structure:
+customersvc logic is separated from the 'webapp' mostly because I tend to separate reusable logic into packages/modules/etc this way is possible to create a simple CLI app consuming the customersvc logic
+
+```
+..
+└── app  : GOPATH
+    ├── docker
+    │   ├── docker-compose.yml
+    │   ├── Dockerfile.build
+    │   └── Dockerfile.dist
+    ├── Makefile
+    ├── README.md
+    └── src
+        └── sampleapp
+            ├── cmd
+            │   └── server
+            │       └── main.go
+            ├── customerhandlers.go
+            ├── customersvc
+            │   ├── customer.go
+            │   ├── customersvc.go
+            │   └── customersvc_test.go
+            ├── routes.go
+            ├── sampleapp.go
+            ├── sampleapp_test.go
+            ├── testutils
+            │   └── testutils.go
+            └── utils.go
+```
+
+Depending on situation I often create go-getable packages    
+
+Sometimes a project is small enough to fit both backend/frontend in it
+```
+Project 
+   + Backend  : GOPATH
+      +src
+   + Frontend
+      + Web
+```

+ 1 - 1
Dockerfile.build

@@ -2,7 +2,7 @@ FROM golang:1.8
 
 ADD src/sampleapp $GOPATH/src/sampleapp
 WORKDIR $GOPATH
-RUN go get -v sampleapp/cmd/server
+RUN go get sampleapp/cmd/server
 
 RUN go test sampleapp/customersvc
 RUN go test sampleapp

+ 1 - 1
Dockerfile.dist

@@ -2,4 +2,4 @@ FROM scratch
 ADD dist.tar.gz /app/
 WORKDIR /app
 EXPOSE 8080
-ENTRYPOINT ["./sampleapp"]
+ENTRYPOINT ["./bin/sampleapp"]

+ 19 - 0
docker/docker-compose.yml

@@ -0,0 +1,19 @@
+version: '2'
+services:
+  myapp:
+    image: "sampleapp"
+    container_name: sampleapp
+    # image: f080690e516f
+    restart: always
+    depends_on: [ mongodb ]
+    environment:
+      - DBHOST=mongodb
+    ports:
+      - 8080:8080
+
+  mongodb:
+    image: "mongo"
+    container_name: sampleapp-mongodb
+    restart: always
+    #volumes:
+    #  - "./data/db:/data/db"

+ 13 - 4
src/sampleapp/cmd/server/main.go

@@ -1,6 +1,8 @@
 package main
 
 import (
+	"fmt"
+	"os"
 	"sampleapp"
 	"sampleapp/customersvc"
 
@@ -8,8 +10,12 @@ import (
 )
 
 func main() {
-
-	sess, err := mgo.Dial("localhost")
+	// Load session
+	dbUrl := os.Getenv("DBHOST")
+	if dbUrl == "" {
+		dbUrl = "localhost" // default
+	}
+	sess, err := mgo.Dial(dbUrl)
 	if err != nil {
 		panic(err)
 	}
@@ -18,8 +24,11 @@ func main() {
 	s := sampleapp.New()
 	// This kind of injection is not a good practice
 	s.CustomerSVC = &customersvc.Service{db}
-
+	port := "8080"
+	if nport := os.Getenv("PORT"); nport != "" {
+		port = nport
+	}
 	// Initialize things like DB
 
-	s.ListenAndServe(":8080")
+	s.ListenAndServe(fmt.Sprintf(":%s", port))
 }

+ 0 - 51
src/sampleapp/customerctrl.go

@@ -1,51 +0,0 @@
-/*
- * This could be in another package, controllers, but golang tend to be simgle folder per concern
- */
-
-package sampleapp
-
-import (
-	"encoding/json"
-	"log"
-	"net/http"
-	"sampleapp/customersvc"
-
-	"gopkg.in/mgo.v2-unstable/bson"
-
-	"github.com/gorilla/mux"
-)
-
-type CustomerController struct {
-	svc customersvc.Servicer
-}
-
-func (cc *CustomerController) GetCustomerHandler(w http.ResponseWriter, r *http.Request) {
-	vars := mux.Vars(r)
-	id := vars["id"]
-
-	if !bson.IsObjectIdHex(id) {
-		w.WriteHeader(http.StatusBadRequest)
-		return
-	}
-	cust, err := cc.svc.FetchByID(id) // This should be in userservice
-	if err == customersvc.ErrNotFound {
-		httpErr(w, http.StatusNotFound, "Not found")
-		return
-	}
-	if err != nil {
-		httpErr(w, http.StatusInternalServerError, "Error fetching customer", err)
-		return
-	}
-	json.NewEncoder(w).Encode(cust)
-}
-
-func (cc *CustomerController) PostCustomerHandler(w http.ResponseWriter, r *http.Request) {
-	log.Println("Post")
-	defer r.Body.Close()
-
-	cust := &customersvc.Customer{}
-	json.NewDecoder(r.Body).Decode(&cust)
-
-	log.Println("Received:", cust)
-	//Store user from body
-}

+ 66 - 0
src/sampleapp/customerhandlers.go

@@ -0,0 +1,66 @@
+/*
+ * This could be in another package, handlers, but golang tend to be simgle folder per concern
+ */
+
+package sampleapp
+
+import (
+	"encoding/json"
+	"log"
+	"net/http"
+	"sampleapp/customersvc"
+
+	"github.com/gorilla/mux"
+)
+
+//CustomerHandlers handlers related to customers
+type CustomerHandlers struct {
+	svc customersvc.Servicer
+}
+
+//GetCustomer gets a customer
+func (ch *CustomerHandlers) GetCustomer(w http.ResponseWriter, r *http.Request) {
+	vars := mux.Vars(r)
+
+	id := vars["id"]
+
+	cust, err := ch.svc.FetchByID(id) // This should be in userservice
+	if err == customersvc.ErrNotFound {
+		httpErr(w, http.StatusNotFound, "Not found")
+		return
+	}
+	if err != nil {
+		httpErr(w, http.StatusInternalServerError, "Error fetching customer", err)
+		return
+	}
+	json.NewEncoder(w).Encode(cust)
+}
+
+//PostCustomer posts a customer from application/json content as body
+func (ch *CustomerHandlers) PostCustomer(w http.ResponseWriter, r *http.Request) {
+	//TODO: check things like json validation, etc
+	log.Println("Post")
+	defer r.Body.Close()
+
+	cust := &customersvc.Customer{}
+	json.NewDecoder(r.Body).Decode(&cust)
+
+	if cust.Name == "" {
+		httpErr(w, http.StatusBadRequest, "Missing name field")
+		return
+	}
+	if len(cust.Phone) == 0 {
+		httpErr(w, http.StatusBadRequest, "Missing a phone number")
+		return
+	}
+
+	err := ch.svc.Store(cust)
+	if err != nil {
+		httpErr(w, http.StatusInternalServerError, "Error saving customer", err)
+		return
+	}
+	json.NewEncoder(w).Encode(cust)
+
+	log.Println("Received:", cust)
+	//Store user from body
+}

+ 1 - 1
src/sampleapp/customersvc/customer.go

@@ -9,7 +9,7 @@ const (
 type Customer struct {
 	ID    string `bson:"_id,omitempty"` // Could be anything else like UUID, or simple int
 	Name  string
-	Phone Phone
+	Phone []Phone
 }
 
 // Phone data structure

+ 0 - 7
src/sampleapp/customersvc/customersvc.go

@@ -27,17 +27,10 @@ type Service struct {
 	DS *mgo.Database
 }
 
-// New or Init?
-
-func (s *Service) Init() {
-
-}
-
 // Store customer
 func (s *Service) Store(c *Customer) error {
 
 	c.ID = uuid.New().String() // could collide
-
 	return s.coll().Insert(c)
 }
 

+ 8 - 3
src/sampleapp/customersvc/customersvc_test.go

@@ -24,6 +24,9 @@ func MustPrepareDB() *mgo.Database {
 	return db
 }
 
+// Will not go much further on this testing since requires DB and the point is clear
+// my github.com/gohxs/hqi would serve this purpose to be able to mock a query
+
 func TestStore(t *testing.T) {
 	db := MustPrepareDB()
 	if db == nil {
@@ -34,9 +37,11 @@ func TestStore(t *testing.T) {
 
 	c := customersvc.Customer{
 		Name: "Luis",
-		Phone: customersvc.Phone{
-			Type:   customersvc.TypMobile,
-			Number: "+5545991121214",
+		Phone: []customersvc.Phone{
+			customersvc.Phone{
+				Type:   customersvc.TypMobile,
+				Number: "+5545991121214",
+			},
 		},
 	}
 	err := cs.Store(&c)

+ 3 - 3
src/sampleapp/routes.go

@@ -3,9 +3,9 @@ package sampleapp
 func (s *SampleApp) InitRoutes() {
 
 	// Customer routes
-	cc := &CustomerController{s.CustomerSVC}
-	s.router.Methods("GET").Path("/customer/{id}").HandlerFunc(LogHandler(cc.GetCustomerHandler))
-	s.router.Methods("POST").Path("/customer").HandlerFunc(LogHandler(cc.PostCustomerHandler))
+	cc := &CustomerHandlers{s.CustomerSVC}
+	s.router.Methods("GET").Path("/customer/{id}").HandlerFunc(LogHandler(cc.GetCustomer))
+	s.router.Methods("POST").Path("/customer").HandlerFunc(LogHandler(cc.PostCustomer))
 
 	// Prepare handlers and middleware
 	s.router.HandleFunc("/", LogHandler(nil))

+ 1 - 6
src/sampleapp/sampleapp.go

@@ -10,14 +10,9 @@ import (
 
 // SampleApp
 type SampleApp struct {
-	// Datastore for this app
-	//sess *mgo.Session
-	//db   *mgo.Database
-
 	// Services
 	CustomerSVC customersvc.Servicer
-
-	// golang http router
+	//...
 	router *mux.Router
 }
 

+ 36 - 5
src/sampleapp/sampleapp_test.go

@@ -15,9 +15,11 @@ var (
 	sampleUser = customersvc.Customer{ // omit id
 		ID:   "58ff6b3f673686f96801707b",
 		Name: "Luis",
-		Phone: customersvc.Phone{
-			Type:   customersvc.TypMobile,
-			Number: "+5545991121214",
+		Phone: []customersvc.Phone{
+			customersvc.Phone{
+				Type:   customersvc.TypMobile,
+				Number: "+5545991121214",
+			},
 		},
 	}
 )
@@ -31,7 +33,7 @@ func TestGetCustomerBadID(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	FailNotEq(t, res.StatusCode, 400)
+	FailNotEq(t, res.StatusCode, 404)
 
 }
 func TestGetCustomer(t *testing.T) {
@@ -50,7 +52,6 @@ func TestGetCustomer(t *testing.T) {
 	json.NewDecoder(res.Body).Decode(&c)
 	FailNotEq(t, c.Name, "Luis")
 }
-
 func TestPostCustomer(t *testing.T) {
 	s := sampleapp.SampleApp{CustomerSVC: &CustomerSVCMock{}}
 	ts := httptest.NewServer(s.Router())
@@ -65,6 +66,36 @@ func TestPostCustomer(t *testing.T) {
 
 	FailNotEq(t, res.StatusCode, 200)
 
+}
+func TestPostCustomerNoName(t *testing.T) {
+	s := sampleapp.SampleApp{CustomerSVC: &CustomerSVCMock{}}
+	ts := httptest.NewServer(s.Router())
+
+	var bufData bytes.Buffer
+
+	newCustomer := sampleUser // Copy
+	newCustomer.Name = ""
+
+	json.NewEncoder(&bufData).Encode(newCustomer)
+	res, err := http.Post(ts.URL+"/customer", "application/json", &bufData)
+	FailNotEq(t, err, nil)
+	FailNotEq(t, res.StatusCode, 400)
+
+}
+func TestPostCustomerNoPhone(t *testing.T) {
+	s := sampleapp.SampleApp{CustomerSVC: &CustomerSVCMock{}}
+	ts := httptest.NewServer(s.Router())
+
+	var bufData bytes.Buffer
+
+	newCustomer := sampleUser // Copy
+	newCustomer.Phone = []customersvc.Phone{}
+
+	json.NewEncoder(&bufData).Encode(newCustomer)
+	res, err := http.Post(ts.URL+"/customer", "application/json", &bufData)
+	FailNotEq(t, err, nil)
+	FailNotEq(t, res.StatusCode, 400)
+
 }
 
 // CustomerSVCMock customer service mock