AdvPL - Integrating with Redis
Tags: redis, advpl
Published: 2024-05-14 13:47:17

Hey guys 👋🏻

In today's article I will show you how to integrate AdvPL with Redis.

**What is Redis?**

Redis is a data store in key and value format provided by AWS. Its difference is related to the fact that it stores this information in the RAM memory of the server machine, making searching for your data much more agile than a conventional database. And contrary to what many think, even though we are talking about volatile data, it is possible to create snapshots with Redis to save the state of information saved in memory up to a certain moment.

**Why should I use Redis?**

Imagine that you maintain a blog that has more than 100,000 simultaneous hits per day, and you want to make this information available in a more agile way for your users. With Redis you can store blog content in a type of cache, and from that moment on, instead of users having to wait for your API to return the data, only the first request must wait for the process to complete. Other users will have data returned from caching done with Redis. See an illustration below:

| ![Image that demonstrates the flow of a caching strategy used in an API with Redis](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sadrxd1k1lwch0fab3wr.png) | 
|:--:| 
| Reference: https://www.linkedin.com/pulse/using-redis-caching-api-data-saral-saxena/ |

Note that in this model the conventional flow is followed, where the request made by the client goes to the API route, and this in turn is responsible for talking to the database to return what was requested. In the data return process we have an additional step, where the content found by the API is stored in Redis at the moment the information is returned to the first requester. But what is being stored in Redis? Nothing more or less than JSON, after all, as we said previously, it is a key and value store.

See how in this second example the same process becomes leaner. This is because it is no longer necessary to go to the API to get the information, since all the data necessary for the user is cached in Redis.

**Usage in AdvPL**

Below I leave you an example that I created of an API that lists films for a user. Since, for the first request made, the data will be returned by the bank and subsequent requests by Redis.

```clipper
#INCLUDE "TOTVS.CH"
#INCLUDE "RESTFUL.CH"
 
/*/{Protheus.doc} IBGENA01
 
Description of Function...
 
@type class
@author Wallace
@since 14/02/2022
 
/*/
WSRESTFUL IBGENA01 DESCRIPTION "List Movies using Redis (Caching Strategy)"
 
    WSMETHOD GET DESCRIPTION "List Movies using Redis" WSSYNTAX "/"
 
END WSRESTFUL
 
/*/{Protheus.doc} IBGENA01
 
GET method responsible for listing the movies, sometimes coming from the bank, sometimes coming from Redis
 
@type method
@author Wallace
@since 14/02/2022
 
/*/
WSMETHOD GET WSSERVICE IBGENA01
    Local lRedisConnected   := .F.
    Local cMoviesJson       := ""
    Local cKey              := ""
    Private oRedisClient    := Nil
    Private cAlias          := getNextAlias()
 
    lRedisConnected := connectRedis()
 
    If lRedisConnected
        cKey        := "movies"
        cMoviesJson := getRedis( cKey )
 
        If Empty( cMoviesJson )
            listMoviesDatabase()
            setMoviesInJson( @cMoviesJson )
            setExRedis( cKey, cMoviesJson, 20 )
        EndIf
 
        closeConnections()
    EndIf
 
    ::setResponse( cMoviesJson )
 
Return .T.
 
/*
    Connect to Redis
*/
Static Function connectRedis()
    Local cHost         := "localhost"
    Local nPort         := 6379
    Local lIsConnected  := .F.
 
    oRedisClient := tRedisClient():New()
    oRedisClient:connect( cHost, nPort )
   
    If oRedisClient:lConnected
        lIsConnected := .T.
    EndIf
     
Return lIsConnected
 
/*
    List movies coming from database
*/
Static Function listMoviesDatabase()
    Local cSQL      := ""
 
    cSQL := "SELECT                             " + CRLF
    cSQL += "   ZZ4_CODIGO                      " + CRLF
    cSQL += "   , ZZ4_NOME                      " + CRLF
    cSQL += "   , ZZ4_GENERO                    " + CRLF
    cSQL += "FROM                               " + CRLF
    cSQL += "   " + RetSQLName("ZZ4") + " ZZ4   " + CRLF
    cSQL += "WHERE                              " + CRLF
    cSQL += "   1 = 1                           " + CRLF
    cSQL += "   AND D_E_L_E_T_ = ' '            " + CRLF
 
    plsQuery( cSQL, cAlias )
 
Return
 
/*
    Stores movies in a JSON structure
*/
Static Function setMoviesInJson( cMoviesJson )
    Local oMoviesJson   := JsonObject():new()
    Local aListMovies   := {}
    Local nIndex        := 1
    Default cMoviesJson := ""
 
    While !( cAlias )->( EoF() )
        aAdd( aListMovies, JsonObject():new() )
 
        nIndex := Len( aListMovies )
 
        aListMovies[nIndex]['code']     := allTrim( ( cAlias )->ZZ4_CODIGO )
        aListMovies[nIndex]['name']     := allTrim( ( cAlias )->ZZ4_NOME )
        aListMovies[nIndex]['gender']   := allTrim( ( cAlias )->ZZ4_GENERO )
 
        ( cAlias )->( dbSkip() )
    EndDo
 
    oMoviesJson:set( aListMovies )
    cMoviesJson := oMoviesJson:toJSON()
 
Return
 
/*
    Find content into key in the Redis
*/
Static Function getRedis( cKey )
    Local cCommand      := ""
    Local cValue        := ""
    Local xValueReturn  := Nil
    Default cKey        := ""
     
    cCommand := "GET " + cKey
    oRedisClient:exec( cCommand, @xValueReturn )
 
    If oRedisClient:lOk
        cValue := xValueReturn
 
        If valType( xValueReturn ) != 'C'
            cValue := cValToChar( xValueReturn )
        EndIf
    EndIf
 
Return cValue
 
/*
    Storage the content in the key into Redis
*/
Static Function setExRedis( cKey, cValue, nExpiresInSeconds )
    Local xValueReturn  := Nil
    Default cKey        := ""
    Default cValue      := ""
    Default nExpiresIn  := 0
 
    cCommand := "SETEX " + cKey + " " + cValToChar( nExpiresInSeconds ) + " '" + cValue + "' "
    oRedisClient:exec( cCommand, @xValueReturn )
 
    If !oRedisClient:lOk
        conOut("Failed to saved JSON in the key of Redis")
    EndIf
 
Return 
 
/*
    Closed open connections
*/
Static Function closeConnections()
    oRedisClient:disconnect()
 
    If Select(cAlias) > 0
        (cAlias)->( dbCloseArea() )
    EndIf
     
Return
```

In the example above, we have an API that lists all movies in a table. Note that on line 39, after connecting to Redis, we use a function to check if there is any content within the “movies” key. If the return is empty, it means that we are making the first request, and therefore we must store the returned JSON within Redis. For all content saved in Redis we spend the time that its content will be available before expiring (see the setRedisEx function).

Below we illustrate the difference in performance between a request returned by the bank and another by Redis:

| ![Image that shows a request made to an API through Postman fetching data from the DB](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fh8e0na7ubdnx6xxs1yw.png) | 
|:--:| 
| Request made to the BD |

| ![Image that shows a request made to an API through Postman fetching data from the Redis](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jfvy2ab0ioj88rajtx2k.png) | 
|:--:| 
| Request made to the Redis |

Notice the difference between each request: in the first image, the entire content was returned by the DB after 891ms, while in the request made by Redis the same content was returned after 95ms. This is just one of several uses that Redis can provide us.

If you want to delve deeper into the subject, here are the links used as references when putting together this post:

[Redis](https://redis.io/commands)
[tRedisClient](https://tdn.totvs.com/display/tec/Classe+tRedisClient)

_To use Redis with Advpl, you will need to install the dll **rdwincli.dll**, available for download from the TDN link provided above, in the Observations section._

The objective of this post was to bring alternatives to improve data listing performance using the caching concept.

I hope you enjoyed.

To the next!