Skip to content

Commit

Permalink
Merge pull request #1 from shortbin/redis
Browse files Browse the repository at this point in the history
[feat] add redis
  • Loading branch information
m4tu4g authored Sep 29, 2024
2 parents bda80cb + 848167b commit 4c559d5
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 25 deletions.
9 changes: 8 additions & 1 deletion cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"shortbin/pkg/database"
"shortbin/pkg/kafka"
"shortbin/pkg/logger"
"shortbin/pkg/redis"
"shortbin/pkg/validation"
)

Expand All @@ -23,9 +24,15 @@ func main() {
Topic: cfg.Kafka.Topic,
})

cache := redis.New(redis.Config{
Address: cfg.Redis.Address,
Password: cfg.Redis.Password,
Database: cfg.Redis.Database,
})

validator := validation.New()

httpSvr := httpServer.NewServer(validator, db, kp)
httpSvr := httpServer.NewServer(validator, db, kp, cache)
if err = httpSvr.Run(); err != nil {
logger.Fatal(err)
}
Expand Down
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ require (
github.com/gin-gonic/gin v1.10.0
github.com/go-playground/locales v0.14.1
github.com/go-playground/universal-translator v0.18.1
github.com/go-playground/validator/v10 v10.22.0
github.com/go-playground/validator/v10 v10.22.1
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/uuid v1.6.0
github.com/jackc/pgx/v5 v5.7.1
github.com/redis/go-redis/v9 v9.6.1
github.com/segmentio/kafka-go v0.4.47
github.com/spf13/viper v1.19.0
go.elastic.co/apm/module/apmgin/v2 v2.6.2
Expand All @@ -24,8 +24,10 @@ require (
github.com/armon/go-radix v1.0.0 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/elastic/go-sysinfo v1.14.1 // indirect
github.com/elastic/go-windows v1.0.2 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
Expand Down
16 changes: 12 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
Expand All @@ -12,6 +18,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/elastic/go-sysinfo v1.14.1 h1:BpY/Utfz75oKSpsQnbAJmmlnT3gBV9WFsopBEYgjhZY=
github.com/elastic/go-sysinfo v1.14.1/go.mod h1:FKUXnZWhnYI0ueO7jhsGV3uQJ5hiz8OqM5b3oGyaRr8=
github.com/elastic/go-windows v1.0.2 h1:yoLLsAsV5cfg9FLhZ9EXZ2n2sQFKeDYrHenkcivY4vI=
Expand All @@ -34,17 +42,15 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
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/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
Expand Down Expand Up @@ -93,6 +99,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
Expand Down
49 changes: 36 additions & 13 deletions internal/retrieve/http/handlers.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
package http

import (
"go.elastic.co/apm/module/apmzap/v2"
"errors"
"net/http"
"time"

"github.com/gin-gonic/gin"
"go.elastic.co/apm/module/apmzap/v2"

"shortbin/internal/retrieve/service"
"shortbin/pkg/config"
"shortbin/pkg/kafka"
"shortbin/pkg/logger"
"shortbin/pkg/redis"
"shortbin/pkg/response"
)

type RetrieveHandler struct {
service service.IRetrieveService
kafkaProducer kafka.IKafkaProducer
redis redis.IRedis
}

func NewRetrieveHandler(service service.IRetrieveService, kafkaProducer kafka.IKafkaProducer) *RetrieveHandler {
func NewRetrieveHandler(service service.IRetrieveService, kafkaProducer kafka.IKafkaProducer, redis redis.IRedis) *RetrieveHandler {
return &RetrieveHandler{
service: service,
kafkaProducer: kafkaProducer,
redis: redis,
}
}

Expand All @@ -31,23 +37,31 @@ func NewRetrieveHandler(service service.IRetrieveService, kafkaProducer kafka.IK
// @Produce json
// @Param short_id path string true "Short ID"
// @Success 301 {string} string "Redirects to the long URL"
// @Failure 404 {object} response.ErrorResponse "Not Found"
// @Failure 404 {object} response.ErrorResponse "id not Found"
// @Router /{short_id} [get]
func (h *RetrieveHandler) Retrieve(c *gin.Context) {
shortID := c.Param("short_id")
traceContextFields := apmzap.TraceContext(c.Request.Context())

longURL, err := h.service.Retrieve(c, shortID)
if err != nil {
if e := err.Error(); e == response.IDNotFound || e == response.IDLengthNotInRange {
response.Error(c, http.StatusNotFound, err, response.IDNotFound)
} else {
logger.ApmLogger.With(traceContextFields...).Error(err.Error())
response.Error(c, http.StatusInternalServerError, err, response.SomethingWentWrong)
}
return
var longURL string // empty ""
if err := h.redis.Get(shortID, &longURL); !errors.Is(err, redis.NilReturn) && err != nil {
logger.ApmLogger.With(traceContextFields...).Error(err.Error())
}

if longURL == "" {
var err error
longURL, err = h.service.Retrieve(c, shortID)
if err != nil {
if e := err.Error(); e == response.IDNotFound || e == response.IDLengthNotInRange {
response.Error(c, http.StatusNotFound, err, response.IDNotFound)
} else {
logger.ApmLogger.With(traceContextFields...).Error(err.Error())
response.Error(c, http.StatusInternalServerError, err, response.SomethingWentWrong)
}
return
}
go cache(h, c, shortID, longURL)
}
go produce(h, c, shortID)
c.Redirect(http.StatusMovedPermanently, longURL)
}
Expand All @@ -67,6 +81,15 @@ func produce(h *RetrieveHandler, c *gin.Context, shortID string) {
if err != nil {
logger.Infof("failed to produce message to Kafka: %v", err)
logger.ApmLogger.With(traceContextFields...).Error(err.Error())
return
}
return

Check failure on line 85 in internal/retrieve/http/handlers.go

View workflow job for this annotation

GitHub Actions / lint

S1023: redundant `return` statement (gosimple)
}

func cache(h *RetrieveHandler, c *gin.Context, shortID, longURL string) {
traceContextFields := apmzap.TraceContext(c.Request.Context())
if err := h.redis.Set(shortID, longURL, config.GetConfig().Redis.TTL*time.Minute); err != nil {
logger.Infof("failed to set cache: %v", err)
logger.ApmLogger.With(traceContextFields...).Error(err.Error())
}
return

Check failure on line 94 in internal/retrieve/http/handlers.go

View workflow job for this annotation

GitHub Actions / lint

S1023: redundant `return` statement (gosimple)
}
5 changes: 3 additions & 2 deletions internal/retrieve/http/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import (
"shortbin/internal/retrieve/repository"
"shortbin/internal/retrieve/service"
"shortbin/pkg/kafka"
"shortbin/pkg/redis"
)

func Routes(e *gin.Engine, dbPool *pgxpool.Pool, kafkaProducer kafka.IKafkaProducer) {
func Routes(e *gin.Engine, dbPool *pgxpool.Pool, kafkaProducer kafka.IKafkaProducer, cache redis.IRedis) {
retrieveRepo := repository.NewRetrieveRepository(dbPool)
retrieveSvc := service.NewRetrieveService(retrieveRepo)
retrieveHandler := NewRetrieveHandler(retrieveSvc, kafkaProducer)
retrieveHandler := NewRetrieveHandler(retrieveSvc, kafkaProducer, cache)

e.GET("/:short_id", retrieveHandler.Retrieve)
}
12 changes: 10 additions & 2 deletions internal/server/http/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"shortbin/pkg/config"
"shortbin/pkg/kafka"
"shortbin/pkg/logger"
"shortbin/pkg/redis"
"shortbin/pkg/validation"
)

Expand All @@ -25,15 +26,22 @@ type Server struct {
validator validation.Validation
db *pgxpool.Pool
kp kafka.IKafkaProducer
cache redis.IRedis
}

func NewServer(validator validation.Validation, db *pgxpool.Pool, kp kafka.IKafkaProducer) *Server {
func NewServer(
validator validation.Validation,
db *pgxpool.Pool,
kp kafka.IKafkaProducer,
cache redis.IRedis,
) *Server {
return &Server{
engine: gin.Default(),
cfg: config.GetConfig(),
validator: validator,
db: db,
kp: kp,
cache: cache,
}
}

Expand Down Expand Up @@ -73,7 +81,7 @@ func (s Server) GetEngine() *gin.Engine {
func (s Server) MapRoutes() error {
v1 := s.engine.Group("/api/v1")

retrieveHttp.Routes(s.engine, s.db, s.kp)
retrieveHttp.Routes(s.engine, s.db, s.kp, s.cache)
authHttp.Routes(v1, s.db, s.validator)
createHttp.Routes(v1, s.db, s.validator)

Expand Down
12 changes: 11 additions & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package config

import (
"github.com/spf13/viper"
"log"
"time"

"github.com/spf13/viper"
)

const ProductionEnv = "production"
Expand All @@ -15,6 +17,7 @@ type Config struct {
ShortIDLength ShortIDLimit `mapstructure:"short_id_length"`
ExpirationInYears int `mapstructure:"expiration_in_years"`
Kafka Kafka `mapstructure:"kafka"`
Redis Redis `mapstructure:"redis"`
EnablePprof bool `mapstructure:"enable_pprof"`
}

Expand All @@ -29,6 +32,13 @@ type Kafka struct {
Topic string `mapstructure:"topic"`
}

type Redis struct {
Address string `mapstructure:"address"`
Password string `mapstructure:"password"`
Database int `mapstructure:"database"`
TTL time.Duration `mapstructure:"ttl"`
}

var cfg Config

func LoadConfig(configPath string) *Config {
Expand Down
87 changes: 87 additions & 0 deletions pkg/redis/redis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package redis

import (
"context"
"encoding/json"
"time"

goredis "github.com/redis/go-redis/v9"

"shortbin/pkg/logger"
)

const (
ContextTimeout = 1
InitContextTimeout = 5
)

// IRedis interface
type IRedis interface {
Get(key string, value interface{}) error
Set(key string, value interface{}, expiryTime time.Duration) error
}

// Config redis
type Config struct {
Address string
Password string
Database int
}

const NilReturn = goredis.Nil

type redis struct {
cmd goredis.Cmdable
}

// New Redis interface with config
func New(config Config) IRedis {
ctx, cancel := context.WithTimeout(context.Background(), InitContextTimeout*time.Second)
defer cancel()

redisClient := goredis.NewClient(&goredis.Options{
Addr: config.Address,
Password: config.Password,
DB: config.Database,
})

pong, err := redisClient.Ping(ctx).Result()
if err != nil {
logger.Fatal(pong, err)
return nil
}

return &redis{
cmd: redisClient,
}
}

func (r *redis) Get(key string, value interface{}) error {
ctx, cancel := context.WithTimeout(context.Background(), ContextTimeout*time.Second)
defer cancel()

strValue, err := r.cmd.Get(ctx, key).Result()
if err != nil {
return err
}

err = json.Unmarshal([]byte(strValue), value)
if err != nil {
return err
}

return nil
}

func (r *redis) Set(key string, value interface{}, expiryTime time.Duration) error {
ctx, cancel := context.WithTimeout(context.Background(), ContextTimeout*time.Second)
defer cancel()

bData, _ := json.Marshal(value)
err := r.cmd.Set(ctx, key, bData, expiryTime).Err()
if err != nil {
return err
}

return nil
}

0 comments on commit 4c559d5

Please sign in to comment.