Go example: how to cache the content of a file.
This is a simple example of how to implement a basic content caching system using the Decorator design pattern in Go, where we will develop a simple function to read a file, to which we will add the caching function so that whenever we need get the content of a file, we will access the cached file content, avoiding reading it again.
We are going to use the Decorator Design Pattern in Go.
The Decorator Design Pattern is a structural pattern that allows adding new behaviors to objects dynamically by placing them inside special wrapper objects, called decorators.
Source: https://refactoring.guru/design-patterns/decorator
Decorators allow us to wrap objects whenever we need to, since both target objects and decorators follow the same interface. The resulting object will get stacking behavior from all containers.
We will write the code to read a file in Go.
package main
import (
"fmt"
"log"
"os"
)
func readFile(filename string) []byte {
content, err := os.ReadFile(filename)
if err != nil {
log.Fatal(err)
}
return content
}
func main() {
content := readFile("names.txt")
fmt.Println(string(content))
}
Executing the code, we will get the content of the File name.txt.
Now, we will develop the decorator function to cache the file content, and avoid opening it again.
package main
import (
"fmt"
"log"
"os"
"sync"
)
type fileReader func(string) []byte
func cacheFile(reader fileReader, cache *sync.Map) fileReader {
return func(filename string) []byte {
fn := func(filename string) []byte {
key := fmt.Sprintf("filename=%s", filename)
content, ok := cache.Load(key)
if ok {
log.Println("Using cached content for key =", key)
return content.([]byte)
}
content = reader(filename)
cache.Store(key, content)
log.Println("First reading, cached content with key =", key)
return content.([]byte)
}
return fn(filename)
}
}
func readFile(filename string) []byte {
content, err := os.ReadFile(filename)
if err != nil {
log.Fatal(err)
}
return content
}
func main() {
reader := cacheFile(readFile, &sync.Map{})
content := reader("names.txt")
fmt.Println(string(content))
content = reader("names.txt")
fmt.Println(string(content))
}
We have created the following cacheFile function to encapsulate the readFile function. The cacheFile function uses sync.Map as a cache to store the content of the files using key-value, where the key is the name of the file and the value is the content of the file, so each time we open via the wrapped readFile by cacheFile, the function (cacheFile) will check if the content is cached using the filename as the key; if the content exists it will return the cached content, if not will create a record (key-value = filename = content) in the cache and will return the content.
type fileReader func(string) []byte
func cacheFile(reader fileReader, cache *sync.Map) fileReader {
return func(filename string) []byte {
fn := func(filename string) []byte {
key := fmt.Sprintf("filename=%s", filename)
content, ok := cache.Load(key)
if ok {
log.Println("Using cached content for key =", key)
return content.([]byte)
}
content = reader(filename)
cache.Store(key, content)
log.Println("First reading, cached content with key =", key)
return content.([]byte)
}
return fn(filename)
}
}
Notice that we have created a fileReader type to be used as the data type in the cacheFile function so we can allow the readFile function to be wrapped inside the cacheFile function.
Once the command is run we will get the following output.
$ go run main.go
2023/08/26 22:08:57 First reading, cached content with key : filename=names.txt
id, name
1, Fred
2, Batman
3, Joker
2023/08/26 22:08:57 Using cached content for key : filename=names.txt
id, name
1, Fred
2, Batman
3, Joker
This is a very basic example of how to develop a content caching system applying the decorator design pattern in Go.
If you want to learn more about go, Go basics