REST with Asynchronous Jersey and RXJava – Part 2

By | November 25, 2016
Reading Time: 7 minutes

In this series of articles I will implement a RESTful web service using Spring Boot and Jersey. I will then modify the service as to use asynchronous server-side request processing and RXJava. Load tests will be made using Gatling before and after the modifications, in order to determine any improvements.

If you haven’t read part 1 yet, I do recommend it.

In this part two I will show how to add a Gatling load test that is run with Maven to the example program. The technique shown in this article can be used to add Gatling load tests to any Maven based project (as far as I know); be it Java, Scala or Groovy etc.

Heavy load. Photo by Government of Thailand CC-BY-2.0.

Heavy load. Photo by Government of Thailand CC-BY-2.0.

Gatling Load Tests

Gatling load tests are added to the example program using the following steps:

  • Add Gatling dependency information to the project pom.xml file.
  • Add and configure the Gatling Maven plug-in configuration to the project pom.xml file.
  • Add the payload-files to be used in the load test.
  • Implement the Gatling load test.

The result will make it possible to run the Gatling load tests using Maven; either locally, for example in a terminal window, or on a build server, such as Jenkins, using the following Maven command:

mvn gatling:test

Add Gatling Dependency Information

One dependency needs to be added in order to incorporate Gatling into the example program. In addition, I have used two properties to specify version numbers – the gatling.version property is for the Gatling dependency, the gatling-plugin.version is the version of the Gatling Maven plug-in.

  • Add the two Gatling-related properties shown here in the <properties> element of the pom.xml file:
        <properties>
            ...
            <gatling.version>2.2.2</gatling.version>
            <gatling-plugin.version>2.2.0</gatling-plugin.version>
        </properties>
  • Add the Gatling dependency that enables running load tests in the example program in the <dependencies> element in the pom.xml file:
            <dependency>
                <groupId>io.gatling.highcharts</groupId>
                <artifactId>gatling-charts-highcharts</artifactId>
                <version>${gatling.version}</version>
                <scope>test</scope>
            </dependency>

    Note that the Gatling dependency is in the test scope, since it will only be used for load tests.

Configuring the Gatling Maven Plug-In

The Gatling Maven plug-in compiles and runs the Gatling load test(s) in the project. The use-case in this example is quite simple and the plug-in has more features than shown here. Please refer to the Gatling documentation for more information.

  • Add the plug-in configuration shown below in the <plugins> element in the <build> element in the pom.xml file:
    ...
    <build>
        <plugins>
            ...
            <!--
                Gatling Maven plugin that runs the load-simulation.
                To perform a load-test, first start the application 
                (RestExampleApplication in this case) and then run the load-tests using:
                mvn gatling:test.
            -->
            <plugin>
                <groupId>io.gatling</groupId>
                <artifactId>gatling-maven-plugin</artifactId>
                <version>${gatling-plugin.version}</version>
                <configuration>
                    <simulationsFolder>${project.basedir}/src/test/gatling</simulationsFolder>
                    <bodiesFolder>${project.basedir}/src/test/resources/gatlingpayloads</bodiesFolder>
                </configuration>
            </plugin>
        </plugins>
    </build>
    ...

Note that:

  • In the plug-in configuration there is an element named <simulationsFolder>.
    The location specified in this element will contain the Gatling load test simulation source code. As with all Gatling load tests, these are implemented in Scala using the Gatling DSL.
  • In the plug-in configuration there is an element named <bodiesFolder>.
    This element specifies the location of the directory that holds the files used by the Gatling load tests that contain payload bodies.

Add Load Test Payload Files

The load test will, among other things we soon will see, create circles, rectangles and drawings by sending POST requests to the REST service. Such POST requests need to have a payload and in this step we are going to create three payload files to be used in the load test.

  • In src/test/resources in the example program project, create a directory named “gatlingpayloads”.
  • In the “gatlingpayloads” directory, create a file named “createCircle.json” with the following contents, which is the JSON representation of a circle entity:
    {
      "shapeType": ".Circle",
      "colour": "Colour-${RandomNumber}",
      "position": {
        "x": ${RandomNumber}.0,
        "y": ${RandomNumber}.0
      },
      "radius": ${RandomNumber}
    }
  • In the same directory, create another file named “createRectangle.json” with the JSON representation of a rectangle entity:
    {
      "shapeType": ".Rectangle",
      "colour": "Colour-${RandomNumber}",
      "position": {
        "x": ${RandomNumber}.0,
        "y": ${RandomNumber}.0
      },
      "height": ${RandomNumber},
      "width": ${RandomNumber}
    }
  • Finally, in the same directory, create a file named “createDrawing.json” with the following contents:
    {
      "name": "DrawingName${RandomNumber}",
      "creationDate": 1478725227674,
      "shapes": [
        {
            "shapeType": ".Circle",
            "colour": "Colour-${RandomNumber}",
            "position": {
              "x": ${RandomNumber}.0,
              "y": ${RandomNumber}.0
            },
            "radius": ${RandomNumber}
        },
        {
          "shapeType": ".Rectangle",
          "colour": "Colour-${RandomNumber}",
          "position": {
            "x": ${RandomNumber}.0,
            "y": ${RandomNumber}.0
          },
          "height": ${RandomNumber},
          "width": ${RandomNumber}
        },
        {
          "shapeType": ".Circle",
          "colour": "Colour-${RandomNumber}",
          "position": {
            "x": ${RandomNumber}.0,
            "y": ${RandomNumber}.0
          },
          "radius": ${RandomNumber}
        }
      ]
    }

Note that:

  • None of the JSON representations contain an entity id.
    These JSON representations are to be sent using POST requests in order to create new entities and as such may not contain entity ids.
  • There are placeholders of the type ${RandomNumber} in the JSON data.
    Such placeholders are written using the Gatling Expression Language and allow for data to be dynamically inserted into URLs or payload data during the execution of the load test.
  • The circle and rectangle JSON representation will result in one single entity being created while the drawing JSON representation will result in one drawing and three shape entities being created.

Implement the Gatling Load Test

Next up is implementing the Gatling load test. For those unfamiliar with Gatling, the code below is written in the Scala programming language. For a more thorough introduction to Gatling, please refer to my four-part article series Introduction to Load Testing with Gatling.

  • In the src/test directory in the example project, create a directory named “gatling”.
  • In the “gatling” directory, create a file named GatlingLoadTest.scala with the following contents:
    import io.gatling.core.scenario.Simulation
    import io.gatling.core.Predef._
    import io.gatling.http.Predef._
    import scala.concurrent.duration._
    import scala.util.Random
    import java.net.HttpURLConnection
    
    /**
      * Gatling load test of the REST service.
      * The load tests consists of requests that creates different types of entities and
      * requests that retrieve all entities of a type. These requests are issued in
      * a round-robin fashion.
      *
      * @author Ivan Krizsan
      */
    class GatlingLoadTest extends Simulation {
        /* Simulation timing and load parameters. */
        val rampUpTimeSecs = 5
        val testTimeSecs = 30
        val noOfUsers = 300
        val noOfRequestPerSeconds = 1500
    
        val baseURL = "http://localhost:8080"
        val circlesResourcePath = "/circles"
        val rectanglesResourcePath = "/rectangles"
        val drawingsResourcePath = "/drawings"
    
        /* Request that creates one rectangle and verifies the outcome. */
        object CreateRectangle {
            val request = exec(http("CreateRectangle")
                .post(rectanglesResourcePath)
                .body(ElFileBody("createRectangle.json")).asJSON
                .check(status.is(HttpURLConnection.HTTP_OK)))
        }
    
        /* Request that creates one rectangle and verifies the outcome. */
        object CreateCircle {
            val request = exec(http("CreateCirlce")
                .post(circlesResourcePath)
                .body(ElFileBody("createCircle.json")).asJSON
                .check(status.is(HttpURLConnection.HTTP_OK)))
        }
    
        /* Request that creates one drawing that contains a number of shapes and verifies the outcome. */
        object CreateDrawing {
            val request = exec(http("CreateDrawing")
                .post(drawingsResourcePath)
                .body(ElFileBody("createDrawing.json")).asJSON
                .check(status.is(HttpURLConnection.HTTP_OK)))
        }
    
        /* Request that retrieves all rectangles and verifies the outcome. */
        object RetrieveAllRectangles {
            val request = exec(http("RetrieveAllRectangles")
                .get(rectanglesResourcePath)
                .check(status.is(HttpURLConnection.HTTP_OK)))
        }
    
        /* Request that retrieves all circles and verifies the outcome. */
        object RetrieveAllCircles {
            val request = exec(http("RetrieveAllCircles")
                .get(circlesResourcePath)
                .check(status.is(HttpURLConnection.HTTP_OK)))
        }
    
        /* Request that retrieves all drawings and verifies the outcome. */
        object RetrieveAllDrawings {
            val request = exec(http("RetrieveAllDrawings")
                .get(drawingsResourcePath)
                .check(status.is(HttpURLConnection.HTTP_OK)))
        }
    
        /**
          * Random number generator that generates random numbers in the range 0-1000 that
          * are made available in the load-tests.
          */
        val randomNumberGenerator = Iterator.continually(
            Map("RandomNumber" -> Random.nextInt(1000))
        )
    
        val httpProtocol = http
            .baseURL(baseURL)
            .acceptHeader("application/json")
            .userAgentHeader("Gatling")
    
        val testScenario = scenario("LoadTest")
            .feed(randomNumberGenerator)
            .during(testTimeSecs) {
                exec(
                    roundRobinSwitch(
                        CreateRectangle.request,
                        CreateCircle.request,
                        CreateDrawing.request,
                        RetrieveAllRectangles.request,
                        RetrieveAllCircles.request,
                        RetrieveAllDrawings.request
                    )
                )
            }
    
        setUp(
            testScenario
                .inject(atOnceUsers(noOfUsers)))
            .throttle(
                reachRps(noOfRequestPerSeconds) in (rampUpTimeSecs seconds),
                holdFor(testTimeSecs seconds))
            .protocols(httpProtocol)
    }

Note that:

  • First in the load test class there are some numeric constants defined.
    These allow us to tweak some timing and load parameters of the test, for instance the duration of the load test, the number of simulated users and the number of requests per second.
  • After the numeric constants there are some string constants defined.
    These constants are used to construct the different URLs when sending HTTP requests to the various REST resources.
  • There is an object named CreateRectangle which contains a constant named request.
    This is a definition of the procedure of sending a request to the REST service that creates a rectangle and verifying that the HTTP request succeeded, i.e. that HTTP status 200 was returned.
  • Similar to the CreateRectangle object, there are also CreateCircle and CreateDrawing objects.
    These objects work on circle and drawing entities respectively.
  • In a manner similar to the create requests, there are also objects RetrieveAllRectangles, RetrieveAllCircles and RetrieveAllDrawings.
    These objects define the procedure of retrieving all rectangles, circles and drawings respectively from the REST service and verifying that the request was successful.
  • There is a constant named randomNumberGenerator.
    This is a Gatling feeder, which creates data for, for instance, the placeholders we have used in the payload files. We had the placeholder ${RandomNumber} and recognize the key “RandomNumber” used in the feeder when inserting a random number into a map.
  • Next up is the more or less standard httpProtocol definition which specifies the base URL, the value of the Accept and the User-Agent headers to be used when sending requests.
  • In the test scenario testScenario there are several things happening.
    First the feeder to be used in the scenario is specified.
    Second, the duration of the scenario is specified.
    Third the execution of the scenario is defined as a round-robin distribution over the listed requests.
  • Last, the set up of the load-simulation is defined, specifying the number of users, ramp-up and duration time of the load-simulation and the protocol to use.

Running the Load Test

With the load test code and the payload-files in place, we should now be ready to run the load test. I have developed the example application in an IDE and will run it from within the IDE but running the load-simulation may involve having to open a terminal window if your IDE does not support running Maven goals.

  • Open a terminal window.
  • Go to the root of the example program project.
    This is the directory with the pom.xml file in it.
  • In the terminal window, run the command “mvn clean package”.
    This will clean the project, including any Gatling reports that may exist, and build it. All tests will be run but the load test will not run.
  • Start the example application.
    In my IDE I right-click on the RestExampleApplication.java file and select “Run RestExampleApplication”.
  • Back in the terminal window, run the command “mvn gatling:test”.
    Running the load test will take approximately 30 seconds and the result output in the console should be a “BUILD SUCCESS”.
  • In the IDE or in your operating system, open the target/gatling directory in the example application’s root directory.
    In my IDE I see the report generated by Gatling in a directory named “gatlingloadtest-1479968884010”:

    Gatling report in the target directory of the example project.

    Gatling report in the target directory of the example project.

  • Open the index.html file with a browser.
  • You should now be looking at the load test report.
    In my case, the response distribution graph looks like this for the current version of the example-program:

    REST example - response time distribution Gatling report.

    REST example – response time distribution Gatling report.

In the next part of this series we will tweak the parameters of the load test in order to exercise the REST service properly and then start modifying it.

Happy coding!

Leave a Reply

Your email address will not be published. Required fields are marked *