DEV Community

Cover image for Storage Layer 📦
Јане Џумеркоски
Јане Џумеркоски

Posted on

Storage Layer 📦

In this part, we will be working on building the storage layer of our short URL application, so let's start!

📝 If Redis is not yet installed on your computer, you can do it following the instructions for installation with respect to your operating system.

First thing first, we will install Redis client for Golang

go get github.com/go-redis/redis/v9
Enter fullscreen mode Exit fullscreen mode

then we create the store folder in the project, and two empty Go files: store.go and store_test.go

├── go.mod
├── go.sum
├── main.go
└── shortener
   ├── shortener.go
   └── shortener_test.go
└── store
   ├── store.go
   └── store_test.go
Enter fullscreen mode Exit fullscreen mode

Now we can define wrapper structs and initialize the store service, in this case our Redis client.

package store

import (
    "context"
    "fmt"
    "github.com/go-redis/redis/v9"
    "time"
)

// StorageService is struct wrapper around raw Redis client
type StorageService struct {
    redisClient *redis.Client
}

// Top level declarations for the storeService and Redis context
var (
    storeService = &StorageService{}
    ctx          = context.Background()
)

const CacheDuration = 6 * time.Hour

// InitializeStore is initializing the store service and return a store pointer
func InitializeStore() *StorageService {
    rdb := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // no password set
        DB:       0,  // use default DB
    })

    pong, err := rdb.Ping(ctx).Result()
    if err != nil {
        panic(fmt.Sprintf("Error init Redis: %v", err))
    }

    fmt.Printf("\nRedis started successfully: pong message = {%s}", pong)
    storeService.redisClient = rdb
    return storeService
}
Enter fullscreen mode Exit fullscreen mode

Now that our data store service has successfully been initialized, it's time to consider what storage API to offer for our shortener server.

  1. We want to be able to save the mapping between the original URL and the generated short URL.
  2. We should be able to retrieve the initial long URL once the short is provided.

As the next step let's update store.go and implement our storage API.

func SaveURLInRedis(shortURL, originalURL string) {
    err := storeService.redisClient.Set(ctx, shortURL, originalURL, CacheDuration).Err()
    if err != nil {
        panic(fmt.Sprintf("Failed SaveURLInRedis | Error: %v - shortURL: %s - originalURL: %s\n", 
            err, shortURL, originalURL))
    }
}

func RetrieveInitialURLFromRedis(shortURL string) string {
    result, err := storeService.redisClient.Get(ctx, shortURL).Result()
    if err != nil {
        panic(fmt.Sprintf("Failed RetrieveInitialURLFromRedis | Error: %v - shortURL: %s\n", 
            err, shortURL))
    }
    return result
}
Enter fullscreen mode Exit fullscreen mode

It was fairly straightforward. Great job!

Since we already created store_test.go file, we will set up the test shell first and then write unit test for the storage APIs.

package store

import (
    "github.com/stretchr/testify/assert"
    "testing"
)

var testStoreService = &StorageService{}

func init() {
    testStoreService = InitializeStore()
}

func TestStoreInit(t *testing.T) {
    assert.True(t, testStoreService.redisClient != nil)
}

func TestInsertionAndRetrieval(t *testing.T) {
    initialLink := "https://go.dev/doc/tutorial/getting-started"
    shortURL := "dysg5Fas"

    // Persist data mapping
    SaveURLInRedis(shortURL, initialLink)

    // Retrieve initial URL
    retrievedUrl := RetrieveInitialURLFromRedis(shortURL)
    assert.Equal(t, initialLink, retrievedUrl)
}
Enter fullscreen mode Exit fullscreen mode

📝 Don't forget to run go test ./... to check if all your tests work as expected (✅).

As our storage service is set up, we will expose a Rest API endpoint for encoding and decoding the URLs in the next part.


Originally published at projectex.dev

Top comments (0)