Skip to content

skynet2/datasource-cache

Repository files navigation

Datasource Cache

build workflow codecov go-report PkgGoDev

Installation

go get github.com/skynet2/datasource-cache

Quickstart

package main

import (
	"context"
	"fmt"
	"github.com/go-redis/redis/v8"
	"github.com/rs/zerolog"
	cache "github.com/skynet2/datasource-cache"
	"strings"
)

func main() {
	redisClient := redis.NewClient(&redis.Options{
		Addr: "localhost:6379",
	})

	redisCacheProvider := cache.NewRedisCache[TranslatedEntity, string](
		redisClient)

	cacheManager := cache.NewCacheBuilder[TranslatedEntity, string](
		ModelVersion, redisCacheProvider).Build()

	tr := &translateService{cacheManager: cacheManager, dbRepo: &dbRepo{}}

	tr.GetTranslation(context.TODO(), "en", []string{"web:data1", "web:data2", "web:data3"})
}

const ModelVersion = uint16(1)

type dbRepo struct {
}

func (d *dbRepo) GetTokens(ctx context.Context, tokens []string) (map[string]map[string]string, error) {
	// retrieve data from db or any other source
    // for example if we requested 10 tokens, but 8 was in cache, here we`ll have only 2 tokens to request
	return map[string]map[string]string{}, nil
}

type TranslationToken struct {
	TokenKey     string
	Translations map[string]string
}

type TranslatedEntity struct {
	Value        string
	ModelVersion uint16
}

func (t TranslatedEntity) GetCacheModelVersion() uint16 {
	return t.ModelVersion
}

type translateService struct {
	cacheManager *cache.Cache[TranslatedEntity, string]
	dbRepo       *dbRepo
}

func (t *translateService) GetTranslation(
	ctx context.Context,
	language string,
	inputTokens []string,
) (map[string]string, error) {
	cacheTokens := make([]*cache.Key[string], 0)

	for _, token := range inputTokens {
		tok := strings.ToLower(token)

		cacheTokens = append(cacheTokens, &cache.Key[string]{
			OriginalValue: tok,                                 // here it will be as web:data1
			Key:           fmt.Sprintf("%v:%v", language, tok), // en:web:data1
		})
	}

	result, err := t.cacheManager.MGet(ctx, cacheTokens, func(ctx context.Context, keys []*cache.Key[string]) (map[*cache.Key[string]]*TranslatedEntity, error) {
		translations := make(map[*cache.Key[string]]*TranslatedEntity)
		missingTranslations := make(map[string]TranslationToken)

		toRequest := make([]string, 0, len(keys))
		reverseMap := map[string]*cache.Key[string]{}

		for _, k := range keys {
			toRequest = append(toRequest, k.OriginalValue)
			reverseMap[k.OriginalValue] = k
		}

		foundDbTokens, err := t.dbRepo.GetTokens(ctx, toRequest)

		if err != nil {
			return nil, err
		}

		for _, key := range toRequest {
			v, ok := reverseMap[key]

			if !ok {
				zerolog.Ctx(ctx).Warn().Msgf("not found in reverse map key %v", key)
				continue
			}

			if dbKey, ok := foundDbTokens[key]; ok {
				translations[v] = &TranslatedEntity{
					Value:        dbKey["default"], // find proper value
					ModelVersion: ModelVersion,
				}
			} else {
				translations[v] = &TranslatedEntity{
					ModelVersion: ModelVersion,
					Value:        key,
				}
				missingTranslations[key] = TranslationToken{
					TokenKey: key,
					Translations: map[string]string{
						"en": strings.ToLower(key),
					},
				}
			}
		}

		return translations, nil
	})

	if err != nil {
		return nil, err
	}

	final := make(map[string]string)

	for k, v := range result {
		final[k.OriginalValue] = v.Value
	}

	return final, nil
}