httpcache

package module
v0.3.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 19, 2024 License: MIT Imports: 20 Imported by: 0

README

HTTPCache module

Go Report Card GoDoc Tests

The HTTPCache module provides an easy interface to cache simple HTTP results in Flamingo.

The basic concept is, that there is a so-called "cache frontend" - that offers an interface to cache certain types, and a "cache backend" that takes care about storing(persisting) the cache entry.

Caching HTTP responses from APIs

A typical use case is, to cache responses from (slow) APIs that you need to call.

First, add the dependency to your project:

go get flamingo.me/httpcache

For an easy start the module ships with a cache frontend factory, we'll configure a frontend that relies on an in-memory backend. The name of the frontend will be myServiceCache and the cache can store up to 50 entries before dropping the least frequently used.

Add this to your config.yaml:

httpcache:
  frontendFactory:
    myServiceCache:
      backendType: memory
      memory:
        size: 50 // limit of entries

For a list of all supported cache backends see the cache backends section below.

Afterward you can use the cache frontend in your API client:

package api

import (
	"context"
	"net/http"
	"time"

	"flamingo.me/httpcache"
)

type MyApiClient struct {
	Cache *httpcache.Frontend `inject:"myServiceCache"`
}

type Result struct{}

func (m *MyApiClient) Operation(ctx context.Context) (*Result, error) {
	cacheEntry, err := m.Cache.Get(ctx, "operation-cache-key", m.doApiCall(ctx, "https://example.com/v1/operation"))
	if err != nil {
		return nil, err
	}

	// unmarshall cacheEntry.Body map to Result struct
	_ = cacheEntry.Body

	return nil, nil
}

func (m *MyApiClient) doApiCall(ctx context.Context, endpoint string) httpcache.HTTPLoader {
	return func(ctx context.Context) (httpcache.Entry, error) {
		// grab http client with timeout
		// call endpoint
		_ = endpoint

		return httpcache.Entry{
			Meta: httpcache.Meta{
				LifeTime:  time.Now().Add(5 * time.Minute),
				GraceTime: time.Now().Add(10 * time.Minute),
				Tags:      nil,
			},
			Body:       []byte{}, // API Response that should be cached
			StatusCode: http.StatusOK,
		}, nil
	}
}

Cache backends

Currently, there are the following backends available:

In memory

backendType: memory

Caches in memory - and therefore is a very fast cache.

It is base on the LRU-Strategy witch drops the least used entries. For this reason the cache will be no over-commit your memory and will atomically fit the need of your current traffic.

Example config:

httpcache:
  frontendFactory:
    myServiceCache:
      backendType: memory
      memory:
        size: 200 // limit of entries
Redis

backendType: redis

Is using redis as a shared inMemory cache. Since all cache-fetched has an overhead to the inMemoryBackend, the redis is a little slower. The benefit of redis is the shared storage and the high efficiency in reading and writing keys. Especially if you need scale fast horizontally, it helps to keep your backend-systems healthy.

Be aware of using redis (or any other shared cache backend) as a single backend, because of network latency. (have a look at the twoLevelBackend)

httpcache:
  frontendFactory:
    myServiceCache:
      backendType: redis
      redis:
        host: '%%ENV:REDISHOST%%localhost%%'
        port: '6379'
Two Level

backendType: twolevel

The two level backend was introduced to get the benefit of the extreme fast memory backend and a shared backend. Using the inMemoryBackend in combination with a shared backend, gives you blazing fast responses and helps you to protect your backend in case of fast scaleout-scenarios.

Example config using the frontend cache factory:

httpcache:
  frontendFactory:
    myServiceCache:
      backendType: twolevel
      twolevel:
        first:
          backendType: memory
          memory:
            size: 200
        second:
          backendType: redis
          redis:
            # Close connections after remaining idle for this duration. If the value
            # is zero, then idle connections are not closed. Applications should set
            # the timeout to a value less than the server's timeout.
            idleTimeOutSeconds: 60
            host: '%%ENV:REDISHOST%%localhost%%'
            port: '6379'
            maxIdle: 8
Implement custom cache backend

If you are missing a cache backend feel free to open a issue or pull request. It's of course possible to implement a custom cache backend in your project, see example below:

package cache_backend

import (
	"flamingo.me/dingo"
	"flamingo.me/httpcache"
)

type Module struct {
	provider httpcache.FrontendProvider
}

type CustomBackend struct {
	// implement logic
}

var _ httpcache.Backend = new(CustomBackend)

// Inject dependencies
func (m *Module) Inject(
	provider httpcache.FrontendProvider,
) *Module {
	m.provider = provider
	return m
}

// Configure DI
func (m *Module) Configure(injector *dingo.Injector) {
	frontend := m.provider()
	frontend.SetBackend(&CustomBackend{})

	injector.Bind((*httpcache.Frontend)(nil)).AnnotatedWith("myServiceWithCustomBackend").ToInstance(frontend)
}

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrAllBackendsFailed       = errors.New("all backends failed")
	ErrAtLeastOneBackendFailed = errors.New("at least one backends failed")
)
View Source
var ErrInvalidBackend = errors.New("invalid backend supplied")
View Source
var ErrInvalidEntry = errors.New("cache returned invalid entry type")
View Source
var (
	ErrInvalidRedisConfig = errors.New("invalid redis config")
)
View Source
var ErrMemoryConfig = errors.New("memory config not complete")
View Source
var ErrNoCacheBackend = errors.New("no backend defined")
View Source
var ErrRedisConfig = errors.New("redis config not complete")
View Source
var ErrTwoLevelConfig = errors.New("twolevel config not complete")

Functions

This section is empty.

Types

type Backend

type Backend interface {
	Get(key string) (Entry, bool)
	Set(key string, entry Entry) error
	Purge(key string) error
	Flush() error
}

Backend to persist cache data

type BackendConfig

type BackendConfig struct {
	BackendType string
	Memory      *MemoryBackendConfig
	Redis       *RedisBackendConfig
	Twolevel    *struct {
		First  *BackendConfig
		Second *BackendConfig
	}
}

BackendConfig typed configuration used to build BackendCaches by the factory

type Entry

type Entry struct {
	Meta       Meta
	Header     map[string][]string
	Status     string
	StatusCode int
	Body       []byte
}

Entry represents a cached HTTP Response

type FactoryConfig

type FactoryConfig map[string]BackendConfig

FactoryConfig typed configuration used to build Caches by the factory

type Frontend

type Frontend struct {
	singleflight.Group
	// contains filtered or unexported fields
}

Frontend caches and delivers HTTP responses

func (*Frontend) Get

func (f *Frontend) Get(ctx context.Context, key string, loader HTTPLoader) (Entry, error)

Get the cached response if possible or perform a call to loader The result of loader will be returned and cached

func (*Frontend) Inject

func (f *Frontend) Inject(
	logger flamingo.Logger,
) *Frontend

Inject dependencies

func (*Frontend) Purge added in v0.2.0

func (f *Frontend) Purge(ctx context.Context, key string) error

func (*Frontend) SetBackend

func (f *Frontend) SetBackend(b Backend) *Frontend

SetBackend for usage

type FrontendFactory

type FrontendFactory struct {
	// contains filtered or unexported fields
}

FrontendFactory that can be used to build caches

func (*FrontendFactory) BindConfiguredCaches

func (f *FrontendFactory) BindConfiguredCaches(injector *dingo.Injector) error

BindConfiguredCaches creates annotated bindings from the cache configuration

func (*FrontendFactory) BuildBackend

func (f *FrontendFactory) BuildBackend(backendConfig BackendConfig, frontendName string) (Backend, error)

BuildBackend by given BackendConfig and frontendName

func (*FrontendFactory) BuildWithBackend

func (f *FrontendFactory) BuildWithBackend(backend Backend) *Frontend

BuildWithBackend returns new HTTPFrontend cache with given backend

func (*FrontendFactory) Inject

func (f *FrontendFactory) Inject(
	provider FrontendProvider,
	redisBackendFactory *RedisBackendFactory,
	inMemoryBackendFactory *InMemoryBackendFactory,
	twoLevelBackendFactory *TwoLevelBackendFactory,
	cfg *struct {
		CacheConfig config.Map `inject:"config:httpcache.frontendFactory,optional"`
	},
) *FrontendFactory

Inject for dependencies

func (*FrontendFactory) NewMemoryBackend

func (f *FrontendFactory) NewMemoryBackend(config MemoryBackendConfig, frontendName string) (Backend, error)

NewMemoryBackend with given config and name

func (*FrontendFactory) NewRedisBackend

func (f *FrontendFactory) NewRedisBackend(config RedisBackendConfig, frontendName string) (Backend, error)

NewRedisBackend with given config and name

func (*FrontendFactory) NewTwoLevel

func (f *FrontendFactory) NewTwoLevel(config TwoLevelBackendConfig) (Backend, error)

NewTwoLevel with given config

type FrontendProvider

type FrontendProvider func() *Frontend

FrontendProvider - Dingo Provider func

type HTTPLoader

type HTTPLoader func(context.Context) (Entry, error)

HTTPLoader returns an Entry to be cached. All Entries will be cached if error is nil

type InMemoryBackendFactory

type InMemoryBackendFactory struct {
	// contains filtered or unexported fields
}

InMemoryBackendFactory factory

func (*InMemoryBackendFactory) Build

func (f *InMemoryBackendFactory) Build() (Backend, error)

Build the instance

func (*InMemoryBackendFactory) SetConfig

SetConfig for factory

func (*InMemoryBackendFactory) SetFrontendName

func (f *InMemoryBackendFactory) SetFrontendName(frontendName string) *InMemoryBackendFactory

SetFrontendName used in Metrics

func (*InMemoryBackendFactory) SetLurkerPeriod added in v0.3.1

func (f *InMemoryBackendFactory) SetLurkerPeriod(period time.Duration) *InMemoryBackendFactory

SetLurkerPeriod sets the timeframe how often expired cache entries should be checked/cleaned up, if 0 is provided the default period of 1 minute is taken

type MemoryBackend

type MemoryBackend struct {
	// contains filtered or unexported fields
}

MemoryBackend implements the cache backend interface with an "in memory" solution

func (*MemoryBackend) Flush

func (m *MemoryBackend) Flush() error

Flush purges all entries in the cache

func (*MemoryBackend) Get

func (m *MemoryBackend) Get(key string) (Entry, bool)

Get tries to get an object from cache

func (*MemoryBackend) Purge

func (m *MemoryBackend) Purge(key string) error

Purge a cache key

func (*MemoryBackend) Set

func (m *MemoryBackend) Set(key string, entry Entry) error

Set a cache entry with a key

func (*MemoryBackend) SetSize

func (m *MemoryBackend) SetSize(size int) error

SetSize creates a new underlying cache of the given size

type MemoryBackendConfig

type MemoryBackendConfig struct {
	Size int
}

MemoryBackendConfig config

type Meta

type Meta struct {
	LifeTime  time.Time
	GraceTime time.Time
	Tags      []string
}

Meta data for a cache Entry

type Metrics

type Metrics struct {
	// contains filtered or unexported fields
}

Metrics take care of publishing metrics for a specific cache

func NewCacheMetrics

func NewCacheMetrics(backendType string, frontendName string) Metrics

NewCacheMetrics creates a backend metrics helper instance

type MetricsBackend

type MetricsBackend struct {
}

MetricsBackend - a Backend that logs metrics

type Module

type Module struct {
	// contains filtered or unexported fields
}

Module basic struct

func (*Module) Configure

func (m *Module) Configure(injector *dingo.Injector)

Configure DI

func (*Module) CueConfig

func (m *Module) CueConfig() string

CueConfig definition

func (*Module) Inject

func (m *Module) Inject(
	frontendFactory *FrontendFactory,
) *Module

Inject dependencies

type RedisBackend

type RedisBackend struct {
	// contains filtered or unexported fields
}

RedisBackend implements the cache backend interface with a redis solution

func (*RedisBackend) Flush

func (b *RedisBackend) Flush() error

Flush the whole cache

func (*RedisBackend) Get

func (b *RedisBackend) Get(key string) (entry Entry, found bool)

Get a cache key

func (*RedisBackend) Purge

func (b *RedisBackend) Purge(key string) error

Purge a cache key

func (*RedisBackend) PurgeTags

func (b *RedisBackend) PurgeTags(tags []string) error

PurgeTags purges all keys+tags by tag(s)

func (*RedisBackend) Set

func (b *RedisBackend) Set(key string, entry Entry) error

Set a cache key

func (*RedisBackend) Status added in v0.3.0

func (b *RedisBackend) Status() (bool, string)

Status checks the health of the used redis instance

type RedisBackendConfig

type RedisBackendConfig struct {
	MaxIdle            int
	IdleTimeOutSeconds int
	Host               string
	Port               string
}

RedisBackendConfig holds the configuration values

type RedisBackendFactory

type RedisBackendFactory struct {
	// contains filtered or unexported fields
}

RedisBackendFactory creates fully configured instances of Redis

func (*RedisBackendFactory) Build

func (f *RedisBackendFactory) Build() (Backend, error)

Build a new redis backend

func (*RedisBackendFactory) Inject

Inject Redis dependencies

func (*RedisBackendFactory) SetConfig

SetConfig for redis

func (*RedisBackendFactory) SetFrontendName

func (f *RedisBackendFactory) SetFrontendName(frontendName string) *RedisBackendFactory

SetFrontendName for redis cache metrics

func (*RedisBackendFactory) SetPool

SetPool directly - use instead of SetConfig if desired

type TagSupporting

type TagSupporting interface {
	PurgeTags(tags []string) error
}

TagSupporting describes a cache backend, responsible for storing, flushing, setting and getting entries

type TwoLevelBackend

type TwoLevelBackend struct {
	// contains filtered or unexported fields
}

TwoLevelBackend the cache backend interface with a two level solution

func (*TwoLevelBackend) Flush

func (mb *TwoLevelBackend) Flush() (err error)

Flush the whole cache

func (*TwoLevelBackend) Get

func (mb *TwoLevelBackend) Get(key string) (entry Entry, found bool)

Get entry by key

func (*TwoLevelBackend) Purge

func (mb *TwoLevelBackend) Purge(key string) (err error)

Purge entry by key

func (*TwoLevelBackend) Set

func (mb *TwoLevelBackend) Set(key string, entry Entry) error

Set entry for key

func (*TwoLevelBackend) Status added in v0.3.0

func (mb *TwoLevelBackend) Status() (bool, string)

Status checks the health of the used backends

type TwoLevelBackendConfig

type TwoLevelBackendConfig struct {
	FirstLevel  Backend
	SecondLevel Backend
}

TwoLevelBackendConfig defines the backends to be used

type TwoLevelBackendFactory

type TwoLevelBackendFactory struct {
	// contains filtered or unexported fields
}

TwoLevelBackendFactory creates instances of TwoLevel backends

func (*TwoLevelBackendFactory) Build

func (f *TwoLevelBackendFactory) Build() (Backend, error)

Build the instance

func (*TwoLevelBackendFactory) Inject

Inject dependencies

func (*TwoLevelBackendFactory) SetConfig

SetConfig for factory

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL