How to Build a Real Time A/B Testing Tool Using Kotlin and Redis

ajeetraina

Ajeet Singh Raina

Posted on January 14, 2022

How to Build a Real Time A/B Testing Tool Using Kotlin and Redis

A/B testing has become an indispensable asset to marketers and website owners competing in today’s digital economy. It allows users to test existing ideas, experiment with new ones and highlight what works and what doesn’t.

Without this information, trying to optimize web pages can be a frustrating and time-consuming process. But unfortunately, A/B testing software can be expensive, preventing many from having access to its benefits.

But with Redis, programmer Thiago Camargo was able to remove these barriers by creating an open A/B testing tool that can operate in real time. Redis’ powerful data processing features allow users to experiment with variables of their choosing and discover the most effective ways to optimize their web pages.

Let’s take at how Thiago was able to put this application together. But before we go any further, we’d like to point out that we have a wide variety of innovative applications for you to discover on the Redis Launchpad.

So make sure to have a browse after this post!

  • What will you build?
  • What will you need?
  • Architecture
  • Getting started
  • How it works

What will you build?

You’ll build a powerful yet simple A/B testing tool that’s scalable and operates in real time. Below we’ll show you how to build this application from the bottom up, highlighting what components are required along with their functionality.

What will you need?

  • Kotlin: used as an open-sourced programming language that’s also used by Google to develop Android apps.
  • Gradle: used as a build-automation tool for multi-language software development.
  • Lettuce: builds non-blocking reactive applications
  • RedisGraph: sparses matrices to represent the adjacency matrix in graphs and linear algebra to query the graph
  • RedisTimeSeries: allows you to ingest and query millions of samples and events with hyper-efficiency.
  • RedisJSON: allows storing, updating, and fetching JSON values from Redis keys

Architecture

Image description

  • An experiment is created on Swagger based on the variables you want to test on your website. This user has chosen to test what impact different colours have on sales.
  • Trigger events determine the users that will automatically be enrolled in the experiment.
  • Users that sign up for a subscription will be recorded as a trigger event (a conversion). The colour of where each trigger event will also be recorded to measure results.

What is A/B testing?

Also known as split testing, A/B testing refers to a methodical experimentation process where two or more versions of a variable are shown to website visitors to determine which one has a greater impact on conversions.

It’s designed to add clarity on how you can position your brand, products, and services more effectively in the marketplace.

Let’s say for example you have a product on your website you’re trying to sell. One variable you could experiment with would be the call to action (CTA) button next to the product. There are a number of different ways you can A/B test the CTA, including:

  1. Placement
  2. Design
  3. Colour
  4. Size
  5. Copywriting

By split testing these variables, you’ll discover which version of your CTA is going to be most optimal to driving conversions.

Getting started

Prerequisites

  • Docker
  • Install Gradle 7.2
  • Maven 3.8.1
  • Install OpenJDK
  • Swagger API documentation

Step 1: Clone the repository

$ git clone https://github.com/redis-developer/banda
Enter fullscreen mode Exit fullscreen mode

Step 2. Performing the build task

$ gradle task
Enter fullscreen mode Exit fullscreen mode
$ gradle wrapper
Enter fullscreen mode Exit fullscreen mode
$ ./gradlew clean build
Enter fullscreen mode Exit fullscreen mode

Step 3: Set up Redis modules

If you’re using Docker desktop, ensure that the file sharing option is enabled for the volume mount. Use the code below to set up Redis:

 docker run \
  -p 6379:6379 \
  -v /home/user/redis_data:/data \
  -it \
  redislabs/redismod \
  --loadmodule /usr/lib/redis/modules/rejson.so \
  --loadmodule /usr/lib/redis/modules/redistimeseries.so \
  --loadmodule /usr/lib/redis/modules/redisgraph.so \
  --dir /data
Enter fullscreen mode Exit fullscreen mode

Step 4. Running the application

Use the code below to start up the docker:

docker run -p 8080:8080 -e BASE_URL -e PORT -e REDIS_URL com.xmppjingle/bjomeliga-docker-boot -ti
Enter fullscreen mode Exit fullscreen mode
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.1.RELEASE)

2021-10-03 13:19:15.856  INFO 1 --- [           main] c.x.bjomeliga.BjomeligaApplicationKt     : Starting BjomeligaApplicationKt on b9002f934d84 with PID 1 (/bjomeliga-0.0.1-SNAPSHOT.jar started by root in /)
2021-10-03 13:19:15.863  INFO 1 --- [           main] c.x.bjomeliga.BjomeligaApplicationKt     : No active profile set, falling back to default profiles: default

2021-10-03 13:19:18.253  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
Enter fullscreen mode Exit fullscreen mode

How it works

Experiments API

Here we’re going to show you how to create scalable A/B testing experiments with trigger-based weighted enrollment.

How to create the experiment

In this step, we’ll show you how to create an experiment that allows you to A/B test different variables. We’re going to put ourselves in the shoes of a marketer looking to split test the different screen colors to see what impact they have on sales.

To get started, use the code below.

curl --location --request POST 'localhost:8080/experiment' \
--header 'Content-Type: application/json' \
--data-raw '{
  "goalIds": [
    "purchase"
  ],
  "id": "subscription1",
  "triggerEventId": "user-plan-screen-view",
  "variants": {
    "variants": [
      {
        "id": "red",
        "params": {
          "additionalProp1": "string",
          "additionalProp2": "string",
          "additionalProp3": "string"
        },
        "weight": 20
      },
      {
        "id": "blue",
        "params": {
          "additionalProp1": "string",
          "additionalProp2": "string",
          "additionalProp3": "string"
        },
        "weight": 80
      }

    ]
  }
}'
Enter fullscreen mode Exit fullscreen mode

It’s worth noting that we changed the ‘goalIds’ at the top to ‘purchase’ to measure the number of sales made from each screen color. Below that, we’ve made the ‘id’ of the experiment ‘subscription1.’

To determine the trigger event, we inserted ‘user-plan-screen-view.’ This means that when an event is triggered, the user will automatically be enrolled in the experiment.

Next, we identified the variants as ‘red’ and ‘blue’ since these are the two variables we’ll be A/B testing.

How to retrieve the experiment

curl --location --request GET 'localhost:8080/experiment/subscription1'
Enter fullscreen mode Exit fullscreen mode

Response.

{{
  "id": "subscription1",
  "variants": {
    "variants": [
      {
        "id": "red",
        "weight": 20,
        "params": {
          "additionalProp1": "string",
          "additionalProp2": "string",
          "additionalProp3": "string"
        }
      },
      {
        "id": "blue",
        "weight": 80,
        "params": {
          "additionalProp1": "string",
          "additionalProp2": "string",
          "additionalProp3": "string"
        }
      }
    ]
  },
  "triggerEventId": "user-plan-screen-view",
  "goalIds": [
    "purchase"
  ]
}
Enter fullscreen mode Exit fullscreen mode

Remote Config API

Here we’ll show you how to create a dynamic and scalable remote client configuration service (Firebase Replacement).

How to update the remote config

 curl --location --request POST 'localhost:8080/config' \
--header 'Content-Type: application/json' \
--data-raw '{
  "params": {
    "additionalProp1": "red",
    "additionalProp2": "green",
    "additionalProp3": "blue"
  },
  "userId": "pixel"
}'
view rawHow to update the remote config hosted with ❤ by GitHub
How to retrieve the remote config
curl --location --request GET 'localhost:8080/config/pixel'
Enter fullscreen mode Exit fullscreen mode

Response

{
  "userId": "pixel",
  "params": {
    "additionalProp1": "red",
    "additionalProp2": "green",
    "additionalProp3": "blue"
  }
}
Enter fullscreen mode Exit fullscreen mode

Event API

This step involves Timeseries Event Indexing.

How to push event

curl --location --request POST 'localhost:8080/events' \
--header 'Content-Type: application/json' \
--data-raw '{
  "category": "generic",
  "emitterId": "rickAstley",
  "id": "Rickrolling",
  "labels": {
    "channel": "youtube",
    "prankedBy": "steveTyler"
  },
  "retention": 900000,
  "type": "prank",
  "value": 100
}'
Enter fullscreen mode Exit fullscreen mode

Summary API – Generic Summary Service

Here is a simple and flexible summary service that’s capable of keeping and maintaining summaries of multiple types of applications. These include: game scoreboards, product ratings, user ratings, incremental metrics and many more.

Updating a summary

curl --location --request POST 'localhost:8080/summary' \
--header 'Content-Type: application/json' \
--data-raw '{
    "id": "abc",
    "transactionId": "3rd",
    "metrics": [
        {
            "id": "abc",
            "value": 2
        },
        {
            "id": "bcd",
            "value": 4
        },
           {
            "id": "fff",
            "value": 1
        }
    ]
}'
Enter fullscreen mode Exit fullscreen mode

Getting a Summary

curl --location --request GET 'localhost:8080/summary/abc'
Enter fullscreen mode Exit fullscreen mode

Response:

{
    "id": "abc",
    "metrics": [
        {
            "id": "abc",
            "value": 6,
            "count": 3,
            "transactionIds": [
                "3rd",
                "2nd",
                "1st"
            ]
        },
        {
            "id": "bcd",
            "value": 12,
            "count": 3,
            "transactionIds": [
                "3rd",
                "2nd",
                "1st"
            ]
        },
        {
            "id": "fff",
            "value": 1,
            "count": 1,
            "transactionIds": [
                "3rd"
            ]
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Drawer API – Generic Key/Value Service

Flexible Property Storage

Updating a Drawer

curl --location --request POST 'localhost:8080/drawer/abc' \
--header 'Content-Type: application/json' \
--data-raw '{
    "id": "abc",
    "values": {
        "google" : "123",
        "fb": "abc"
        }
}'
Enter fullscreen mode Exit fullscreen mode

Getting a Drawer

curl --location --request GET 'localhost:8080/drawer/abc'
Enter fullscreen mode Exit fullscreen mode

Response:

{
    "id": "abc",
    "values": {
        "google": "123",
        "fb": "abc",
        "insta": "1222"
    }
}
Enter fullscreen mode Exit fullscreen mode

Some Used Redis Queries

Most of the commands used below are implemented using Lettuce

Redis Command Annotation.

Graph

  • enrollEmitterOnExperiment
GRAPH.QUERY experiments :cmd
Enter fullscreen mode Exit fullscreen mode
  • fetchParticipantsOnExperiment
GRAPH.QUERY MATCH  (u:User)-[:participants]->(:Exp {id: '$experimentId'}) RETURN COUNT(u.id)
Enter fullscreen mode Exit fullscreen mode
  • graphQuery
GRAPH.QUERY MERGE (:User {id: '$emitterId' })-[:participants]->(:Exp {id: '$experimentId' }
Enter fullscreen mode Exit fullscreen mode

TimeSeries

  • pushEvent
TS.ADD :id * :value RETENTION :retention LABELS category :category type :type :labels
Enter fullscreen mode Exit fullscreen mode

JSON

  • setObject
JSON.SET :id . :json")
Enter fullscreen mode Exit fullscreen mode
  • getObject
JSON.GET :id .")
Enter fullscreen mode Exit fullscreen mode
  • setPathValue
JSON.SET :id :path :value
Enter fullscreen mode Exit fullscreen mode
  • getPathValue
JSON.GET :id :path
Enter fullscreen mode Exit fullscreen mode
  • Core

  • HSET

  • HGET / HGETALL

  • HEXISTS

Conclusion: Making A/B Testing Accessible to Everyone

A/B testing has become integral to any marketer competing in today’s digital playground. But obtaining this software can be expensive, forcing many to operate with a sub-optimal website. And to compound this even further, creating your split-testing software has its own programming difficulties.

To create such an application requires a versatile and powerful database capable of transmitting data efficiently between components. Yet despite these obstacles, Redis removed all of these obstacles.

From just by using Redis on your laptop, you can A/B test any variable on your website and bridge yourself closer to your target market. If you want to discover more about how this application was made, then make sure to watch this YouTube Video.

Yet despite these demands, Redis’ advanced data processing capabilities made data transmission between components both hyper-efficient and reliable, creating a highly responsive application.

This meant no lags, no delays, and no causes of friction between the user and the application. From just by using Redis on your laptop, you can A/B test any variable on your website and bridge yourself closer to your target market.

If you want to discover more about how this application was made, then make sure to watch this YouTube Video.

If you’ve enjoyed this post then we also have many more for you to discover on the Redis Launchpad. From creating real-time vehicle tracking systems to building powerful drone systems to protect crop insurers in developing countries, Redis has been leveraged by programmers all over the world to improve everyday lives.

Visit Redis LaunchPad

Image description

Special Thanks to Thiago Camargo for creating this app. If you enjoyed this application then make sure to visit Thiago’s GitHub page to keep up to date with all of the projects he’s been involved in.

This post was originally posted under this link.

💖 💪 🙅 🚩
ajeetraina
Ajeet Singh Raina

Posted on January 14, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related