Skip to content

Commit

Permalink
feat: cache get group by slug
Browse files Browse the repository at this point in the history
  • Loading branch information
FemiNoviaLina committed Jun 11, 2024
1 parent dd54d61 commit bf8c863
Show file tree
Hide file tree
Showing 13 changed files with 357 additions and 7 deletions.
11 changes: 10 additions & 1 deletion .mockery.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,13 @@ packages:
filename: "resource_service.go"
Repository:
config:
filename: "servicedata_repository.go"
filename: "servicedata_repository.go"
github.com/goto/shield/internal/store/cache:
config:
dir: "internal/store/cache/mocks"
outpkg: "mocks"
mockname: "{{.InterfaceName}}"
interfaces:
GroupRepository:
config:
filename: "group_repository.go"
12 changes: 10 additions & 2 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/goto/shield/internal/schema"
"github.com/goto/shield/internal/server"
"github.com/goto/shield/internal/store/blob"
"github.com/goto/shield/internal/store/cache"
"github.com/goto/shield/internal/store/postgres"
"github.com/goto/shield/internal/store/spicedb"
"github.com/goto/shield/pkg/db"
Expand Down Expand Up @@ -143,7 +144,7 @@ func StartServer(logger *log.Zap, cfg *config.Shield) error {
return err
}

deps, err := BuildAPIDependencies(ctx, logger, activityRepository, resourceBlobRepository, dbClient, spiceDBClient)
deps, err := BuildAPIDependencies(ctx, logger, activityRepository, resourceBlobRepository, dbClient, spiceDBClient, cfg)
if err != nil {
return err
}
Expand Down Expand Up @@ -185,7 +186,13 @@ func BuildAPIDependencies(
resourceBlobRepository *blob.ResourcesRepository,
dbc *db.Client,
sdb *spicedb.SpiceDB,
cfg *config.Shield,
) (api.Deps, error) {
cacheObj, err := cache.NewCache(cfg.App.CacheConfig)
if err != nil {
return api.Deps{}, err
}

appConfig := activity.AppConfig{Version: config.Version}
activityService := activity.NewService(appConfig, activityRepository)

Expand All @@ -206,7 +213,8 @@ func BuildAPIDependencies(
relationService := relation.NewService(logger, relationPGRepository, relationSpiceRepository, userService, activityService)

groupRepository := postgres.NewGroupRepository(dbc)
groupService := group.NewService(logger, groupRepository, relationService, userService, activityService)
cachedGroupRepository := cache.NewCachedGroupRepository(cacheObj, groupRepository)
groupService := group.NewService(logger, groupRepository, cachedGroupRepository, relationService, userService, activityService)

organizationRepository := postgres.NewOrganizationRepository(dbc)
organizationService := organization.NewService(logger, organizationRepository, relationService, userService, activityService)
Expand Down
4 changes: 4 additions & 0 deletions core/group/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ type Repository interface {
ListGroupRelations(ctx context.Context, objectId, subjectType, role string) ([]relation.RelationV2, error)
}

type CachedRepository interface {
GetBySlug(ctx context.Context, slug string) (Group, error)
}

type Group struct {
ID string
Name string
Expand Down
8 changes: 5 additions & 3 deletions core/group/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,17 @@ type ActivityService interface {
type Service struct {
logger log.Logger
repository Repository
cacheRepository CachedRepository
relationService RelationService
userService UserService
activityService ActivityService
}

func NewService(logger log.Logger, repository Repository, relationService RelationService, userService UserService, activityService ActivityService) *Service {
func NewService(logger log.Logger, repository Repository, cacheRepository CachedRepository, relationService RelationService, userService UserService, activityService ActivityService) *Service {
return &Service{
logger: logger,
repository: repository,
cacheRepository: cacheRepository,
relationService: relationService,
userService: userService,
activityService: activityService,
Expand Down Expand Up @@ -87,11 +89,11 @@ func (s Service) Get(ctx context.Context, idOrSlug string) (Group, error) {
if uuid.IsValid(idOrSlug) {
return s.repository.GetByID(ctx, idOrSlug)
}
return s.repository.GetBySlug(ctx, idOrSlug)
return s.cacheRepository.GetBySlug(ctx, idOrSlug)
}

func (s Service) GetBySlug(ctx context.Context, slug string) (Group, error) {
return s.repository.GetBySlug(ctx, slug)
return s.cacheRepository.GetBySlug(ctx, slug)
}

func (s Service) GetByIDs(ctx context.Context, groupIDs []string) ([]Group, error) {
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/authzed/authzed-go v0.7.1-0.20221109204547-1aa903788b3b
github.com/authzed/grpcutil v0.0.0-20230109193425-40ce0530e048
github.com/authzed/spicedb v1.15.0
github.com/dgraph-io/ristretto v0.1.1
github.com/doug-martin/goqu/v9 v9.18.0
github.com/envoyproxy/protoc-gen-validate v0.9.1
github.com/ghodss/yaml v1.0.0
Expand Down Expand Up @@ -80,6 +81,7 @@ require (
github.com/dlclark/regexp2 v1.7.0 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/felixge/fgprof v0.9.3 // indirect
Expand All @@ -88,6 +90,7 @@ require (
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/golang/glog v1.0.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/cel-go v0.13.0 // indirect
github.com/google/pprof v0.0.0-20221219190121-3cb0bae90811 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -870,8 +870,12 @@ github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27
github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA=
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dgryski/go-sip13 v0.0.0-20200911182023-62edffca9245/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dhui/dktest v0.3.10 h1:0frpeeoM9pHouHjhLeZDuDTJ0PqjDTrycaHaMmkJAo8=
Expand Down Expand Up @@ -914,6 +918,7 @@ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3
github.com/doug-martin/goqu/v9 v9.18.0 h1:/6bcuEtAe6nsSMVK/M+fOiXUNfyFF3yYtE07DBPFMYY=
github.com/doug-martin/goqu/v9 v9.18.0/go.mod h1:nf0Wc2/hV3gYK9LiyqIrzBEVGlI8qW3GuDCEobC4wBQ=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
Expand Down Expand Up @@ -2614,6 +2619,7 @@ golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220908150016-7ac13a9a928d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
3 changes: 3 additions & 0 deletions internal/server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package server
import (
"fmt"

"github.com/goto/shield/internal/store/cache"
"github.com/goto/shield/pkg/telemetry"
)

Expand Down Expand Up @@ -67,4 +68,6 @@ type Config struct {
ServiceData ServiceDataConfig `yaml:"service_data" mapstructure:"service_data"`

PublicAPIPrefix string `yaml:"public_api_prefix" mapstructure:"public_api_prefix" default:"/shield"`

CacheConfig cache.Config `yaml:"cache" mapstructure:"cache"`
}
39 changes: 39 additions & 0 deletions internal/store/cache/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package cache

import (
"errors"

"github.com/dgraph-io/ristretto"
)

var ErrParsing = errors.New("parsing error")

type Config struct {
NumCounters int64 `yaml:"num_counters" mapstructure:"num_counters" default:"10000000"`
MaxCost int64 `yaml:"max_cost" mapstructure:"max_cost" default:"1073741824"`
BufferItems int64 `yaml:"buffer_items" mapstructure:"buffer_items" default:"64"`
Metrics bool `yaml:"metrics" mapstructure:"metrics" default:"true"`
TTLInSeconds int `yaml:"ttl_in_seconds" mapstructure:"ttl_in_seconds" default:"3600"`
}

type Cache struct {
*ristretto.Cache
config Config
}

func NewCache(config Config) (Cache, error) {
cache, err := ristretto.NewCache(&ristretto.Config{
NumCounters: config.NumCounters,
MaxCost: config.MaxCost,
BufferItems: config.MaxCost,
Metrics: config.Metrics,
})
if err != nil {
return Cache{}, err
}

return Cache{
Cache: cache,
config: config,
}, nil
}
52 changes: 52 additions & 0 deletions internal/store/cache/group_repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package cache

import (
"context"
"fmt"
"time"

"github.com/goto/shield/core/group"
)

var keyPrefix = "group"

type GroupRepository interface {
GetBySlug(ctx context.Context, slug string) (group.Group, error)
}

type CachedGroupRepository struct {
cache Cache
repository GroupRepository
}

func NewCachedGroupRepository(cache Cache, repository GroupRepository) *CachedGroupRepository {
return &CachedGroupRepository{
cache: cache,
repository: repository,
}
}

func getKey(identifier string) string {
return fmt.Sprintf("%s:%s", keyPrefix, identifier)
}

func (r CachedGroupRepository) GetBySlug(ctx context.Context, slug string) (group.Group, error) {
key := getKey(slug)
grp, found := r.cache.Get(key)
if !found {
grp, err := r.repository.GetBySlug(ctx, slug)
if err != nil {
return group.Group{}, err
}

r.cache.SetWithTTL(key, grp, 0, time.Duration(r.cache.config.TTLInSeconds)*time.Second)
return grp, nil
}

grpParsed, ok := grp.(group.Group)
if !ok {
return group.Group{}, ErrParsing
}

return grpParsed, nil
}
122 changes: 122 additions & 0 deletions internal/store/cache/group_repository_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package cache

import (
"context"
"errors"
"testing"

"github.com/goto/shield/core/group"
"github.com/goto/shield/internal/store/cache/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

var (
testCacheConfig = Config{
NumCounters: 10000000,
MaxCost: 1073741824,
BufferItems: 64,
Metrics: true,
TTLInSeconds: 3600,
}
testGroupSlug = "test-group-slug"
testGroup = group.Group{
ID: "test-group-id",
Slug: testGroupSlug,
Name: "test group",
}
)

func TestGetBySlug(t *testing.T) {
t.Parallel()

testCases := []struct {
description string
slug string
setup func(t *testing.T) *CachedGroupRepository
want group.Group
wantErr error
}{
{
description: "should retrieve group from cache",
slug: testGroupSlug,
setup: func(t *testing.T) *CachedGroupRepository {
t.Helper()
groupRepository := &mocks.GroupRepository{}
c, err := NewCache(testCacheConfig)
if err != nil {
return nil
}
c.Set(getKey(testGroupSlug), testGroup, 0)
return NewCachedGroupRepository(c, groupRepository)
},
want: testGroup,
},
{
description: "should retrieve group from repository",
slug: testGroupSlug,
setup: func(t *testing.T) *CachedGroupRepository {
t.Helper()
groupRepository := &mocks.GroupRepository{}
c, err := NewCache(testCacheConfig)
if err != nil {
return nil
}
groupRepository.EXPECT().GetBySlug(mock.Anything, testGroupSlug).
Return(testGroup, nil)
return NewCachedGroupRepository(c, groupRepository)
},
want: testGroup,
},
{
description: "should return parse error if cache data invalid",
slug: testGroupSlug,
setup: func(t *testing.T) *CachedGroupRepository {
t.Helper()
groupRepository := &mocks.GroupRepository{}
c, err := NewCache(testCacheConfig)
if err != nil {
return nil
}
c.Set(getKey(testGroupSlug), "invalid-group-data", 0)
return NewCachedGroupRepository(c, groupRepository)
},
wantErr: ErrParsing,
},
{
description: "should return error from repository",
slug: testGroupSlug,
setup: func(t *testing.T) *CachedGroupRepository {
t.Helper()
groupRepository := &mocks.GroupRepository{}
c, err := NewCache(testCacheConfig)
if err != nil {
return nil
}
groupRepository.EXPECT().GetBySlug(mock.Anything, testGroupSlug).
Return(group.Group{}, group.ErrInvalidDetail)
return NewCachedGroupRepository(c, groupRepository)
},
wantErr: group.ErrInvalidDetail,
},
}

for _, tc := range testCases {
tc := tc
t.Run(tc.description, func(t *testing.T) {
t.Parallel()
cacheRepo := tc.setup(t)
assert.NotNil(t, cacheRepo)

ctx := context.TODO()
got, err := cacheRepo.GetBySlug(ctx, tc.slug)
if tc.wantErr != nil {
assert.Error(t, err)
assert.True(t, errors.Is(err, tc.wantErr))
} else {
assert.NoError(t, err)
}
assert.Equal(t, tc.want, got)
})
}
}
Loading

0 comments on commit bf8c863

Please sign in to comment.