Finding true love is not difficult with RedisJSON

In the first episode of this series, we looked at the importance of JSON, JSON databases and RedisJSON, setting up Redis Cloud, Redis Stack, and Redis Insight, and how we can store all types of data (scalars, objects, arrays of objects). can do. ) in redisjson. If you haven’t then take a moment to read that great article here. Undoubtedly, we were about an inch closer to our goal of finding the right match for the returning prisoners. Eventually everyone can find true love. Let us take a step forward towards our goal in this article.

Wow!!! It’s time to make!

The wonders of RedisJSON will be further explored in this next tutorial. How can we prepare our data dimensions for our match using code? With Golang, we’ll make figuring out how to easily interact with our RedisJSON database and allow returning prisoners to specify their interests a breeze.

Good stuff, yes?!

If you’re already excited, can I get an upvote?

We will generally use a simple directory structure and code arrangement pattern in this post (as much as we can). In more serious implementations it is recommended to use a more Golang idiomatic architectural style. However we will separate the concerns in the simplest forms. We may expand on this pattern in a future post. We will also use the REST API standard. This code would be built as a monolith to avoid complications but could later be extended to more advanced architectures. Lords for micro services and best practices:

Let’s create a directory for our code. In Unix-like systems, we can do this:

mkdir dating-app && cd dating-app
enter fullscreen mode

exit fullscreen mode

It would be great to start with setting up and organizing some of our dependencies. Run this in your terminal’s root project:

#go mod init {your-repo-name}
#For me I have:
go mod init github.com/femolacaster/dating-app

#Tidy things up
go mod tidy

#Call on your Redis Soldiers
go get github.com/gomodule/redigo/redis
go get github.com/nitishm/go-rejson/v4

#Let us include MUX for our API routing
go get -u github.com/gorilla/mux
enter fullscreen mode

exit fullscreen mode

The next step would be to create the following route in a folder named Roots in the root directory of our application:

[route-dir]/route/route.go

package routes

import (
    "github.com/femolacaster/dating-app/controllers"
    "github.com/gorilla/mux"
)

func Init() *mux.Router {
    route := mux.NewRouter()

    route.HandleFunc("/api/v1/criteria", controllers.ShowAll)
    route.HandleFunc("/api/v1/criteria", controllers.Add).Methods("POST")
    route.HandleFunc("/api/v1/criteria/ {id}/dimension", controllers.ShowDimension)
    return route
}
enter fullscreen mode

exit fullscreen mode

A simple routing is shown in the code above. The init function returns 3 exported routes that will allow for the new addition of criteria to return prisoners using the POST method, displaying all the various dating criteria of prisoners on the application, and special criteria (either casual or Critical). Using the GET method.

A good next step would be to create helpers for our code. Helpers are functions that you use repeatedly throughout your code. They come through . The two helper functions identified in this case are “RenderErrorResponse” and “RenderResponse” respectively. These functions help us to present the output of our API in a simple format, depending on whether it is an error or otherwise.

what do we have:

[route-dir]/helpers/dating.go

package helpers

import (
    "encoding/json"
    "net/http"
)

type ErrorResponse struct {
    Error string `json:"error"`
}

func RenderErrorResponse(w http.ResponseWriter, msg string, status int) {
    RenderResponse(w, ErrorResponse{Error: msg}, status)
}

func RenderResponse(w http.ResponseWriter, res interface{}, status int) {
    w.Header().Set("Content-Type", "application/json")
    content, err := json.Marshal(res)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        return
    }
    w.WriteHeader(status)
    if _, err = w.Write(content); err != nil {
    }
}
enter fullscreen mode

exit fullscreen mode

In short, we can add one more helper function. It simply connects to our RedisJSON local database and outputs the Rediigo client connection instance which we can use for our logic:

func NewRedisConn() *rejson.Handler {
    var addr = flag.String("Server", "localhost:6379", "Redis server address")
    rh := rejson.NewReJSONHandler()
    flag.Parse()
    // Redigo Client
    conn, err := redis.Dial("tcp", *addr)
    if err != nil {
        log.Fatalf("Failed to connect to redis-server @ %s", *addr)
    }
    defer func() {
        _, err = conn.Do("FLUSHALL")
        err = conn.Close()
        if err != nil {
            log.Fatalf("Failed to communicate to redis-server @ %v", err)
        }
    }()
    rh.SetRedigoClient(conn)
    return rh
}
enter fullscreen mode

exit fullscreen mode

Let us create the logic for our routes.

We create a new file:

[route-dir]/controllers/dating.go

This file will contain three functions that define our logic. The first would allow a new addition of criteria to return prisoners, the second would display all the various dating criteria of prisoners on the application and the last would allow filtering by criteria (either casual or serious).

The first thing to do in this section would be to store the various interests in a structure and then add interest and other details to form a prisoner’s criteria as shown in this structure:

type Criteria struct {
    ID                int             `json:"id"`
    Name              string          `json:"name"`
    Height            float32         `json:"height"` //height in feet and inches
    WeightKG          int             `json:"weight"`
    SexualOrientation string          `json:"sexualOrientation"`
    Age               int             `json:"age"`
    CasualInterest    CasualInterest  `json:"casualInterest"`
    SeriousInterest   SeriousInterest `json:"seriousInterest"`
}
type SeriousInterest struct {
    Career        bool `json:"career"`
    Children      bool `json:"children "`
    Communication bool `json:"communication"`
    Humanity      bool `json:"humanity"`
    Investment    bool `json:"investment"`
    Marriage      bool `json:"marriage"`
    Religion      bool `json:"religion"`
    Politics      bool `json:"politics"`
}
type CasualInterest struct {
    Entertainment bool `json:"entertainment"`
    Gym           bool `json:"gym"`
    Jewellries    bool `json:"jewellries"`
    OneNight      bool `json:"oneNight"`
    Restaurant    bool `json:"restaurant"`
    Swimming      bool `json:"swimming"`
    Travel        bool `json:"travel"`
    Yolo          bool `json:"yolo"`
}
enter fullscreen mode

exit fullscreen mode

In all our logic functions, we used the Golang Regson instance provided in the helpers. New rediscon function that will be used to communicate with our redisjson database.

rh := helpers.NewRedisConn()
enter fullscreen mode

exit fullscreen mode

Rejson is a Redis module that implements ECMA-404, the JSON Data Interchange Standard, as a native data type and allows storing, updating, and fetching JSON values ​​from Redis keys which are supported by two popular Golang clients: redigo and Also supports go-redis.

Here are the differences between redigo and go-redis to make your own informed choice:

i reduce go-redis
it is less type-safe it is more type-safe
It can be faster and easier to use It can be slow and not as easy to use as Redigo
Don’t use it if you plan to scale your database to a high-available cluster Perfect for clustering. Perfecto!

So yes, the choice is yours. In this post, we have definitely picked out the easier option which is redigo and you must be seeing its usage in controller actions.

For our first function which adds the criteria for a prisoner:

func Add(w http.ResponseWriter, r *http.Request) {
    var req Criteria
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        helpers.RenderErrorResponse(w, "invalid request", http.StatusBadRequest)
        return
    }
    defer r.Body.Close()

    rh := helpers.NewRedisConn()

    res, err := rh.JSONSet("criteria", ".", &req)
    if err != nil {
        log.Fatalf("Failed to JSONSet")
        return
    }
    if res.(string) == "OK" {
        fmt.Printf("Success: %s\n", res)
        helpers.RenderResponse(w, helpers.ErrorResponse{Error: "Successfully inserted new Criteria to Database"}, http.StatusCreated)
    } else {
        fmt.Println("Failed to Set: ")
        helpers.RenderErrorResponse(w, "invalid request", http.StatusBadRequest)
    }
}
enter fullscreen mode

exit fullscreen mode

The second endpoint that shows all the criteria is shown below:

func ShowAll(w http.ResponseWriter, r *http.Request) {
    rh := helpers.NewRedisConn()
    criteriaJSON, err := redis.Bytes(rh.JSONGet("criteria", "."))
    if err != nil {
        log.Fatalf(("Failed to get JSON"))
        return
    }

    readCriteria := Criteria{}
    err = json.Unmarshal(criteriaJSON, &readCriteria)
    if err != nil {
        fmt.Printf("JSON Unmarshal Failed")
        helpers.RenderErrorResponse(w, "invalid request", http.StatusBadRequest)
    }
    fmt.Printf("Student read from RedisJSON:%#v\n", readCriteria)
    helpers.RenderResponse(w, helpers.ErrorResponse{Error: "Successful retrieval of criterias"}, http.StatusOK)

}
enter fullscreen mode

exit fullscreen mode

Now to find out whether the criteria for a prisoner are casual or severe, can you try to apply it yourself?

There are many ways to go about this.

One tip would be:

Get all criteria from RedisJSON as shown in ShowAll function, but this time using key which is id to get those criteria. Then since the CasualInterest struct and the SeriousInterest struct have bool fields, compare the two different struct values ​​to determine which one has “1” or “true”. That way you can decide the prisoner who is inclined to seek something serious or casual. That logic works, I guess. But of course, you can come up with a much better argument.

it should be easy. It would be nice to leave some of your beautiful implementations in the comments section.

In main.go on our root directory, we can create our server:

package main

import (
    "errors"
    "fmt"
    "log"
    "net/http"
    "os"
    "time"

    "github.com/femolacaster/dating-app/routes"
    "github.com/ichtrojan/thoth"
    "github.com/joho/godotenv"
)


func main() {

    logger, thothErr := thoth.Init("log")
    if thothErr != nil {
        log.Fatal(thothErr)
    }

    //warning, error, log, trace, metrics
    if envLoadErr := godotenv.Load(); envLoadErr != nil {
        logger.Log(errors.New("There was a problem loading an environmental file. Please check file is present."))
        log.Fatal("Error:::There was a problem loading an environmental file. Please check file is present.")
    }

    appPort, appPortExist := os.LookupEnv("APPPORT")

    if !appPortExist {
        logger.Log(errors.New("There was no Port variable for the application in the env file"))
        log.Fatal("Error:::There was no Port variable for the application in the env file")
    }
    address := ":" + appPort

    srv := &http.Server{
        Handler:           routes.Init(),
        Addr:              address,
        ReadTimeout:       1 * time.Second,
        ReadHeaderTimeout: 1 * time.Second,
        WriteTimeout:      1 * time.Second,
        IdleTimeout:       1 * time.Second,
    }

    log.Println("Starting server", address)
    fmt.Println("Go to localhost:" + appPort + " to view application")

    log.Fatal(srv.ListenAndServe())

}
enter fullscreen mode

exit fullscreen mode

So, let’s get our server up and running:

In your project root, run the code by running this command:

go run main.go
enter fullscreen mode

exit fullscreen mode

this much only! We have successfully set up a simple API for prisoners to get their matches. how awesome?!

This means that any system can connect to it and use the information of the database in its means, style, etc.

Let us take that claim further. Make sure you have your Redis database instance and spring up RedisInsight to see what’s going on.

1) Consider a simple use case: MR Peter, who was once a prisoner, wants to announce his astonishing profile, showing that he has a lot of qualities and hopes that someone will accept and love him will be who he is. With our API, MR Peter can meet this need through a mobile client, an IoT device, your browser, etc.

curl -X POST localhost :9000 /api/v1/criteria
   -H "Content-Type: application/json"
   -d ' {
    "id":DATIN00025,
    "name":"Mr Peter Griffin",
    "height":6.4,
    "weight":120,
    "sexualOrientation":"straight",
    "age":45,
    "casualInterest":{
       "entertainment":true,
       "gym":false,
       "jewellries":false,
       "oneNight":false,
       "restaurant":true,
       "swimming":false,
       "travel":false,
       "yolo":true
    },
    "seriousInterest":{
       "career":false,
       "children ":true,
       "communication":false,
       "humanity":false,
       "investment":false,
       "marriage":false,
       "religion":false,
       "politics":true
    }
 }
‘
enter fullscreen mode

exit fullscreen mode

2) Another use case. Mrs. Lois wants to connect with someone who can understand her, who understands what it means to be behind bars because she has been in that situation too. She needs that man with dripping masculinity and vigor. Calling our API through his client as seen below does the magic to show him all the men available for him to choose from:

curl localhost :9000 /api/v1/criteria
   -H "Accept: application/json"
enter fullscreen mode

exit fullscreen mode

3) Miss Meg wants both sides of the coin on a casual level. No strong wires connected. She probably wants to know if a particular sweet meets that need. She first looks at Peter Griffin’s profile and wants to determine if he has found some sudden or serious tremors. Miss Meg presses a button on her mobile, and all her mobile has to do is call our non-implemented Show Dimensions endpoint for Mr. Peter Griffin, to see if she encounters a similar call. Match is like:

curl localhost :9000 /api/v1/criteria/ DATIN00025/dimension
   -H "Accept: application/json"
enter fullscreen mode

exit fullscreen mode

Like these matches, Mr. Peter, Mrs. Lois and Miss Meg are also sorted. So many more using this amazing API we have created!

this much only! We are able to easily find the right match! If this is not magic then what is?

Now ask yourself. Should you be a RedisJSON enthusiast?🤔

As we travel through other series, we’ll continue to move forward with our idea of ​​helping returning prisoners find their true love, while discovering the other good sides of Redis like RediSearch in the next episode. And perhaps someday, it will be a means for them to re-engage in society faster and better. See you in the next series.

Something great for you! If you liked this article, click the upvote icon, and show some love too. Show that we already share some interest ️. Maybe we should date. When the total number of upvotes is reached, should I say 200? I will also share the full source code on Github.

Alright, love birds❤️! enjoy! Upvote❤️! Comment! It will go a long way.

Comment in particular. Let’s put some thought into this code. I know you have some great ideas out there.

This post is in collaboration with Redis,

You can see the following references for ideas:

[Image Credits: Photo by Marcus Ganahl on Unsplash]

Leave a Comment