Skip to content

Commit

Permalink
feat: caching
Browse files Browse the repository at this point in the history
  • Loading branch information
daithihearn committed Jan 23, 2024
1 parent 989c966 commit 1ae5908
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 14 deletions.
23 changes: 21 additions & 2 deletions cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ package main
import (
_ "cards-110-api/docs"
"cards-110-api/pkg/auth"
"cards-110-api/pkg/cache"
"cards-110-api/pkg/db"
"cards-110-api/pkg/game"
"cards-110-api/pkg/profile"
"cards-110-api/pkg/settings"
"cards-110-api/pkg/stats"
"context"
"github.com/go-redis/redis/v8"
"log"
"os"
"os/signal"
Expand Down Expand Up @@ -66,6 +68,23 @@ func main() {
dbName = "cards-110"
}

// Configure redis
redisUrl := os.Getenv("REDIS_URL")
if redisUrl == "" {
redisUrl = "localhost:6379"
}
redisPassword := os.Getenv("REDIS_PASSWORD")
if redisPassword == "" {
redisPassword = "password"
}
rdb := redis.NewClient(&redis.Options{
Addr: redisUrl, // use your Redis Address
Password: redisPassword, // no password set
DB: 0, // use default DB
})
gameCache := cache.NewRedisCache[game.State](rdb, ctx)
statsCache := cache.NewRedisCache[[]stats.PlayerStats](rdb, ctx)

// Configure collections
userCol, err := db.GetCollection(ctx, dbName, "appUsers")
if err != nil {
Expand All @@ -91,9 +110,9 @@ func main() {
settingsService := settings.Service{Col: settingsColRec}
settingsHandler := settings.Handler{S: &settingsService}
gamesColRec := db.Collection[game.Game]{Col: gameCol}
gameService := game.Service{Col: &gamesColRec}
gameService := game.Service{Col: &gamesColRec, Cache: gameCache}
gameHandler := game.Handler{S: &gameService}
statsService := stats.Service{Col: &gamesColRec}
statsService := stats.Service{Col: &gamesColRec, Cache: statsCache}
statsHandler := stats.Handler{S: &statsService}

// Set up the API routes.
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ require (
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/bytedance/sonic v1.10.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
Expand All @@ -30,6 +32,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.15.5 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc=
github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
Expand All @@ -20,6 +22,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/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/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk=
Expand Down Expand Up @@ -48,6 +52,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.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24=
github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
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/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
Expand Down
48 changes: 48 additions & 0 deletions pkg/cache/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package cache

import (
"context"
"encoding/json"
"github.com/go-redis/redis/v8"
"time"
)

type RedisCache[T any] struct {
client *redis.Client
ctx context.Context
}

func NewRedisCache[T any](client *redis.Client, ctx context.Context) *RedisCache[T] {
return &RedisCache[T]{client: client, ctx: ctx}
}

func (c *RedisCache[T]) Set(key string, value T, expiration time.Duration) error {
jsonData, err := json.Marshal(value)
if err != nil {
return err
}

return c.client.Set(c.ctx, key, jsonData, expiration).Err()
}

func (c *RedisCache[T]) Get(key string) (T, bool, error) {
var obj T
val, err := c.client.Get(c.ctx, key).Result()
if err != nil {
return obj, false, err
}
if val == "" {
return obj, false, nil
}

err = json.Unmarshal([]byte(val), &obj)
if err != nil {
return obj, false, err
}

return obj, true, nil
}

func (c *RedisCache[T]) Delete(key string) error {
return c.client.Del(c.ctx, key).Err()
}
93 changes: 85 additions & 8 deletions pkg/game/game-service.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package game

import (
"cards-110-api/pkg/cache"
"cards-110-api/pkg/db"
"context"
"errors"
"go.mongodb.org/mongo-driver/bson"
"log"
"time"
)

type ServiceI interface {
Expand All @@ -21,7 +23,12 @@ type ServiceI interface {
}

type Service struct {
Col db.CollectionI[Game]
Col db.CollectionI[Game]
Cache *cache.RedisCache[State]
}

func getCacheKey(gameId string, playerId string) string {
return gameId + "-" + playerId
}

// Create a new game.
Expand Down Expand Up @@ -57,19 +64,31 @@ func (s *Service) Get(ctx context.Context, gameId string) (Game, bool, error) {
return s.Col.FindOne(ctx, bson.M{"_id": gameId})
}

func (s *Service) GetState(ctx context.Context, gameId string, playerId string) (State, bool, error) {
func (s *Service) GetState(ctx context.Context, gameId string, playerID string) (State, bool, error) {
// Check the cache.
state, found, err := s.Cache.Get(getCacheKey(gameId, playerID))
if err != nil && found {
return state, true, nil
}

// Get the game from the database.
game, has, err := s.Get(ctx, gameId)
if err != nil || !has {
return State{}, has, err
game, has, errG := s.Get(ctx, gameId)
if errG != nil || !has {
return State{}, has, errG
}

// Get the state for the player.
state, err := game.GetState(playerId)
state, err = game.GetState(playerID)
if err != nil {
return State{}, true, err
}

// Save the state to the cache.
err = s.Cache.Set(getCacheKey(gameId, playerID), state, 10*time.Minute)
if err != nil {
log.Printf("Failed to save state to cache: %s", err)
}

return state, true, nil
}

Expand Down Expand Up @@ -104,7 +123,7 @@ func (s *Service) Delete(ctx context.Context, gameId string, adminId string) err
}

// Call make a call
func (s *Service) Call(ctx context.Context, gameId string, playerId string, call Call) (Game, error) {
func (s *Service) Call(ctx context.Context, gameId string, playerID string, call Call) (Game, error) {
// Get the game from the database.
game, has, err := s.Get(ctx, gameId)
if err != nil {
Expand All @@ -115,7 +134,7 @@ func (s *Service) Call(ctx context.Context, gameId string, playerId string, call
}

// Make the call.
err = game.Call(playerId, call)
err = game.Call(playerID, call)
if err != nil {
return Game{}, err
}
Expand All @@ -125,6 +144,18 @@ func (s *Service) Call(ctx context.Context, gameId string, playerId string, call
if err != nil {
return Game{}, err
}

// Save the state to the cache.
state, err := game.GetState(playerID)
if err != nil {
return Game{}, err
}

err = s.Cache.Set(getCacheKey(gameId, playerID), state, 10*time.Minute)
if err != nil {
return Game{}, err
}

return game, nil
}

Expand All @@ -150,6 +181,18 @@ func (s *Service) SelectSuit(ctx context.Context, gameId string, playerID string
if err != nil {
return Game{}, err
}

// Save the state to the cache.
state, err := game.GetState(playerID)
if err != nil {
return Game{}, err
}

err = s.Cache.Set(getCacheKey(gameId, playerID), state, 10*time.Minute)
if err != nil {
return Game{}, err
}

return game, nil
}

Expand All @@ -175,6 +218,18 @@ func (s *Service) Buy(ctx context.Context, gameId string, playerID string, cards
if err != nil {
return Game{}, err
}

// Save the state to the cache.
state, err := game.GetState(playerID)
if err != nil {
return Game{}, err
}

err = s.Cache.Set(getCacheKey(gameId, playerID), state, 10*time.Minute)
if err != nil {
return Game{}, err
}

return game, nil
}

Expand All @@ -200,5 +255,27 @@ func (s *Service) Play(ctx context.Context, gameId string, playerID string, card
if err != nil {
return Game{}, err
}

// Save the state to the cache.
state, err := game.GetState(playerID)
if err != nil {
return Game{}, err
}

err = s.Cache.Set(getCacheKey(gameId, playerID), state, 10*time.Minute)
if err != nil {
return Game{}, err
}

// Invalidate the stats caches if the game is over
if game.Status == Completed {
for _, player := range game.Players {
err = s.Cache.Delete("stats-" + player.ID)
if err != nil {
return Game{}, err
}
}
}

return game, nil
}
25 changes: 21 additions & 4 deletions pkg/stats/stats-service.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package stats

import (
"cards-110-api/pkg/cache"
"cards-110-api/pkg/db"
"cards-110-api/pkg/game"
"context"
Expand All @@ -17,16 +18,26 @@ type ServiceI interface {
}

type Service struct {
Col db.CollectionI[game.Game]
Col db.CollectionI[game.Game]
Cache *cache.RedisCache[[]PlayerStats]
}

func getCacheKey(playerId string) string {
return "stats-" + playerId
}

// GetStats Get the stats for a player.
func (s *Service) GetStats(ctx context.Context, playerId string) ([]PlayerStats, error) {
func (s *Service) GetStats(ctx context.Context, playerID string) ([]PlayerStats, error) {
// Check the cache.
stats, found, err := s.Cache.Get(getCacheKey(playerID))
if err != nil && found {
return stats, nil
}

pipeline := mongo.Pipeline{
{{Key: "$match", Value: bson.D{{Key: "status", Value: "FINISHED"}, {Key: "players._id", Value: playerId}}}},
{{Key: "$match", Value: bson.D{{Key: "status", Value: "FINISHED"}, {Key: "players._id", Value: playerID}}}},
{{Key: "$unwind", Value: "$players"}},
{{Key: "$match", Value: bson.D{{Key: "players._id", Value: playerId}}}},
{{Key: "$match", Value: bson.D{{Key: "players._id", Value: playerID}}}},
{{Key: "$project", Value: bson.D{
{Key: "gameId", Value: "$_id"},
{Key: "timestamp", Value: "$timestamp"},
Expand Down Expand Up @@ -104,5 +115,11 @@ func (s *Service) GetStats(ctx context.Context, playerId string) ([]PlayerStats,

}

// Save the result to the cache.
err = s.Cache.Set(getCacheKey(playerID), results, 0)
if err != nil {
log.Printf("Failed to save state to cache: %s", err)
}

return results, nil
}

0 comments on commit 1ae5908

Please sign in to comment.