Category: Technology

November 17th, 2017 by Apostolos Benisis

Print This Post Print This Post
This article is about a very important question today’s innovators face when they develop new software products.

Namely, that of:

What are the appropriate technical skills, technology stack and architecture for the new product?”

This question is very hard to answer, because very often it is not clear what features a product should have. Needless to say, that sometimes it is not even clear what problem the company is trying to solve for its customers.

From startup to scaleup

Consider a development team that follows an iterative approach to test fundamental hypotheses about the product, the business model and the market.

Initially, during the startup phase, the team will attempt to build, test and adapt an MVP, in an iterative process. With each iteration, the team will improve the MVP and adapt it to reflect the feedback received during the tests.

Once the team verifies their fundamental hypotheses and validates the business model, the startup phase concludes and the scaleup phase starts. If the team disproofs or fails to verify their hypotheses, it will attempt to pivot the strategy to correct the course, formulate new fundamental hypotheses and reiterate.

By the time the team completes the MVP and validates the business model, the product may have gone through several fundamental changes and most likely already has many users. The next step is to scaleup and roll out the product to significantly more users and even enter different markets. At the same time the team should continue operating the product for the existing users.

Technology dilemmas

Following the above approach comes with contradictory expectations regarding the appropriate technical skills, architecture and technology stack. The reasons are:

• The problem to solve and the solution are not always clear from the start.
• The requirements are discovered gradually and they may change after each iteration.
• Speed is of essence. The hypotheses have to be verified ahead of the competition and before exhausting available funds.
• Pivoting the strategy may happen at any time and the technical requirements may dramatically change.
• To solve the problem, the team may need skills that are initially unknown.

To put it another way, the technology dilemmas can be described as:

“A startup needs a technology strategy that not only allows for fast delivery of value and supports growth, but can also fulfill unknown, diverse and possibly contradicting requirements”

To put the technology dilemmas in a familiar and tangible context, two examples, which are based on actual situations startups have encountered, will follow.

The e-commerce company

The first example is about an e-commerce company that operates in the B2C market.

Startup phase

Their product idea is great, but they have to be very fast in gaining a significant market share because competition looms. The team decides to go for a simple architectural pattern and a hosted solution on premise. The reasons for their choice are, that they are very familiar with these technology options and feel that this is the fastest way to build the product. Very soon the first version of the website is ready and the team continuously develops and tests their assumptions about the market.

Scaleup phase

After a couple of years, the user base in their home country has grown significantly in size and so has the code of the B2C web-based application. It is now time to grow even further, enter new countries and even the B2B2C markets.

Sadly, the team realizes that the application cannot handle the new traffic load and regularly crashes. To make things worse, implementing requirements for other countries and for the B2B2C market turns out to be very difficult. Their web-based application, a monolithic architecture of spaghetti code, cannot be easily scaled or extended.

The team now feels that they should have chosen a different approach to build the web-based application, but they would not have been as fast as it was required in the beginning. Now, their suggestion is to migrate the application to a modern micro-service architecture and to move it to an external IaaS/PaaS.

However, this will take much time and effort which they cannot afford because the competition is catching up.

The Industry 4.0 startup

The second example is about a startup that builds a SaaS product for the industry 4.0 market.

Startup phase

Their business model assumes that several thousands of customers and tens of thousands of users will work concurrently with the product.

The development team is excited and chooses a micro-service architecture for the product. Work begins and the team chooses to develop the product on the IaaS/PaaS of a major provider. The team is not familiar with these technologies. Progress is slow at the beginning, but they learn fast. With each iteration they incrementally improve their MVP. After several months of work, they begin to test with users and potential customers. Everything seems to go great and finally they are making good progress.

Unfortunately, after a while, the team realizes that their target customers are not willing to adopt a SaaS solution yet. There are good reasons to believe, that in the future, customers will accept a solution running on a third party cloud, but for now it is too early. The majority of the potential customers have concerns, because they plan to use the product with intellectual property and are not willing to allow this information to leave their own infrastructure.

Scaleup phase

The product team decides to pivot. Instead of offering the product as a service to thousands of customers and tens of thousands of users, the decision is to offer it “on premise” to a handful of big customers and later to a few hundred mid-sized customers. Subsequently, every installation must take place on location, as remote access is not always possible. Furthermore, because each installation is customer specific, it will have only a few hundreds of users at most.

The team is now facing a difficult technical challenge, because the requirements have fundamentally changed:

  • The selected architecture is too complex for the new business case.
  • The new requirements expect to deploy and update the application to multiple customers with diverse infrastructure. Sadly, the technology choices the team made bind them to a specific IaaS/PaaS provider.

Re-architecting the application is possible, but it will take much effort and the team still needs to continue developing new features for existing customers

Modeling the technology dilemmas

Modeling the technology dilemmas can help to better understand their dynamics and develop strategies to tackle the associated challenges.

(1) Growth phases

The value a new product creates, resembles an S-Curve. Initially, as the team tests various hypotheses and builds the MVP, the value creation is relatively flat. Once the right strategy is found and the business model is validated, the value creation potential assumes exponential growth. To capitalize on this potential, the scaleup phase starts. Ultimately, as pressure from competition increases and the market saturates, growth flattens out again. If it was possible to choose the perfect technical skills, technology stack and architecture, the development team would be able to develop the MVP and validate the business model fast during the startup phase. Furthermore, development could smoothly transition to a scaleup phase and the team would deliver work that optimally capitalizes on the exponential growth potential.

The imperfect technology match

(2) Imperfect technology match

In reality, because of the uncertainty about the requirements and pressure to deliver, the technology choices cannot always satisfy the contradicting expectations for fast development needed to quickly validate the strategy and at the same time the ability to scaleup.

Constantly changing the MVP to reflect feedback from the market and pressure to deliver, have most likely led to quick fixes, shortcuts and poor engineering choices. In other words buildup of technical debt. In addition to the above, pivoting may have even set a totally new direction for the product, which may not even be compatible with the current technical solution.

The result of the above situations is, that product development cannot keep up to the demands that derive from the exponential growth potential. The technology debt constrains growth and value creation flattens. The business encounters difficulties scaling-up.

The perfect technology pivot for scaling up

(3) Pivoting the technology to scaleup

To avoid a premature plateau in growth, the development team will have to perform a technology shift/pivot. What type of pivot is suitable (e.g. re-engineer, migrate or rewrite) depends on the specific situation the startup faces. However, the optimal point in time to perform the pivot is “fixed” and independent of the situation. It is the moment when the team validates the business model i.e. when it becomes evident that creating value has the potential to grow exponentially.

If the pivot is successful, the product will capitalize the exponential growth potential. However, most teams have difficulties taking this decision. Teams hesitate, because the decision is counter intuitive. The reason is, that the pivot will temporarily delay growth. Additionally, it should happen at a moment when growth has, for the first time, demonstrated an exponential trend. Despite appearances, graph [3] shows that the decision to pivot quickly pays out and outperforms the decision to keep the existing technology choices.

Common pitfalls when pivoting the technology

What are common pitfalls when it comes to pivoting the technology, besides failing to recognize the need to pivot?

Delayed technology pivot

(4) Delayed Technology Pivot

The first most common mistake is delaying to pivot. Doing so may seem to be a better option than sticking to the current technology choices. Even so, this decision will eventually perform worst when compared to pivoting at the moment the growth potential becomes exponential.

Slow technology pivot

(5) Slow Technology Pivot

Another danger is that, if the pivot takes too long to complete, it fails to support the growth potential. As a result, the product will miss the chance to scale.

Cumbersome technology choices

(6) Cumbersome Technology Choices

Finally, teams may be tempted to make technology choices optimal for scaling already from the start of development. For example, by choosing a highly scalable but complex architecture or a heavyweight technology stack. They will do that in hope of avoiding to pivot later. By doing so however, they will most likely be very slow in developing the MVP and validating the business model. The graph [6] shows how such a choice will again lead to sub-optimal growth. Demonstrating feasibility of a scalable technical solution is vital, but implementing it too early is not.

Epilogue

This article, described the technology dilemmas that innovators face and presented a model to explain how the dilemmas affect product development.

The next step would be to answer the question raised at the beginning of the article:

What are the appropriate technical skills, technology stack and architecture for developing the new product?”

As already mentioned, this is a hard question to answer. Fortunately, the model presented here, offers some insights that can help in developing ideas to answer it.

In a follow-up article, I will write about these ideas and try to suggest a technology strategy for innovation.

 

Posted in Business, Product Management, Technology

September 21st, 2017 by Apostolos Benisis

Writing thread-safe code in Java can be a difficult task. Even very simple code, when executed concurrently, can have complex behavior. Additionally, the race conditions that can cause the code to fail, are difficult to reproduce in a testing environment, due to their probabilistic nature.

Fortunately, modern testing frameworks like TestNG have gone a long way and addressed some of these concerns.

In this comprehensive tutorial, you will learn how to use the TestNG framework to test concurrency and write thread-safe code.

The tools you will use are:

Gradle: For building and executing the project.
TestNG: As a testing framework.
AssertJ: For setting assertions.

Here is an outline of the various steps you will follow in this tutorial:

Before you start, you need to make sure you have JDK 1.8, or later, installed on your system.

You also need to have Gradle already installed on your system. You can find the installation manual here.

You can get the complete source code for this tutorial from this git repository:
git clone https://bitbucket.org/apostolosbenisis/thread-safety-with-testng.git

Create the Gradle project

First, create the root directory for the project:

mkdir thread-safety-with-testng

Note: In this tutorial, shell commands are for the Windows operating system. For Unix-based systems it should be easy to find the analogous commands

Using your favorite editor, create a new build.gradle file in the project root directory (thread-safety-with-testng\build.gradle) and add the following code:

buildscript {
  repositories {
    mavenCentral()
  }
}

apply plugin: 'java'

jar {
  baseName = 'multithreading'
  version =  '0.1.0'
}

repositories {
  mavenCentral()
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

dependencies {
  testCompile group: 'org.testng', name:'testng', version:'6.11'
  testCompile group: 'org.assertj', name:'assertj-core', version:'3.8.0'
}

/**
 * Tests
 */
test {
  useTestNG ()
  testLogging {
    events "PASSED", "FAILED"
  }
}

In the dependencies section you declared that your project depends on TestNG and AssertJ. The dependencies are declared for the testCompile configuration, as these are relevant only for testing.

You also setup the main test task to use the TestNG framework (useTestNG()) and also configured the logging of the tests to output information about the tests that passed and failed (events "PASSED", "FAILED").

Next, you will work on the java part of the project.

Write the Java thread-safe code

Start by creating a typical directory structure for the java project:

cd thread-safety-with-testng
mkdir src\main\java\threadsafe
mkdir src\test\java\threadsafe

Your directory structure should look like this:

thread-safety-with-testng
└──src
   ├── main
   |   └── java
   |       └── threadsafe
   └── test
       └── java
           └── threadsafe

You are now ready to work on the Java code.

Create the FooThreadsafeService class

To demonstrate thread safety in Java, you will create a class named FooThreadsafeService.
This class does the following:

• It maintains an internal list of integer numbers. Initially the list will be empty.
• It offers a public method (increment()) that when called, gets the number at the end of the list, increments it by 1 and appends the new number at the end of the list. If the list is empty, it adds 0 as the first element. After calling this function N times, the list is expected to have all the numbers from 0 to N-1 in ascending order, every number in the list should appear only once and the list does not contain any null entries.
• It offers a public method (getArray()) that returns an array with the entries of the list.
• Both increment() and getArray() are thread-safe. This means that they can be called concurrently from multiple threads in any order, without causing the code to fail and without violating the expected integrity of the number list, as described above.

Using your favorite editor create the file FooThreadsafeService.java in the src\main\java\threadsafe directory (src\main\java\threadsafe\FooThreadsafeService.java) and add the following code:

package threadsafe;

import java.util.LinkedList;

public class FooThreadsafeService {
    /**
     * Use a linked list to store the integer numbers.
     */
    private LinkedList<Integer> list = new LinkedList<>();

    /**
     * Get the last number from the list, increment it by 1 and add the new number at the end of the list.
     * If the list is empty add 0 as the first element. After calling this method N times,
     * the list is expected to have all the numbers from 0 to N-1 in ascending order, every number in
     * the list should appear only once and the list should not not contain any <tt>null</tt> entries.
     */
    public synchronized void increment() {
        int nextNumber = 0;
        int size = list.size();
        if (size > 0) {
            nextNumber = list.getLast() + 1;
        }
        Integer nextInteger = new Integer(nextNumber);
        list.addLast(nextInteger);

    }

    /**
     * Returns an int[] array with the entries of the list.
     **/
    public synchronized int[] getArray() {
        int[] intArray = new int[list.size()];
        for (int i = 0; i < list.size(); i++) {
            intArray[i] = list.get(i);
        }
        return intArray;
    }
}

Concurrency in detail

To understand how concurrency and thread synchronization work, consider the case, where two threads concurrently call the increment() method of the same FooThreadsafeService instance. Suppose also, that the increment() method is not synchronized. The image bellow shows how the timing of the code execution, which is probabilistic and unpredictable, leads to double entries being added in the list.

As you can imagine, the race conditions that actually occur when the code is executed, are even more complex. One additional reason for this complexity is the LinkedList used to manage the internal list of numbers. According to the LinkedList API Specification, it’s implementation is not synchronized. Therefore, if multiple threads access a LinkedList instance concurrently and at least one of the threads modifies the list structurally (add or delete one or more elements), it must be synchronized externally. If the list is not synchronized it will also exhibit an unpredictable behavior, due to the probabilistic timing of the code execution.

To address the concurrency problem, you may have considered using a synchronized list with Collections.synchronizedList(new LinkedList(...)). However, such an approach would not have solved the concurrency problem, because, as demonstrated in the example above, both increment() and getArray() functions, need to be treated as atomic operations.

The concurrency problem is solved by using the synchronized keyword at the declaration of each of the increment() and getArray() functions:

public synchronized void increment(){...}
public synchronized int[] getArray(){...}

By doing so, the code within these functions is guaranteed to be executed in a linear fashion, avoiding any race conditions that could have otherwise occurred. You can see how synchronization affects the timing of code execution in the image bellow:

Note: You can read more about synchronization in Java in the concurrency lessons of the official Java™ Tutorials. You can also read another great article on java concurency here.

Build the project

Now, you can build the project by running gradle build from the project root directory thread-safety-with-testng, where the build.gradle file is located. Gradle will download any relevant dependencies, compile the code and assemble the jar artifacts:

C:\thread-safety-with-testng>gradle build
:compileJava
:processResources UP-TO-DATE
:classes
:jar
:assemble
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
:check UP-TO-DATE
:build

BUILD SUCCESSFUL

Total time: 40.905 secs

Note: All Gradle commands need to be run from the directory where the build.gradle file is located.

The next step is to write a test for the class code.

Write the TestNG tests

Now, you can write a test to ensure that the class behaves as expected.

The test scenario you will consider, will call the increment() and getArray() functions N times concurrently and will verify that:

• calling these functions does not cause the code to throw any exception
• the list intergity is never violated, i.e.:
– the list contains all the numbers from 0 to N-1 once and in an ascending order
– it does not contain any null entries

Using your favorite editor create the file FooThreadsafeServiceUnitTest.java in the src\test\java\threadsafe directory (src\test\java\threadsafe\FooThreadsafeServiceUnitTest.java) and add the following code:

package threadsafe;

import org.testng.annotations.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class FooThreadsafeServiceUnitTest {
    //The instance of the service used in the tests.
    FooThreadsafeService fooService = new FooThreadsafeService();

    public final static int INVOCATION_COUNT = 10;

    @Test(threadPoolSize = 2, invocationCount = INVOCATION_COUNT)
    public void ConcurrentlyInvokingMethodsIsSuccessfull() throws Exception {
        //Calling the increment() or getArray() method, while the list is being concurrently modified by other threads,
        //must not throw any exceptions and the list's integrity must not be compromised.
        fooService.increment();
        listIntegrityNotCompromised(fooService.getArray());
    }

    private void listIntegrityNotCompromised(int[] intArray) {
        //The list is not empty.
        assertThat(intArray).isNotEmpty();
        //Elements of the list are in an continuous ordered sequence and it does not contain any nulls.
        for (int i = 0; i < intArray.length; i++) {
            assertThat(intArray[i]).isEqualTo(i);
        }
    }

    @Test(alwaysRun = true, dependsOnMethods = {"ConcurrentlyInvokingMethodsIsSuccessfull"})
    public void IncrementMethodInvocationCountEqualsToListSize() {
        int[] intArray = fooService.getArray();
        //Verify the integrity of the list.
        listIntegrityNotCompromised(intArray);
        //The list contains as many elements as the number of times the increment method was called.
        assertThat(fooService.getArray().length).isEqualTo(INVOCATION_COUNT);
    }
}

The first test method ConcurrentlyInvokingMethodsIsSuccessfull() will verify that:

• Calling the increment() and getArray() functions concurrently does not throw any exceptions. If an exception is thrown the test method will fail.
• While calling the above functions concurrently, the integrity of the list is not violated, i.e.:
– The list is not empty.
– Elements of the list are in an continuous ordered sequence starting from 0 and it does not contain any null values.

The constant INVOCATION_COUNT = 10 is used to declare how many times the test will run. To execute the method concurrently, the @Test annotation attributes invocationCount and threadPoolSize are used. The method will run 10 times (invocationCount = INVOCATION_COUNT) in total and 2 threads (threadPoolSize = 2) will be used to concurrently execute it.

The second test method IncrementMethodInvocationCountEqualsToListSize() will verify that:

• The integrity of the list is not violated, as described above.
• The list contains as many elements as the number of times the increment() function was called.

This test method will be executed only after all concurrent executions of the ConcurrentlyInvokingMethodsIsSuccessfull() method have completed. This is achieved by declaring an execution order dependency, with the use of the dependsOnMethods attribute (dependsOnMethods = {"ConcurrentlyInvokingMethodsIsSuccessfull"}).

You are now ready to run the tests.

Run the tests

From the project root directory enter the command gradle test. Gradle will now download the dependencies, compile all the code, assemble the jar files and finally run the test:

C:\thread-safety-with-testng>gradle test
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:jar UP-TO-DATE
:assemble UP-TO-DATE
:compileTestJava
Download https://repo1.maven.org/maven2/org/testng/testng/6.11/testng-6.11.pom
Download https://repo1.maven.org/maven2/org/assertj/assertj-core/3.8.0/assertj-core-3.8.0.pom
Download https://repo1.maven.org/maven2/org/assertj/assertj-parent-pom/2.1.6/assertj-parent-pom-2.1.6.pom
Download https://repo1.maven.org/maven2/com/beust/jcommander/1.64/jcommander-1.64.pom
Download https://repo1.maven.org/maven2/org/testng/testng/6.11/testng-6.11.jar
Download https://repo1.maven.org/maven2/org/assertj/assertj-core/3.8.0/assertj-core-3.8.0.jar
Download https://repo1.maven.org/maven2/com/beust/jcommander/1.64/jcommander-1.64.jar
:processTestResources UP-TO-DATE
:testClasses
:test
Gradle suite > Gradle test > threadsafe.FooThreadsafeServiceUnitTest.ConcurrentlyInvokingMethodsIsSuccessfull PASSED
Gradle suite > Gradle test > threadsafe.FooThreadsafeServiceUnitTest.ConcurrentlyInvokingMethodsIsSuccessfull PASSED
Gradle suite > Gradle test > threadsafe.FooThreadsafeServiceUnitTest.ConcurrentlyInvokingMethodsIsSuccessfull PASSED
Gradle suite > Gradle test > threadsafe.FooThreadsafeServiceUnitTest.ConcurrentlyInvokingMethodsIsSuccessfull PASSED
Gradle suite > Gradle test > threadsafe.FooThreadsafeServiceUnitTest.ConcurrentlyInvokingMethodsIsSuccessfull PASSED
Gradle suite > Gradle test > threadsafe.FooThreadsafeServiceUnitTest.ConcurrentlyInvokingMethodsIsSuccessfull PASSED
Gradle suite > Gradle test > threadsafe.FooThreadsafeServiceUnitTest.ConcurrentlyInvokingMethodsIsSuccessfull PASSED
Gradle suite > Gradle test > threadsafe.FooThreadsafeServiceUnitTest.ConcurrentlyInvokingMethodsIsSuccessfull PASSED
Gradle suite > Gradle test > threadsafe.FooThreadsafeServiceUnitTest.ConcurrentlyInvokingMethodsIsSuccessfull PASSED
Gradle suite > Gradle test > threadsafe.FooThreadsafeServiceUnitTest.ConcurrentlyInvokingMethodsIsSuccessfull PASSED
Gradle suite > Gradle test > threadsafe.FooThreadsafeServiceUnitTest.IncrementMethodInvocationCountEqualsToListSize PASSED
BUILD SUCCESSFUL

Total time: 9.241 secs

Congratulations, all tests passed! You have successfully tested the thread safety of your code. You can view the test results by opening the thread-safety-with-testng\build\reports\tests\build\reports\tests\index.html file in you browser.

If you want you can change the code to make the test fail. Remove the synchronized keyword from the increment() and the getArray() functions and run the test again by executing gradle test. Depending on your environment, all, some or none of the tests will fail. As you learned in a previous chapter, which test condition will fail is unpredictable and depends on the exact state of your test environment. If your tests don’t fail, try changing the values for the threadPoolSize and invocationCount attributes, to increase to chance of creating a race condition that will cause the code to fail.

Epilogue

Congratulations! You have successfully reached the end of this tutorial.

You gained some insights in writing thread-safe code in java and also learned how to use the TestNG framework to test your code. During this process, you also gained some insights on how to use Gradle and AssertJ.

You can get the complete source code from the git repository:
git clone https://bitbucket.org/apostolosbenisis/thread-safety-with-testng.git

Posted in Technology

Spring-Gatling-Gradle
September 11th, 2017 by Apostolos Benisis

This is a comprehensive tutorial that explains how to automate the load tests of a Spring Boot based RESTful Web Service using Gatling together with Gradle.

You will learn how to combine all the above tools and automate the load testing by defining tasks which can become part of a CI/CD build pipeline.

If you are not familiar with these tools, don’t worry. This tutorial will explain in details all the steps needed to start with all tools from scratch.

Here is an outline of the various steps you will follow in the tutorial:

  1. Create the Gradle Project
  2. Create the Spring Boot RESTful Web Service
  3. Create the Gatling simulation scenario
  4. Setup Gradle to automate the Gatling load tests

For developers that are already familiar with all three tools, perhaps the most interesting part in this tutorial is the part that explains how to Setup Gradle to automate the Gatling load tests. If you have a Spring Boot web service, Gradle build tasks and Gatling simulation scenarios for the load tests already setup, then you could skip the first three parts and jump directly to the fourth step.

What this tutorial will not do, is to explain in detail what is happening under the hood for each of the tools. If you are interested in understanding each tool in depth, there are other tutorials to help you. For example for Gatling:

Gatling quickstart
Gatling Advanced Tutorial

for Gradle:

Creating New Gradle Builds
Building Java Applications
• Java Quickstart

and for Spring Boot:

Building a RESTful Web Service Tutorial
Building an Application with Spring Boot

Before you start, you need to make sure you have Gradle already installed on your system. You can find the installation manual here.

You also need to have JDK 1.8 or later installed on your system.

You can get the complete source code from the git repository:

git clone https://bitbucket.org/apostolosbenisis/tutorial-gatling.git

Create the Gradle project

To begin with, create a new folder named tutorial-gatling and create an empty build.gradle file:

 
mkdir tutorial-gatling 
cd tutorial-gatling 
type nul>build.gradle 

Note: In this tutorial, shell commands are for the Windows operating system. For Unix-based systems it should be easy to find the analogous commands.

In the next steps you will gradually fill the build.gradle file with the appropriate code.

Create the Spring Boot RESTful Web Service

For building the Spring Boot web service, you will follow a similar path as the one presented in the Building a RESTful Web Service Tutorial.

You will build a simple RESTful web service that will accept a HTTP GET request and will respond with a JSON string representing the typical hello world greeting.

Create the java directory structure

Let’s start by creating the typical java directory structure bellow the tutorial-gatling directory:

 
cd tutorial-gatling
mkdir src\main\java\webservice

The directory structure bellow tutorial-gatling now looks like this:

 
tutorial-gatling
└── src
    └── main
        └── java
            └── webservice

The src\main\java\webservicedirectory is used for the java code of the web service.

Setup the Gradle for building

In a previous step you created the build.gradle file. Now you will add the code to build the web service. Using your favorite editor, open the build.gradle file and add the following code:

buildscript {
	repositories {
		mavenCentral()
	}
	dependencies {
		classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.5.6.RELEASE'
	}
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'

jar {
	baseName = 'webservice'
	version = '0.1.0'
}

repositories {
	mavenCentral()
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

dependencies {
	compile('org.springframework.boot:spring-boot-starter-web')
}

Create the Spring Boot application

Using your favorite editor, create the file Application.javain the webservice directory (src\main\java\webservice\Application.java) and add the following code:

package webservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

You can read how to bootstrap and launch a Spring application here.

You can read more about the @SpringBootApplication annotation here.

Create the resource representation class

The web service you are building will handle GET requests at the endpoint /greeting
and will respond with a 200 OK status and a JSON in the body, that represents a greeting. The JSON will look like this:
{"message": "Hello, World"}

You will use a POJO to model the greeting.

First, create a model directory bellow the webservice directory.

Then, using your favorite editor, create the file Greeting.java in the new directory (src\main\java\webservice\model\Greeting.java) and add the following code:

package webservice.model;

public class Greeting {
    private final String message;

    public Greeting(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

In a the next step you will see how this object is automatically marshaled to JSON and added to the body of the HTTP GET response, seamlessly by the spring framework.

Create the controller

First, create a web directory bellow the webservice directory.

Then, using your favorite editor, create the file GreetingController.javain the new directory (src\main\java\webservice\web\GreetingController.java) and add the following code:

 
package webservice.web;

import webservice.model.Greeting;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GreetingController {
    @RequestMapping("/greeting")
    public Greeting greeting() {
        return new Greeting("Hello, World");
    }
}

You can read more about the @RestController annotation here and here.

Build and execute the application

At this point the web service is completed and you can now build it and launch it. You have set up Gradle to use the Spring Boot Gradle plugin. This plugin offers many convenient methods for building the executing the Spring Boot web service.

Launch the web service by the executing the command gradlew bootRun. Gradle will now download all dependencies and then start the Spring Boot web service. Wait some time until the web services is up and running.

:compileJava
Download https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-starter-parent/1.5.6.RELEASE/spring-boot-starter-parent-1.5.6.RELEASE.pom
Download https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-starter-web/1.5.6.RELEASE/spring-boot-starter-web-1.5.6.RELEASE.pom
Download https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-starters/1.5.6.RELEASE/spring-boot-starters-1.5.6.RELEASE.pom
Download https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-starter/1.5.6.RELEASE/spring-boot-starter-1.5.6.RELEASE.pom
Download https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-starter-tomcat/1.5.6.RELEASE/spring-boot-starter-tomcat-1.5.6.RELEASE.pom
Download https://repo1.maven.org/maven2/org/hibernate/hibernate-validator/5.3.5.Final/hibernate-validator-5.3.5.Final.pom
Download https://repo1.maven.org/maven2/org/hibernate/hibernate-validator-parent/5.3.5.Final/hibernate-validator-parent-5.3.5.Final.pom
Download https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.8.9/jackson-databind-2.8.9.pom
Download https://repo1.maven.org/maven2/org/springframework/spring-web/4.3.10.RELEASE/spring-web-4.3.10.RELEASE.pom
Download https://repo1.maven.org/maven2/org/springframework/spring-webmvc/4.3.10.RELEASE/spring-webmvc-4.3.10.RELEASE.pom
Download https://repo1.maven.org/maven2/org/springframework/boot/spring-boot/1.5.6.RELEASE/spring-boot-1.5.6.RELEASE.pom
Download https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-autoconfigure/1.5.6.RELEASE/spring-boot-autoconfigure-1.5.6.RELEASE.pom
Download https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-starter-logging/1.5.6.RELEASE/spring-boot-starter-logging-1.5.6.RELEASE.pom
Download https://repo1.maven.org/maven2/org/apache/tomcat/embed/tomcat-embed-core/8.5.16/tomcat-embed-core-8.5.16.pom
Download https://repo1.maven.org/maven2/org/apache/tomcat/embed/tomcat-embed-el/8.5.16/tomcat-embed-el-8.5.16.pom
Download https://repo1.maven.org/maven2/org/apache/tomcat/embed/tomcat-embed-websocket/8.5.16/tomcat-embed-websocket-8.5.16.pom
Download https://repo1.maven.org/maven2/org/jboss/logging/jboss-logging/3.3.1.Final/jboss-logging-3.3.1.Final.pom
Download https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.8.9/jackson-core-2.8.9.pom
Download https://repo1.maven.org/maven2/org/springframework/spring-aop/4.3.10.RELEASE/spring-aop-4.3.10.RELEASE.pom
Download https://repo1.maven.org/maven2/org/springframework/spring-beans/4.3.10.RELEASE/spring-beans-4.3.10.RELEASE.pom
Download https://repo1.maven.org/maven2/org/springframework/spring-context/4.3.10.RELEASE/spring-context-4.3.10.RELEASE.pom
Download https://repo1.maven.org/maven2/org/springframework/spring-expression/4.3.10.RELEASE/spring-expression-4.3.10.RELEASE.pom
Download https://repo1.maven.org/maven2/org/slf4j/jcl-over-slf4j/1.7.25/jcl-over-slf4j-1.7.25.pom
Download https://repo1.maven.org/maven2/org/slf4j/jul-to-slf4j/1.7.25/jul-to-slf4j-1.7.25.pom
Download https://repo1.maven.org/maven2/org/slf4j/log4j-over-slf4j/1.7.25/log4j-over-slf4j-1.7.25.pom
Download https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-starter-web/1.5.6.RELEASE/spring-boot-starter-web-1.5.6.RELEASE.jar
Download https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-starter/1.5.6.RELEASE/spring-boot-starter-1.5.6.RELEASE.jar
Download https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-starter-tomcat/1.5.6.RELEASE/spring-boot-starter-tomcat-1.5.6.RELEASE.jar
Download https://repo1.maven.org/maven2/org/hibernate/hibernate-validator/5.3.5.Final/hibernate-validator-5.3.5.Final.jar
Download https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.8.9/jackson-databind-2.8.9.jar
Download https://repo1.maven.org/maven2/org/springframework/spring-web/4.3.10.RELEASE/spring-web-4.3.10.RELEASE.jar
Download https://repo1.maven.org/maven2/org/springframework/spring-webmvc/4.3.10.RELEASE/spring-webmvc-4.3.10.RELEASE.jar
Download https://repo1.maven.org/maven2/org/springframework/boot/spring-boot/1.5.6.RELEASE/spring-boot-1.5.6.RELEASE.jar
Download https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-autoconfigure/1.5.6.RELEASE/spring-boot-autoconfigure-1.5.6.RELEASE.jar
Download https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-starter-logging/1.5.6.RELEASE/spring-boot-starter-logging-1.5.6.RELEASE.jar
Download https://repo1.maven.org/maven2/org/apache/tomcat/embed/tomcat-embed-core/8.5.16/tomcat-embed-core-8.5.16.jar
Download https://repo1.maven.org/maven2/org/apache/tomcat/embed/tomcat-embed-el/8.5.16/tomcat-embed-el-8.5.16.jar
Download https://repo1.maven.org/maven2/org/apache/tomcat/embed/tomcat-embed-websocket/8.5.16/tomcat-embed-websocket-8.5.16.jar
Download https://repo1.maven.org/maven2/org/jboss/logging/jboss-logging/3.3.1.Final/jboss-logging-3.3.1.Final.jar
Download https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.8.9/jackson-core-2.8.9.jar
Download https://repo1.maven.org/maven2/org/springframework/spring-aop/4.3.10.RELEASE/spring-aop-4.3.10.RELEASE.jar
Download https://repo1.maven.org/maven2/org/springframework/spring-beans/4.3.10.RELEASE/spring-beans-4.3.10.RELEASE.jar
Download https://repo1.maven.org/maven2/org/springframework/spring-context/4.3.10.RELEASE/spring-context-4.3.10.RELEASE.jar
Download https://repo1.maven.org/maven2/org/springframework/spring-expression/4.3.10.RELEASE/spring-expression-4.3.10.RELEASE.jar
Download https://repo1.maven.org/maven2/org/slf4j/jcl-over-slf4j/1.7.25/jcl-over-slf4j-1.7.25.jar
Download https://repo1.maven.org/maven2/org/slf4j/jul-to-slf4j/1.7.25/jul-to-slf4j-1.7.25.jar
Download https://repo1.maven.org/maven2/org/slf4j/log4j-over-slf4j/1.7.25/log4j-over-slf4j-1.7.25.jar
:processResources UP-TO-DATE
:classes
:findMainClass
:bootRun
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.6.RELEASE)

2017-09-11 19:46:45.095  INFO 9376 --- [           main] webservice.Application                   : Starting Application on UP with PID 9376 (D:\Projects\tutorial-gatling\build\classes\main started by Apostolos Benisis in D:\Projects\tutorial-gatling)
2017-09-11 19:46:45.098  INFO 9376 --- [           main] webservice.Application                   : No active profile set, falling back to default profiles: default
2017-09-11 19:46:45.142  INFO 9376 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@4a7dd4: startup date [Mon Sep 11 19:46:45 CEST 2017]; root of context hierarchy
2017-09-11 19:46:46.330  INFO 9376 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2017-09-11 19:46:46.340  INFO 9376 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2017-09-11 19:46:46.341  INFO 9376 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.16
2017-09-11 19:46:46.427  INFO 9376 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2017-09-11 19:46:46.427  INFO 9376 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1287 ms
2017-09-11 19:46:46.538  INFO 9376 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
2017-09-11 19:46:46.541  INFO 9376 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2017-09-11 19:46:46.542  INFO 9376 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2017-09-11 19:46:46.542  INFO 9376 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2017-09-11 19:46:46.542  INFO 9376 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2017-09-11 19:46:46.784  INFO 9376 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@4a7dd4: startup date [Mon Sep 11 19:46:45 CEST 2017]; root of context hierarchy
2017-09-11 19:46:46.839  INFO 9376 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/greeting]}" onto public webservice.model.Greeting webservice.web.GreetingController.greeting()
2017-09-11 19:46:46.841  INFO 9376 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2017-09-11 19:46:46.842  INFO 9376 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2017-09-11 19:46:46.863  INFO 9376 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-09-11 19:46:46.863  INFO 9376 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-09-11 19:46:46.894  INFO 9376 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-09-11 19:46:47.008  INFO 9376 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-09-11 19:46:47.051  INFO 9376 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-09-11 19:46:47.054  INFO 9376 --- [           main] webservice.Application                   : Started Application in 2.217 seconds (JVM running for 2.492)
> Building 83% > :bootRun

Alternately, you can use the command gradlew build to build and assemble all dependencies and the compiled classes in a single, runnable “über-jar” jar file. The jar file can then be executed with java -jar build/libs/webservice-0.1.0.jar.jar

Note: All Gradle commands must executed from the tutorial-gatling directory where the build.gradle file is located.

Now, open in a browser the url http://localhost:8080/greeting

You should now see in the browser the greeting in JSON format.

JSON greeting

Note: Instead of opening a browser, you can run curl http://localhost:8080/greeting from the command prompt. The windows operating system does not come with curl,  so you have to download it from here and install it on your system. If you have Git already installed on your system, just open a Git bash and run the curl command. It is already included with the Git distribution.

Create the Gatling simulation scenario

Gatling is a load and performance testing tool. You will now learn how to create a scenario and run a simulation to load test the web service you created in the previous steps.

Installation

You will add Gatling as a dependency in the Gradle build.gradle file and let Gradle download all required files.

Since you don’t want to distribute Gatling together with the production artifacts, you will add it as a testCompile dependency.

Note: You can find more information on the dependency types of the java plugin here.

Edit the build.gradle file and add the line testCompile('io.gatling.highcharts:gatling-charts-highcharts:2.3.0') in the dependencies section, towards the end of the file.

The Gatling simulation scenarios are written in Scala code. To compile the Scala code you will add  apply plugin: 'scala' next to the other plugin entries.

The file should look like this:

...
...
...
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'scala'
...
...
...
dependencies {
	compile('org.springframework.boot:spring-boot-starter-web')
	testCompile('io.gatling.highcharts:gatling-charts-highcharts:2.3.0')
}

Note: Gatling offers a manual installation with a zip bundle. If you want to run Gatling manually from the command prompt, you should additionally follow the zip file based installation described in the quickstart guide

Create the Scala directory structure

First, create the directory structure bellow the tutorial-gatling directory for storing the Gatling simulation scenario. In the command prompt go to the tutorial-gatling directory and then run:

 
mkdir src\test\scala\webservice\gatling\simulation
mkdir src\test\resources

The directory structure bellow tutorial-gatling now looks like this:

 
tutorial-gatling
└── src
    ├── main
    |   └── java
    |       └── webservice
    └── test
        ├── scala
        |   └── webservice
        |       └── gatling
        |           └── simulation
        └── resources             

Create the test scenario

The web service you created in the previous steps has a single and very simple end point (http://localhost:8080/greeting). In the next step you will  create a simple test scenario to test the performance of the web service when it is actually consumed. In details it will do the following:

  1. The request will simulate a user using Firefox.
  2. It will run for 20 seconds (testTimeSecs = 20).
  3. During this time a user continuously requests the endpoint http://localhost:8080/greeting and expects a response with the status 200 OK.
  4. Between two requests, the user will pause for 1 to 3 seconds. (minWaitMs = 1000 millisecons maxWaitMs = 3000 milliseconds).
  5. The number of users connecting simultaneously are 10 (noOfUsers = 10).
  6. It will take 5 seconds (rampUpTimeSecs = 5) to ramp up the number of users from 0 to 10.

Using your favorite editor, create the file WebServiceCallSimulation.scala in the new directory (src\test\scala\webservice\gatling\simulation\WebServiceCallSimulation.scala) and add the following code:

package webservice.gatling.simulation

import io.gatling.core.Predef._
import io.gatling.http.Predef._

import scala.concurrent.duration._

class WebServiceCallSimulation extends Simulation {

 val rampUpTimeSecs = 5
 val testTimeSecs = 20
 val noOfUsers = 10
 val minWaitMs = 1000 milliseconds
 val maxWaitMs = 3000 milliseconds

 val baseURL = "http://localhost:8080"
 val baseName = "webservice-call-greeting"
 val requestName = baseName + "-request"
 val scenarioName = baseName + "-scenario"
 val URI = "/greeting"

 val httpConf = http
  .baseURL(baseURL)
  .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") // 6
  .doNotTrackHeader("1")
  .acceptLanguageHeader("en-US,en;q=0.5")
  .acceptEncodingHeader("gzip, deflate")
  .userAgentHeader("Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0")

 val scn = scenario(scenarioName)
  .during(testTimeSecs) {
   exec(
    http(requestName)
     .get(URI)
     .check(status.is(200))
    ).pause(minWaitMs, maxWaitMs) 
  }
 
 setUp(
  scn.inject(rampUsers(noOfUsers) over (rampUpTimeSecs))
  ).protocols(httpConf)
 }

Note:You can find more information on user agent header string here.

Configure logging for Gatling

Next you will configure the logging level for Gatling to make it less verbose. Gatling uses Logback.

Using your favorite editor, create the file logback-gatling.xml in the src\test\resources directory (src\test\resources\logback-gatling.xml) and add the following code:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%-5level] %logger{15} - %msg%n%rEx</pattern>
            <immediateFlush>false</immediateFlush>
        </encoder>
    </appender>
    <!-- Uncomment for logging ALL HTTP request and responses -->
    <!--    <logger name="io.gatling.http.ahc" level="TRACE" /> -->
    <!--    <logger name="io.gatling.http.response" level="TRACE" /> -->
    <!-- Uncomment for logging ONLY FAILED HTTP request and responses -->
    <!--    <logger name="io.gatling.http.ahc" level="DEBUG" /> -->
    <!--    <logger name="io.gatling.http.response" level="DEBUG" /> -->

    <root level="ERROR">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

Setup the Gatling testLoad task in Gradle

Instead of running Gatling from the command prompt, you will run it from a Gradle task.

In a previous step you learned how you can download the package in a convenient way, by declaring the appropriate dependency in the build.gradle script.

Now you will define a new Gradle task to execute the specific simulation from Gradle. It will be namedtestLoadand it will be a java based Gradle task.
The task will execute the main Gatling class io.gatling.app.Gatling and pass required parameters such as:

• the location where the compiled simulation classes can be found (-Dgatling.core.directory.binaries),
• the Logback configuration file (-Dlogback.configurationFile),
• the simulation class name to be executed (--simulation) and
• the location to store the results of the simulation runs (--results-folder)

Edit the build.gradle file and add at the end of the file the following code:

/**
 * Gatling load tests
 */
task testLoad(type: JavaExec) {
	description = 'Test load the Spring Boot web service with Gatling'
	group = 'Load Test'
	classpath = sourceSets.test.runtimeClasspath
	jvmArgs = [
		// workaround for https://github.com/gatling/gatling/issues/2689
		"-Dgatling.core.directory.binaries=${sourceSets.test.output.classesDir.toString()}",
		"-Dlogback.configurationFile=${logbackGatlingConfig()}"
	]
	main = 'io.gatling.app.Gatling'
	args = [
		'--simulation', 'webservice.gatling.simulation.WebServiceCallSimulation',
		'--results-folder', "${buildDir}/gatling-results",
		'--binaries-folder', sourceSets.test.output.classesDir.toString() // ignored because of above bug		
	]
}

def logbackGatlingConfig() {
 return sourceSets.test.resources.find { it.name == 'logback-gatling.xml'};
}

At this point everything is prepared to run the load test.

Manually run the load test

You will now manually execute the Gatling simulation. If the Spring Boot web service is not already running, you need to start it before the simulation can run. You will need two command prompts.

In the first command prompt execute gradlew bootRun to launch the Spring Boot web service, if it is not already running.

In the second command prompt executegradlew testLoadto start the Gatling test. Wait until the Spring Boot web service is up and running before starting the Gatling test, otherwise the test will report failed requests. Gradle will do all required jobs to run the task e.g. download any dependencies, compile and assemble packages and eventually Gatling will start and run the simualtion. You should now see some output in the console with the results of the Gatling test:

---- webservice-call-greeting-scenario -----------------------------------------
[##########################################################################]100%
          waiting: 0      / active: 0      / done:10
================================================================================

Simulation webservice.gatling.simulation.WebServiceCallSimulation completed in 25 seconds
Parsing log file(s)...
Parsing log file(s) done
Generating reports...
> Building 87% > :testLoad
================================================================================
---- Global Information --------------------------------------------------------
> request count                                        108 (OK=108    KO=0     )
> min response time                                      5 (OK=5      KO=-     )
> max response time                                    128 (OK=128    KO=-     )
> mean response time                                     9 (OK=9      KO=-     )
> std deviation                                         12 (OK=12     KO=-     )
> response time 50th percentile                          7 (OK=7      KO=-     )
> response time 75th percentile                          9 (OK=9      KO=-     )
> response time 95th percentile                         17 (OK=17     KO=-     )
> response time 99th percentile                         21 (OK=21     KO=-     )
> mean requests/sec                                  4.154 (OK=4.154  KO=-     )
---- Response Time Distribution ------------------------------------------------
> t < 800 ms                                           108 (100%)
> 800 ms < t < 1200 ms                                   0 (  0%)
> t > 1200 ms                                            0 (  0%)
> failed                                                 0 (  0%)
================================================================================

Reports generated in 0s.

Reports generated in 0s.
Please open the following file: C:\Git Repositories\tutorial-gatling\build\gatling-results\webservicecallsimulation-1505126773724\index.html

In the tutorial-gatling\build\gatling-results directory you can find the reports of the executed simulation runs. Here is how such a report looks like:

Gatling report

Note: If you have downloaded the zip bundle of Gatling as described in the quickstart guide you can manually run the  simulation from the command prompt: "%GATLING_HOME%\bin\gatling.bat" --simulations-folder .\src\test\scala\webservice\gatling\simulation\

Note: You can also investigate if the gradle-gatling-plugin can be used and if it offers a more convenient way to start Gatling from Gradle, instead of using the JavaExec task.

Setup Gradle to automate the Gatling load tests

At this point, you managed to start the Gatling tests from Gradle. However, you have to manually make sure that the Spring Boot web service is up and running, before you can start the Gatling tests. This is a problem if you want to automate the process. In the next steps you will solve this problem, by extending the Gradle scripts to:

• launch the Spring Boot web service
• wait until it is up and running
• then execute the Gatling load test and
• finally shut down the Spring Boot web service, when the tests are over.

The advantage of this approach is that, since the complete process is executed from a grade script, it can be easily become part of a CI/CD automated build pipeline, without the need for manual intervention.

Add Actuator to the Spring Boot web service

For checking the status of the Spring Boot web service, you will add the Spring Boot Actuator to your web service. You can read the Building a RESTful Web Service with Spring Boot Actuator tutorial if you want to better understand the process.

Edit the build.gradle file and add in the dependencies section the following line:

compile('org.springframework.boot:spring-boot-starter-actuator')

By adding Actuator to your web service, you get several new endpoints. The endpoint that is interesting to your purposes, is the http://localhost:8080/health endpoint. You will use this endpoint to check when the Spring Boot web service is up and running.

Note:You can manually try out the new endpoint. Run gradlew bootRun in a command prompt. Gradle will download all required dependencies and eventually start the Spring Boot web service. Open the http://localhost:8080/health endpoint in a browser and you should see the following JSON: {"status": "UP"}

Define the Spring Boot start, stop and wait Gradle tasks

Now that you have a way to check if the Spring Boot web service is up, you will extend the build.gradle script to define the following tasks:

startSpringBoot: Start the Spring Boot web service in the background (in a separate process).
stopSpringBoot: Stop the Spring Boot web service that was started by startSpringBoot.
waitUntilSringBootIsUp: Call the /health endpoint and wait until it responds

Edit the build.gradle script and replace it with the following code:

import com.github.jengelman.gradle.plugins.processes.tasks.JavaFork
import groovy.json.JsonSlurper

buildscript {
    repositories {
        mavenCentral()
		maven {
            url 'https://plugins.gradle.org/m2/'
        }
    }
    dependencies {
        classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.5.6.RELEASE'
		classpath 'com.github.jengelman.gradle.plugins:gradle-processes:0.3.0'
    }
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'scala'
apply plugin: 'com.github.johnrengelman.processes'

jar {
    baseName = 'webservice'
    version =  '0.1.0'
}

repositories {
    mavenCentral()
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
	compile('org.springframework.boot:spring-boot-starter-actuator')
	testCompile('io.gatling.highcharts:gatling-charts-highcharts:2.3.0')
}

/**
 * Gatling load tests
 */

task startSpringBoot(type: JavaFork) {
    description = 'Start Spring Boot in the background.'
    group = 'Load Test'
    classpath = sourceSets.main.runtimeClasspath
    main = 'webservice.Application'    
}
startSpringBoot.dependsOn assemble

task stopSpringBoot << {
    description = 'Stop the instance of Spring Boot that was started with \'startSpringBoot\''
    group = 'Load Test'
    startSpringBoot.processHandle.abort()
}

task waitUntilSpringBootIsUp << {
    description = 'Call the /health endpoint and wait until it responds with the status UP.'
	group = 'Load Test'
    String url = 'http://localhost:8080/health'
    boolean success = false
    int count = 0
    while (count < 15 && !success) {
        println 'Trying to connect to \'' + url + '\' : Attempt number=' + count
        try {
            count += 1
            JsonSlurper jsonSlurper = new JsonSlurper();
            String responceBody = url.toURL().text
            println responceBody
            Object result = jsonSlurper.parseText(responceBody);
            Map jsonResult = (Map) result;

            String status = (String) jsonResult.get('status');

            success = (status == 'UP')
        }
        catch (all) {
            sleep(5 * 1000) // wait for another 5 seconds until next retry
        }
    }
    if (success) {
        println 'SUCCEFULLY Connected to \'' + url + '\''
    } else {
        println 'FAILED to Connected to \'' + url + '\''
    }
}
waitUntilSpringBootIsUp.dependsOn startSpringBoot

task testLoad(type: JavaExec) {
	description = 'Test load the Spring Boot web service with Gatling'
	group = 'Load Test'
	classpath = sourceSets.test.runtimeClasspath
	jvmArgs = [
		// workaround for https://github.com/gatling/gatling/issues/2689
		"-Dgatling.core.directory.binaries=${sourceSets.test.output.classesDir.toString()}",
		"-Dlogback.configurationFile=${logbackGatlingConfig()}"
	]
	main = 'io.gatling.app.Gatling'
	args = [
		'--simulation', 'webservice.gatling.simulation.WebServiceCallSimulation',
		'--results-folder', "${buildDir}/gatling-results",
		'--binaries-folder', sourceSets.test.output.classesDir.toString() // ignored because of above bug		
	]
}

def logbackGatlingConfig() {
 return sourceSets.test.resources.find { it.name == 'logback-gatling.xml'};
}

testLoad.dependsOn waitUntilSpringBootIsUp
testLoad.finalizedBy stopSpringBoot

Note: The changed/added code has been highlighted in the listing above,.

Lets now examine in detail the code changes you introduced and how they work.

Launch/Stop the Spring Boot web service

import com.github.jengelman.gradle.plugins.processes.tasks.JavaFork
...
buildscript {
    repositories {
        ...
		maven {
			url 'https://plugins.gradle.org/m2/'
        }
    }
    dependencies {
        ...
		classpath 'com.github.jengelman.gradle.plugins:gradle-processes:0.3.0'
    }
}
...
apply plugin: 'com.github.johnrengelman.processes'
...
task startSpringBoot(type: JavaFork) {
    description = 'Start Spring Boot in the background.'
    group = 'Load Test'
    classpath = sourceSets.main.runtimeClasspath
    main = 'webservice.Application'    
}
startSpringBoot.dependsOn assemble

task stopSpringBoot << {
    description = 'Stop the instance of Spring Boot that was started with \'startSpringBoot\''
    group = 'Load Test'
    startSpringBoot.processHandle.abort()
}
...

First, you added the Gradle-Processes plugin, which provides the capability to create forked processes. Then, you defined the startSpringBoot, which uses the new type: JavaFork to start the Spring Boot web service in a forked process. Since the jar file needs to be assembled before it can be executed, you also declared a dependency to the assemble task. Then, you defined the stopSpringBoot task, which can be used to stop the process of the startSpringBoot task.

Note:You can investigate how to rewrite the stopSpringBoot task, to use the actuator /shutdown endpoint, instead of the processHandle.abort() function. Be aware that the /shutdown endpoint is disabled by default and you have to explicitly enable it.

Wait until the Spring Boot web service is up and running

...
import groovy.json.JsonSlurper
...
task waitUntilSpringBootIsUp << {
    description = 'Call the /health endpoint and wait until it responds with the status UP.'
	group = 'Load Test'
    String url = 'http://localhost:8080/health'
    boolean success = false
    int count = 0
    while (count < 15 && !success) {
        println 'Trying to connect to \'' + url + '\' : Attempt number=' + count
        try {
            count += 1
            JsonSlurper jsonSlurper = new JsonSlurper();
            String responceBody = url.toURL().text
            println responceBody
            Object result = jsonSlurper.parseText(responceBody);
            Map jsonResult = (Map) result;

            String status = (String) jsonResult.get('status');

            success = (status == 'UP')
        }
        catch (all) {
            sleep(5 * 1000) // wait for another 5 seconds until next retry
        }
    }
    if (success) {
        println 'SUCCEFULLY Connected to \'' + url + '\''
    } else {
        println 'FAILED to Connected to \'' + url + '\''
    }
}
waitUntilSpringBootIsUp.dependsOn startSpringBoot
....

You imported groovy.json.JsonSlurper, which offers functionality to parse the JSON response from the Actuator health endpoint. You defined the waitUntilSpringBootIsUp task, which attempts to call the /health endpoint. If the task fails to receive a status UP in the JSON response, it will retry several times and finally give up. You also declared that the waitUntilSpringBootIsUp has a dependency to the startSpringBoot task.

Start the Gatling test if the web service is up and running

...
testLoad.dependsOn waitUntilSpringBootIsUp
testLoad.finalizedBy stopSpringBoot

Finally, you declared that the testLoad task has a dependency to the waitUntilSpringBootIsUp task and that when it completes, the stopSpringBoot should be called.

Putting everything together

Because of the dependencies you defined between the tasks, when the testLoad task is executed (by calling gradlew testLoad), before it starts, it will call waitUntilSpringBootIsUp which will in turn call the startSpringBoot task. This task will effectively launch the Spring Boot web service in the background (if required the jars will be assembled first). While startSpringBoot runs, waitUntilSpringBootIsUp concurrently attempts to establish if the service is up. Once the service is up, waitUntilSpringBootIsUp will complete, allowing for the testLoad to start. Once testLoad is completed, the stopSpringBoot will be called, ending the Spring Boot web service.

You can now try it out by simply running gradlew testLoad in the command prompt.

Epilogue

Congratulations! You have successfully reached the end of this tutorial.

You learned how to write a simple Spring Boot web service and load test it using Gatling. You also learned how to setup Gradle to perform all the actions required to run the load test in an automated fashion.

You can get the complete source code from the git repository:
git clone https://bitbucket.org/apostolosbenisis/tutorial-gatling.git

Posted in Technology

August 21st, 2012 by Apostolos Benisis

How can data cubes (multidimensional data models) be used in the context of Business Process Management (BPM) to support management efforts in understanding, planning and improving business processes?

Traditional approaches define a customized multidimensional data model for analyzing operational data.

The proposed approach is to define a standardized multidimensional data model for analyzing data that can be applied to any business process model.

This approach is novel and innovative, not only because of the mapping of business process data to a data cube, but also because, once the mapping has been completed, and facts have been generated (e.g. via simulation), non-technical business analysts are able to have a completely new understanding of the dynamic behavior within organizations by using an extensive range of inquiries that are possible using standard data cube analysis interfaces.

The three perspectives

Business process management, simulation and the use of a tested methodology have complementary roles in providing businesses with useful information so that managers can make better decisions.

Business Process Management (BPM) is about the management of business
processes. It is a top down, cross-functional, process centric management approach, which deals with the design, implementation, control and improvement of the end-end process of an organization.

Business process modeling, as a part of Business Process Management, provides a visualization and static analysis component that describes the context in which a set of activities occur.

Simulation provides a dynamic behavior component that is able to test assumptions about all conditions that effect process performance.

Business Process Cube

The three perspectives (BPM, business process models and simulation) allow the extremely complex interactions and inter-dependencies within organizations to be better understood by managers, so that they can better direct their organizations. All three views are associated with traditional analyses methods that are at best, only partially integrated. Due to lack of integration, and the current cumbersome, highly technical, analysis techniques available, many critical questions cannot be easily answered or they cannot be answered at all.

A new approach is proposed that extends the functionality and value of BPM,
process modeling, simulation and data cube analysis techniques, by combining, transforming and integrating all relevant data into a data cube structure that can be easily created and queried. In the context of business process modeling, a multidimensional model for analyzing the data generated by business process model simulations is developed. A process model, along with the simulation meta model, is used to define the dimensions and the facts for a multidimensional model. The multidimensional model developed is a generic one and is therefore not constrained to a specific kind of a business process model.

When using the this approach, non-technical users are able to analyze a specific business process model and answer critical questions about their organization.  The business process model is used to fill the dimensions with data, and data from simulations of the model are used to fill the facts.

The business process cube makes it possible to answer a wide range of mission critical questions regarding the performance of the organization.  Questions that can now be answered range from simple ones like “What is the time required to perform Activities per Process?”, or to more complex questions which could not be easily answered before, such as “What is the average throughput time and costs for processing a specific piece of Information for a specific type of customer for each product type between a specific Input and a  specific Output?”.

Posted in Business Process Management, Data Mining

LinkedIn Auto Publish Powered By : XYZScripts.com