Skip to content

Commit

Permalink
TRQ-002: Integrate service with ZooKeeper (#4)
Browse files Browse the repository at this point in the history
* TRQ-002: Setup zookeeper via docker

* TRQ-002: Integrate zookeeper with service

* TRQ-002: Move service to docker

* TRQ-002: Add retry for connecting to zookeeper

* TRQ-002: (minor) remove comment
  • Loading branch information
godcrampy authored May 19, 2024
1 parent ddae38d commit 80065a2
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 9 deletions.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
PORT=8081
ENV=release
ZOOKEEPER_SERVERS=127.0.0.1:2181
15 changes: 15 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM golang:alpine

WORKDIR /app

COPY go.mod go.sum ./

RUN go mod download

COPY . .

RUN go build -o main ./cmd/main.go

ENV PORT=8080

CMD ["./main"]
21 changes: 12 additions & 9 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package main

import (
"log"
"net/http"
"os"

"github.com/gin-gonic/gin"
"github.com/godcrampy/torquay/pkg/counter"
"github.com/godcrampy/torquay/pkg/handlers"
"github.com/joho/godotenv"
)

Expand All @@ -18,17 +19,19 @@ func main() {
mode := os.Getenv("ENV")
gin.SetMode(mode)

r := gin.Default()
servers := []string{os.Getenv("ZOOKEEPER_SERVERS")}
zkPath := "/counter"

token := 1
c, err := counter.NewCounterWithRetry(servers, zkPath)
if err != nil {
log.Fatalf("Unable to connect to ZooKeeper: %v", err)
}
defer c.Close()

r.GET("/api/v1/token", func(ctx *gin.Context) {
ctx.JSON(http.StatusOK, gin.H{
"token": token,
})
h := handlers.NewHandler(c)

token += 1
})
r := gin.Default()
r.GET("/api/v1/token", h.GetToken)

port := os.Getenv("PORT")
log.Printf("INFO: Starting server on port %s\n", port)
Expand Down
31 changes: 31 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
version: '3.8'

services:
go-server-8080:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
- PORT=8080
- ZOOKEEPER_SERVERS=zookeeper:2181
depends_on:
- zookeeper
go-server-8081:
build:
context: .
dockerfile: Dockerfile
ports:
- "8081:8081"
environment:
- PORT=8081
- ZOOKEEPER_SERVERS=zookeeper:2181
depends_on:
- zookeeper
zookeeper:
image: 'bitnami/zookeeper:latest'
ports:
- '2181:2181'
environment:
- ALLOW_ANONYMOUS_LOGIN=yes
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/go-zookeeper/zk v1.0.3
github.com/goccy/go-json v0.10.2 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg=
github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
Expand Down
92 changes: 92 additions & 0 deletions pkg/counter/counter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package counter

import (
"fmt"
"log"
"strconv"
"time"

"github.com/go-zookeeper/zk"
)

type Counter struct {
zkConn *zk.Conn
zkPath string
}

func NewCounterWithRetry(servers []string, path string) (*Counter, error) {
const RetryCount = 5
const RetryDelayS = 5

var (
c *Counter
err error
)

for attempt := 1; attempt <= RetryCount; attempt++ {
c, err = NewCounter(servers, path)

if err != nil {
log.Printf("ERROR: Failed to connect to ZooKeeper (attempt %d/%d): %v", attempt, RetryCount, err)
time.Sleep(time.Second * RetryDelayS)
continue
}

return c, nil
}

return nil, err
}

func NewCounter(servers []string, path string) (*Counter, error) {
conn, _, err := zk.Connect(servers, time.Second*5)
if err != nil {
return nil, err
}

c := &Counter{
zkConn: conn,
zkPath: path,
}

if exists, _, err := conn.Exists(path); err != nil {
return nil, err
} else if !exists {
_, err := conn.Create(path, []byte("0"), 0, zk.WorldACL(zk.PermAll))
if err != nil {
return nil, err
}
}

return c, nil
}

func (c *Counter) GetAndIncrement() (int, error) {
for {
data, stat, err := c.zkConn.Get(c.zkPath)
if err != nil {
return 0, err
}

currentValue, err := strconv.Atoi(string(data))
if err != nil {
return 0, err
}

newValue := currentValue + 1
newData := []byte(fmt.Sprintf("%d", newValue))

_, err = c.zkConn.Set(c.zkPath, newData, stat.Version)
if err == zk.ErrBadVersion {
continue
} else if err != nil {
return 0, err
}

return newValue, nil
}
}

func (c *Counter) Close() {
c.zkConn.Close()
}
26 changes: 26 additions & 0 deletions pkg/handlers/token_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package handlers

import (
"net/http"

"github.com/gin-gonic/gin"
"github.com/godcrampy/torquay/pkg/counter"
)

type Handler struct {
Counter *counter.Counter
}

func NewHandler(c *counter.Counter) *Handler {
return &Handler{Counter: c}
}

func (h *Handler) GetToken(c *gin.Context) {
newValue, err := h.Counter.GetAndIncrement()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

c.JSON(http.StatusOK, gin.H{"token": newValue})
}

0 comments on commit 80065a2

Please sign in to comment.