GithubHelp home page GithubHelp logo

shomali11 / fridge Goto Github PK

View Code? Open in Web Editor NEW
13.0 5.0 3.0 204 KB

A cache that resembles storing, restocking and retrieving items from a fridge

License: MIT License

Go 100.00%
fridge cache cache-storage hot cold expired use-by redis fresh best-by

fridge's Introduction

fridge Build Status Go Report Card GoDoc License: MIT

fridge is a layer applied on top of a cache that makes interacting with it similar to interacting with a fridge. Items are tagged with a "Best By" and "Use By" timestamps, stored, restocked and retrieved.

Typically when using a cache, one would store some value along with a timeout. The value could be retrieved from the cache as long as it has not expired. If the value had expired, then an external call (Such as a database query) is usually made to retrieve the value, put it back in the cache and return it.

With fridge, we are taking a slightly different approach. Before storing a value in the fridge, one tags its key with a Best By and a Use By durations. When retrieving the value, a Restock function can be provided to refresh the value.

When attempting to retrieve a value from the fridge, there are multiple scenarios that could happen:

  • If the item has not passed its Best By duration (it is fresh)
    • Then the item is returned immediately.
  • If the item has passed its Best By duration but not its Use By duration (Not fresh but not expired either)
    • Then the item is returned immediately
    • But the Restock function is called asynchronously to refresh the item.
  • If the item has passed its Use By duration (it has expired)
    • The Restock function is called synchronously to retrieve a fresh item and return it.
  • If the item was not found
    • It is treated similarly to an expired item
    • The Restock function is called synchronously to retrieve a fresh item and return it.

Why?

The thinking behind fridge is to increase the chances for a value to be retrieved from the cache. The longer the value stays in the cache, the better the chances are to retrieve it faster (As opposed to from the database)

The challenge, of course, is to keep the value in the cache "fresh".

Dependencies

Examples

Example 1

Using NewClient to create a new fridge client. Note: NewClient accepts an object that implements the Cache interface which allows the user to use fridge with any underlying implementation.

// Cache is a Fridge cache interface
type Cache interface {
	// Get a value by key
	Get(key string) (string, bool, error)

	// Set a key value pair
	Set(key string, value string, timeout time.Duration) error

	// Remove a key
	Remove(key string) error

	// Ping to test connectivity
	Ping() error

	// Close to close resources
	Close() error
}
package main

import (
	"fmt"
	"github.com/shomali11/fridge"
	"time"
)

type SimpleCache struct {
	memory map[string]string
}

func (c *SimpleCache) Get(key string) (string, bool, error) {
	value, ok := c.memory[key]
	return value, ok, nil
}

func (c *SimpleCache) Set(key string, value string, timeout time.Duration) error {
	// We are not implementing the expiration to keep the example simple
	c.memory[key] = value
	return nil
}

func (c *SimpleCache) Remove(key string) error {
	delete(c.memory, key)
	return nil
}

func (c *SimpleCache) Ping() error {
	return nil
}

func (c *SimpleCache) Close() error {
	return nil
}

func main() {
	simpleCache := &SimpleCache{memory: make(map[string]string)}
	client := fridge.NewClient(simpleCache)

	fmt.Println(client.Ping())
}

Example 2

Using NewRedisCache to use fridge with redis. Note: NewRedisCache creates an redis client that implements the Cache interface

package main

import (
	"fmt"
	"github.com/shomali11/fridge"
)

func main() {
	redisCache := fridge.NewRedisCache()
	client := fridge.NewClient(redisCache)
	defer client.Close()

	fmt.Println(client.Ping())
}

Output

<nil>

Example 3

Using NewRedisCache with modified settings for a redis client

package main

import (
	"fmt"
	"github.com/shomali11/fridge"
)

func main() {
	redisCache := fridge.NewRedisCache(
		fridge.WithHost("localhost"), 
		fridge.WithPort(6379))
		
	client := fridge.NewClient(redisCache)
	defer client.Close()

	fmt.Println(client.Ping())
}

Output

<nil>

Example 4

Using NewSentinelCache with modified settings for a redis sentinel client. Note: NewSentinelCache creates an redis sentinel client that implements the Cache interface

package main

import (
	"fmt"
	"github.com/shomali11/fridge"
)

func main() {
	redisCache := fridge.NewSentinelCache(
		fridge.WithSentinelAddresses([]string{"localhost:26379"}),
		fridge.WithSentinelMasterName("master"))

	client := fridge.NewClient(redisCache)
	defer client.Close()

	fmt.Println(client.Ping())
}

Output

<nil>

Example 5

Using Put, Get & Remove to show how to put, get and remove an item. Note: That we are using a default client that has a default Best By of 1 hour and Use By of 1 Day for all keys

package main

import (
	"fmt"
	"github.com/shomali11/fridge"
)

func main() {
	redisCache := fridge.NewRedisCache()
	client := fridge.NewClient(redisCache)
	defer client.Close()

	fmt.Println(client.Put("food", "Pizza"))
	fmt.Println(client.Get("food"))
	fmt.Println(client.Remove("food"))
	fmt.Println(client.Get("food"))
}

Output

<nil>
Pizza true <nil>
<nil>
 false <nil>

Example 6

Using WithDefaultDurations to override the default Best By and Use By durations for all keys

package main

import (
	"fmt"
	"github.com/shomali11/fridge"
	"time"
)

func main() {
	redisCache := fridge.NewRedisCache()
	client := fridge.NewClient(redisCache, fridge.WithDefaultDurations(time.Second, 2*time.Second))
	defer client.Close()

	fmt.Println(client.Put("food", "Pizza"))
	fmt.Println(client.Get("food"))

	time.Sleep(time.Second)

	fmt.Println(client.Get("food"))

	time.Sleep(2 * time.Second)

	fmt.Println(client.Get("food"))
	fmt.Println(client.Remove("food"))
}

Output

<nil>
Pizza true <nil>
Pizza true <nil>
 false <nil>
<nil>

Example 7

Using Put to show how to put an item and override that item's durations.

package main

import (
	"fmt"
	"github.com/shomali11/fridge"
	"time"
)

func main() {
	redisCache := fridge.NewRedisCache()
	client := fridge.NewClient(redisCache)
	defer client.Close()

	fmt.Println(client.Put("food", "Pizza", fridge.WithDurations(time.Second, 2*time.Second)))
	fmt.Println(client.Get("food"))

	time.Sleep(time.Second)

	fmt.Println(client.Get("food"))

	time.Sleep(2 * time.Second)

	fmt.Println(client.Get("food"))
	fmt.Println(client.Remove("food"))
}

Output

<nil>
Pizza true <nil>
Pizza true <nil>
 false <nil>
<nil>

Example 8

Using Get to show how to retrieve an item while providing a restocking mechanism.

package main

import (
	"fmt"
	"github.com/shomali11/fridge"
	"time"
)

func main() {
	redisCache := fridge.NewRedisCache()
	client := fridge.NewClient(redisCache)
	defer client.Close()

	restock := func() (string, error) {
		return "Hot Pizza", nil
	}

	fmt.Println(client.Put("food", "Pizza", fridge.WithDurations(time.Second, 2*time.Second)))
	fmt.Println(client.Get("food", fridge.WithRestock(restock)))

	time.Sleep(time.Second)

	fmt.Println(client.Get("food", fridge.WithRestock(restock)))

	time.Sleep(2 * time.Second)

	fmt.Println(client.Get("food", fridge.WithRestock(restock)))
	fmt.Println(client.Remove("food"))
}

Output

<nil>
Pizza true <nil>
Pizza true <nil>
Hot Pizza true <nil>
<nil>

Example 9

Using HandleEvent to pass a callback to access the stream of events generated

package main

import (
	"fmt"
	"github.com/shomali11/fridge"
	"time"
)

func main() {
	redisCache := fridge.NewRedisCache()
	client := fridge.NewClient(redisCache, fridge.WithDefaultDurations(time.Second, 2*time.Second))
	defer client.Close()

	client.HandleEvent(func(event *fridge.Event) {
		fmt.Print("Key: " + event.Key + " - ")

		switch event.Type {
		case fridge.Fresh:
			fmt.Println("Woohoo! it is fresh!")
		case fridge.Cold:
			fmt.Println("Not fresh! But not bad either!")
		case fridge.Expired:
			fmt.Println("Sigh. It has expired!")
		case fridge.NotFound:
			fmt.Println("Oops! Did not find it.")
		case fridge.Restock:
			fmt.Println("Yay! Getting a new one!")
		case fridge.OutOfStock:
			fmt.Println("Oh no! It is out of stock.")
		case fridge.Unchanged:
			fmt.Println("Interesting! It has not changed.")
		}
	})

	restock := func() (string, error) {
		return "Pizza", nil
	}

	client.Put("food1", "Pizza")
	client.Put("food2", "Milk")

	client.Get("food1", fridge.WithRestock(restock))
	client.Get("food2")
	client.Get("food3")

	time.Sleep(time.Second)

	client.Get("food1", fridge.WithRestock(restock))
	client.Get("food2")
	client.Get("food3")

	time.Sleep(2 * time.Second)

	client.Get("food1", fridge.WithRestock(restock))
	client.Get("food2")
	client.Get("food3")

	client.Remove("food1")
	client.Remove("food2")
	client.Remove("food3")
}

Output:

Key: food1 - Woohoo! it is fresh!
Key: food2 - Woohoo! it is fresh!
Key: food3 - Oops! Did not find it.
Key: food1 - Not fresh! But not bad either!
Key: food1 - Yay! Getting a new one!
Key: food1 - Interesting! It has not changed.
Key: food2 - Not fresh! But not bad either!
Key: food2 - Oh no! It is out of stock.
Key: food3 - Oops! Did not find it.
Key: food1 - Sigh. It has expired!
Key: food1 - Yay! Getting a new one!
Key: food2 - Sigh. It has expired!
Key: food2 - Oh no! It is out of stock.
Key: food3 - Oops! Did not find it.

fridge's People

Contributors

raed-shomali avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.