Skip to content

Commit 393b29c

Browse files
Add Golang version + minor fixes to Java version
1 parent 5a9f952 commit 393b29c

27 files changed

+290
-137
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@ target
22
.idea
33
*.iml
44
main
5+
~$*
6+
.classpath
7+
.project
8+
.settings

README.md

+15-2
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,16 @@ minikube start
2424

2525
Install MySQL(with Helm):
2626
```
27-
helm install stable/mysql
27+
helm install --set mysqlRootPassword=password --name mysql stable/mysql
28+
kubectl get service mysql-mysql -o yaml | sed 's/type: ClusterIP/type: NodePort/g' | kubectl replace -f -
2829
```
2930

3031
Create db:
3132
```
32-
mysql/mysql.sh create
33+
cd mysql && ./mysql.sh create
3334
```
3435

36+
#### Users Service - Java
3537
Run Users Service in Kubernetes:
3638
```
3739
kubectl apply -f kube/java/01_users_srv.yaml
@@ -42,6 +44,17 @@ Run Users Deployment in Kubernetes:
4244
kubectl apply -f kube/java/02_deployment.yaml
4345
```
4446

47+
#### Users Service - Golang
48+
Run Users Service in Kubernetes:
49+
```
50+
kubectl apply -f kube/golang/01_users_srv.yaml
51+
```
52+
53+
Run Users Deployment in Kubernetes:
54+
```
55+
kubectl apply -f kube/golang/02_deployment.yaml
56+
```
57+
4558
Verification:
4659

4760
Open:

golang/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ ADD $TYPE/main /usr/share/zero/main
88

99
EXPOSE 8080
1010

11-
ENTRYPOINT /usr/share/zero/main
11+
ENTRYPOINT [ "/usr/share/zero/main", "-port=8080", "-db-user=root", "-db-pass=password", "-db-host=mysql-mysql:3306", "-db-name=users" ]

golang/all.sh

+4-2
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,7 @@
22

33
./dev.sh build simple v1
44
./dev.sh docker simple v1
5-
./dev.sh build simple v2
6-
./dev.sh docker simple v2
5+
./dev.sh build graceful v2
6+
./dev.sh docker graceful v2
7+
./dev.sh build simple v3
8+
./dev.sh docker simple v3

golang/dev.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ case "$CMD" in
3434
cd $TYPE; CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$LDFLAGS" -o main -a -tags netgo .
3535
;;
3636
docker)
37-
docker build --build-arg TYPE=$TYPE -t "mateuszdyminski/zero-$TYPE:$VERSION" . && docker push "mateuszdyminski/zero-$TYPE:$VERSION"
37+
docker build --build-arg TYPE=$TYPE -t "mateuszdyminski/zero-golang:$VERSION" . && docker push "mateuszdyminski/zero-golang:$VERSION"
3838
;;
3939
*)
4040
usage

golang/graceful/main.go

+31-14
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import (
77
"net/http"
88
"os"
99
"os/signal"
10+
"syscall"
1011
"time"
1112

13+
_ "github.com/go-sql-driver/mysql"
1214
"github.com/gorilla/mux"
1315
"github.com/mateuszdyminski/zero/golang"
1416
"github.com/prometheus/client_golang/prometheus"
@@ -17,6 +19,10 @@ import (
1719

1820
// Flags
1921
var port = flag.String("port", "8080", "HTTP port number")
22+
var dbHost = flag.String("db-host", "mysql-mysql:3306", "Mysql database host with port")
23+
var dbUser = flag.String("db-user", "root", "Mysql database user")
24+
var dbPass = flag.String("db-pass", "password", "Mysql database password")
25+
var dbName = flag.String("db-name", "users", "Mysql database name")
2026

2127
// Variables injected by -X flag
2228
var appVersion = "unknown"
@@ -27,35 +33,30 @@ var lastCommitUser = "unknown"
2733
var buildTime = "unknown"
2834

2935
// Timeout is the duration to allow outstanding requests to survive before forcefully terminating them.
30-
const Timeout = 10
36+
const Timeout = 20
3137

3238
func main() {
3339
flag.Parse()
3440

35-
// subscribe to SIGINT signals
36-
quit := make(chan os.Signal)
37-
signal.Notify(quit, os.Interrupt, os.Kill)
38-
39-
// configure http handlers
40-
flag.Parse()
41-
4241
router := mux.NewRouter()
4342

44-
rest, err := golang.NewUserRest(buildInfo())
43+
rest, err := golang.NewUserRest(buildInfo(), dbInfo())
4544
if err != nil {
4645
log.Fatal(err)
4746
}
4847

4948
router.HandleFunc("/api/users", rest.Users).Methods("GET")
5049
router.HandleFunc("/api/users", rest.AddUser).Methods("POST")
5150
router.HandleFunc("/api/users/{id}", rest.GetUser).Methods("GET")
52-
router.HandleFunc("/api/health", rest.Health).Methods("GET")
51+
router.HandleFunc("/health", rest.Health).Methods("GET")
5352
router.HandleFunc("/api/error", rest.Err).Methods("POST")
5453
router.Handle("/metrics", promhttp.HandlerFor(
5554
prometheus.DefaultGatherer,
5655
promhttp.HandlerOpts{},
5756
))
5857

58+
log.Println("starting http server with graceful shutdown mode!")
59+
5960
// create and start http server in new goroutine
6061
srv := &http.Server{Addr: ":" + *port, Handler: golang.NewLogginHandler(router)}
6162
go func() {
@@ -65,18 +66,25 @@ func main() {
6566
}
6667
}()
6768

68-
// blocks the execution until os.Interrupt or os.Kill signal appears
69-
<-quit
69+
// subscribe to SIGTERM signals
70+
quit := make(chan os.Signal)
71+
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
7072

73+
// blocks the execution until os.Interrupt or syscall.SIGTERM signal appears
74+
<-quit
7175
log.Println("shutting down server. waiting to drain the ongoing requests...")
76+
rest.Unhealthy()
77+
78+
// add extra time to prevent new requests be routed to our service
79+
time.Sleep(5 * time.Second)
7280

7381
// shut down gracefully, but wait no longer than the Timeout value.
7482
ctx, cancelF := context.WithTimeout(context.Background(), Timeout*time.Second)
7583
defer cancelF()
7684

7785
// shutdown the http server
7886
if err := srv.Shutdown(ctx); err != nil {
79-
log.Fatalf("error while shutdown http server: %v\n", err)
87+
log.Printf("error while shutdown http server: %v\n", err)
8088
}
8189

8290
log.Println("server gracefully stopped")
@@ -89,8 +97,17 @@ func buildInfo() golang.BuildInfo {
8997
BuildTime: buildTime,
9098
LastCommit: golang.Commit{
9199
Author: lastCommitUser,
92-
Id: lastCommitHash,
100+
ID: lastCommitHash,
93101
Time: lastCommitTime,
94102
},
95103
}
96104
}
105+
106+
func dbInfo() golang.DBInfo {
107+
return golang.DBInfo{
108+
Name: *dbName,
109+
Host: *dbHost,
110+
User: *dbUser,
111+
Password: *dbPass,
112+
}
113+
}

golang/http.go

+15-9
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/sirupsen/logrus"
1212
)
1313

14+
// WriteJSON writes JSON response struct to ResponseWriter.
1415
func WriteJSON(w http.ResponseWriter, response interface{}) error {
1516
w.Header().Set("Content-Type", "application/json; charset=utf-8")
1617
json, err := json.Marshal(response)
@@ -25,6 +26,7 @@ func WriteJSON(w http.ResponseWriter, response interface{}) error {
2526
return nil
2627
}
2728

29+
// WriteErr writes error to ResponseWriter.
2830
func WriteErr(w http.ResponseWriter, err error, httpCode int) {
2931
logrus.Error(err.Error())
3032

@@ -36,10 +38,14 @@ func WriteErr(w http.ResponseWriter, err error, httpCode int) {
3638
"error": err.Error(),
3739
}
3840

39-
errJson, _ := json.Marshal(errMap)
40-
logrus.Error(string(errJson))
41+
errJSON, err := json.Marshal(errMap)
42+
if err != nil {
43+
logrus.Errorf("can't marshal error response. err: %s", err)
44+
}
45+
46+
logrus.Error(string(errJSON))
4147
w.WriteHeader(httpCode)
42-
w.Write(errJson)
48+
w.Write(errJSON)
4349
}
4450

4551
// callStats holds various stats associated with HTTP request-response pair.
@@ -156,13 +162,13 @@ func getRemoteAddr(r *http.Request) string {
156162
}
157163

158164
// Describe implements prometheus.Collector interface.
159-
func (d LoggingHandler) Describe(in chan<- *prometheus.Desc) {
160-
d.duration.Describe(in)
161-
d.requests.Describe(in)
165+
func (h LoggingHandler) Describe(in chan<- *prometheus.Desc) {
166+
h.duration.Describe(in)
167+
h.requests.Describe(in)
162168
}
163169

164170
// Collect implements prometheus.Collector interface.
165-
func (d LoggingHandler) Collect(in chan<- prometheus.Metric) {
166-
d.duration.Collect(in)
167-
d.requests.Collect(in)
171+
func (h LoggingHandler) Collect(in chan<- prometheus.Metric) {
172+
h.duration.Collect(in)
173+
h.requests.Collect(in)
168174
}

golang/rest.go

+52-11
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,30 @@ import (
44
"database/sql"
55
"encoding/json"
66
"errors"
7+
"fmt"
78
"io/ioutil"
89
"net/http"
910
"os"
1011
"sync"
1112
"time"
1213

13-
_ "github.com/go-sql-driver/mysql"
1414
"github.com/gorilla/mux"
1515
)
1616

17+
// UserRest is REST controller used to handler request about the user entities.
1718
type UserRest struct {
1819
wg sync.WaitGroup
1920
buildInfo BuildInfo
2021
hostname string
2122
startedAt time.Time
2223
db *sql.DB
24+
mu sync.Mutex
25+
healthy bool
2326
}
2427

25-
func NewUserRest(buildInfo BuildInfo) (*UserRest, error) {
26-
db, err := sql.Open("mysql", "root:7J52xZ0B9V@tcp(mysql-mysql:3306)/users?charset=utf8&parseTime=true")
28+
// NewUserRest constructs new UserRest controller used to handler request about the user entities.
29+
func NewUserRest(buildInfo BuildInfo, dbInfo DBInfo) (*UserRest, error) {
30+
db, err := sql.Open("mysql", dbInfo.ConnectionString())
2731
if err != nil {
2832
return nil, err
2933
}
@@ -38,9 +42,18 @@ func NewUserRest(buildInfo BuildInfo) (*UserRest, error) {
3842
buildInfo: buildInfo,
3943
hostname: host,
4044
startedAt: time.Now().UTC(),
45+
healthy: true,
4146
}, nil
4247
}
4348

49+
// Unhealthy sets server health to false - used in gracefull shutdown mode
50+
func (r *UserRest) Unhealthy() {
51+
r.mu.Lock()
52+
defer r.mu.Unlock()
53+
r.healthy = false
54+
}
55+
56+
// Users handler responds with list of all users in system.
4457
func (r *UserRest) Users(w http.ResponseWriter, req *http.Request) {
4558
rows, err := r.db.Query("SELECT * FROM users")
4659
if err != nil {
@@ -71,6 +84,7 @@ func (r *UserRest) Users(w http.ResponseWriter, req *http.Request) {
7184
WriteJSON(w, users)
7285
}
7386

87+
// GetUser handler responds with particular user based on the id of the user.
7488
func (r *UserRest) GetUser(w http.ResponseWriter, req *http.Request) {
7589
id := mux.Vars(req)["id"]
7690
if id == "" {
@@ -99,6 +113,7 @@ func (r *UserRest) GetUser(w http.ResponseWriter, req *http.Request) {
99113
WriteJSON(w, user)
100114
}
101115

116+
// AddUser handler is responsible for adding new user to system.
102117
func (r *UserRest) AddUser(w http.ResponseWriter, req *http.Request) {
103118
var user User
104119
err := json.NewDecoder(req.Body).Decode(&user)
@@ -114,17 +129,26 @@ func (r *UserRest) AddUser(w http.ResponseWriter, req *http.Request) {
114129
}
115130
}
116131

132+
// Health handler is responsible for serving resonse with current health status of service. Healthz concept is used to leverage the regular health status pattern.
117133
func (r *UserRest) Health(w http.ResponseWriter, req *http.Request) {
118-
resp := HealthStatus{
119-
Hostname: r.hostname,
120-
StartedAt: r.startedAt.Format("2006-01-02_15:04:05"),
121-
Uptime: time.Now().UTC().Sub(r.startedAt).String(),
122-
Build: r.buildInfo,
123-
}
134+
r.mu.Lock()
135+
defer r.mu.Unlock()
136+
137+
if r.healthy {
138+
resp := HealthStatus{
139+
Hostname: r.hostname,
140+
StartedAt: r.startedAt.Format("2006-01-02_15:04:05"),
141+
Uptime: time.Now().UTC().Sub(r.startedAt).String(),
142+
Build: r.buildInfo,
143+
}
124144

125-
WriteJSON(w, resp)
145+
WriteJSON(w, resp)
146+
} else {
147+
WriteErr(w, fmt.Errorf("Server in graceful shutdown mode"), http.StatusInternalServerError)
148+
}
126149
}
127150

151+
// Err handler is dummy simulator of error which occurs in out service.
128152
func (r *UserRest) Err(w http.ResponseWriter, req *http.Request) {
129153
bytes, err := ioutil.ReadAll(req.Body)
130154
if err != nil {
@@ -135,29 +159,46 @@ func (r *UserRest) Err(w http.ResponseWriter, req *http.Request) {
135159
WriteErr(w, errors.New(string(bytes)), http.StatusInternalServerError)
136160
}
137161

162+
// User holds basic info about the User entity.
138163
type User struct {
139164
ID int `json:"id"`
140165
FirstName string `json:"firstName"`
141166
SecondName string `json:"secondName"`
142167
BirthDate time.Time `json:"birthDate"`
143168
}
144169

170+
// HealthStatus holds basic info about the Health status of the application.
145171
type HealthStatus struct {
146172
Build BuildInfo `json:"buildInfo"`
147173
Hostname string `json:"hostname"`
148174
Uptime string `json:"uptime"`
149175
StartedAt string `json:"startedAt"`
150176
}
151177

178+
// BuildInfo holds basic info about the build based on the git statistics.
152179
type BuildInfo struct {
153180
Version string `json:"version"`
154181
GitVersion string `json:"gitVersion"`
155182
BuildTime string `json:"buildTime"`
156183
LastCommit Commit `json:"lastCommit"`
157184
}
158185

186+
// Commit holds info about the git commit.
159187
type Commit struct {
160-
Id string `json:"id"`
188+
ID string `json:"id"`
161189
Time string `json:"time"`
162190
Author string `json:"author"`
163191
}
192+
193+
// DBInfo holds configuration how to access to database.
194+
type DBInfo struct {
195+
User string `json:"user"`
196+
Password string `json:"password"`
197+
Host string `json:"host"`
198+
Name string `json:"name"`
199+
}
200+
201+
// ConnectionString returns connection string based on the DBInfo configuration.
202+
func (db *DBInfo) ConnectionString() string {
203+
return fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=true", db.User, db.Password, db.Host, db.Name)
204+
}

0 commit comments

Comments
 (0)