DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report
  1. DZone
  2. Coding
  3. Frameworks
  4. Lightweight Real-Time Analytics With Spring Boot + WSO2 Siddhi

Lightweight Real-Time Analytics With Spring Boot + WSO2 Siddhi

In this post, we learn how to tackle microservice development using Spring Boot, Angular, WSO2 Siddhi, and more. Read on to get started!

Tharindu Vibuddha Gangodagama user avatar by
Tharindu Vibuddha Gangodagama
·
Mar. 19, 19 · Tutorial
Like (2)
Save
Tweet
Share
13.22K Views

Join the DZone community and get the full member experience.

Join For Free

Are you looking for a way to create a lightweight real-time analytics engine? This article explains how to do exactly that by combining a fast, embeddable real-time analytics library (WSO2 Siddhi) with an immensely popular microservices platform, Spring Boot. As a bonus, I will show you how to do that in under 5 minutes with code generation! After following this article, you'll be able to generate a full-fledged Java/Spring Boot project with a documented REST API, friendly, responsive front-end, basic Spring security, comprehensive test coverage, and database integration with just a few shell commands! The generated code will be your foundation for real-time microservice application.

JHipster is a handy application generator that creates a Spring Boot and Angular application. JHipster is one of the best low code development platforms in the open source community. JHipster has become very popular on GitHub within a short amount of time. It has a high-performance Java stack on the server-side with Spring Boot and a front-end based on Angular, Bootstrap, and React. It makes project management easy with a powerful workflow, as well as build tools like Yeoman, Webpack, and Maven/Gradle. Many developers personally use it to generate multiple Spring microservices that are preconfigured to work in their company’s infrastructure. But this articles’ objective is not to give a detailed explanation of JHipster. This article will focus on how to create a JHipster module for WSO2 Siddhi. If you are new to JHipster or want to read more about JHipster, please read you can check out their documentation here: https://cbornet.github.io/.

You may have some specific real-time analytics (WSO2 Siddhi) setup that you like to use in your JHipster projects. Because you don’t want to reinvent the wheel in every project, it makes sense to abstract all the boilerplate into your own generator. In that case, you can build your own JHipster generator for WSO2 Siddhi and use it with your every JHipster project.

At Xiges.io, we heavily rely on code generation (with a custom built toolchain) to get rid of a lot of boilerplate code which we tend to program into each and every solution/project/product. When we encounter a repeatable architectural pattern we tend to apply our toolchain so, next time, we don’t have to code it again. Most of this work, including the toolchain, is part of the Xiges low code platform.

Getting Set Up

This section explains how to build the basic JHipster generator module. If you already know how to create a JHipster module, you can skip this part and go to the next section.

A JHipster module is a Yeoman generator. Also, it is an NPM package. As a first step to generate a basic yeoman generator (which would eventually be our JHipster generator) install Node.js. After that you’ll need to have Yeoman, Yarn, and Bower installed as well as the generator (yo) for creating generators. Please follow below commands with npm.

npm install -g generator-generator

npm install -g yeoman

npm install -g yo

To check whether Yeoman is installed correctly just type the yo command on the command line which will list out all the installed generators. If you can find Yeoman there, you are good to go. Finally, make sure you have git installed.

First, create a new directory in which you’ll write your JHipster generator. This directory must be named \ generator-jhipster-<name of your module(Siddhi) > . Normally, a Yeoman generator is prefixed with “generator-” but JHipster modules are prefixed with “generator-jhipster-”. Executing the below commands in order will create the folder and generate the JHipster module template.

npm install -g generator-jhipster-module

mkdir generator-jhipster-siddhi

cd generator-jhipster-siddhi

yo jhipster-module

This is not really a JHipster module that is meant to not be used in a JHipster application. This module is used to generate a new template for coding a new JHipster module for Siddhi. Essentially, it's a JHipster module to create a JHipster module. 

File structure:

JHipster File Structure

As you can see, we have a readme file and package.json for the generator itself. The test folder holds tests for the generator. The index.js file is the entry point for the generator. It contains the template files for the boilerplate (for generating the actual scaffolding). We created the default generator. Now we can modify it and add in our custom features.

Scripting the Siddhi Generator

Now we will check how to customize the JHipster generator we created and add our own features in the generator with the following steps:

1. Setup and Import the generator-jhipster

The index.js file needs to export the generator-jhipster which will get run by Yeoman. Now I am going to clear everything in our generator and start from scratch. Here is what index.js file looks like after that:

const chalk = require('chalk');
const generator = require('yeoman-generator');
const packagejs = require('../../package.json');

// Stores JHipster variables
const jhipsterVar = { moduleName: 'siddhi' };

// Stores JHipster functions
const jhipsterFunc = {};

module.exports = generator.extend({

     // all your yeoman code here

});

We assign the extended generator to module.exports and make it available to the ecosystem. This is the typical method we use when we export modules in Node.js. Then we can have our own functionalities to perform through methods. For every method we added to the index.js file, the method is run once the generator is called (and usually run in sequence). Some method names have priority in this generator. The available priorities (in running order) are:

  1. initializing- Initialization of your methods (getting configs and checking the project's current state).
  2. prompting - prompt user preferences for the options (call this.prompt()).
  3. configuring - Saving configurations and configuring the project.
  4. default - If your method name doesn’t match priority and put into this group.
  5. writing - copy template-files to the output folder and parsing (routes, controllers, etc).
  6. conflicts - Handling conflicts in the code.
  7. install - Where installations are run and add Maven dependencies to the target JHipster project pom file. (npm, Bower)
  8. end - Called last, clean up.

After implementing these methods your file should be like this.

module.exports = generator.extend({

    initializing: {
        compose() {
            this.composeWith('jhipster:modules',
                { jhipsterVar, jhipsterFunc },
                this.options.testmode ? { local: require.resolve('generator-jhipster/generators/modules') } : null
            );
        },
        displayLogo() {
            // Have Yeoman greet the user.
            this.log(`Welcome to the ${chalk.bold.yellow('WSO2 siddhi')} generator! ${chalk.yellow(`v${packagejs.version}\n`)}`);
        }
    },

    prompting() {
        // return the function to call once the task is done
        const done = this.async();


        const prompts = [
            {
                type: 'input',
                name: 'userSiddhi',
                message: 'Please write your own Siddhi app',
                default: 'Do. Or do not. There is no try.'
            }
        ];

        this.prompt(prompts).then((props) => {
            this.props = props;
            // To access props later use this.props.someOption;

            done();
        });
    },

    writing() {
        // function to use directly template
        this.template = function (source, destination) {
            this.fs.copyTpl(
                this.templatePath(source),
                this.destinationPath(destination),
                this
            );
        };

        this.baseName = jhipsterVar.baseName;
        this.packageName = jhipsterVar.packageName;
        this.packageFolder = jhipsterVar.packageFolder;
        this.angularAppName = jhipsterVar.angularAppName;
        this.clientFramework = jhipsterVar.clientFramework;
        this.clientPackageManager = jhipsterVar.clientPackageManager;
        const javaDir = jhipsterVar.javaDir;
        const resourceDir = jhipsterVar.resourceDir;
        const webappDir = jhipsterVar.webappDir;



        this.template('src/main/java/package/domain/_RealtimeAnalyticsServiceImpl.java', `${javaDir}domain/RealtimeAnalyticsServiceImpl.java`);
        this.template('src/main/java/package/domain/_RealtimeAnalyticsService.java', `${javaDir}domain/RealtimeAnalyticsService.java`);
        this.template('src/main/java/package/domain/_TemperatureData.java', `${javaDir}domain/TemperatureData.java`);

        this.template('src/main/java/package/web/rest/_TemperatureDataResource .java', `${javaDir}web/rest/TemperatureDataResource.java`);
    },

    install() {


        this.addMavenDependency('org.wso2.siddhi', 'siddhi-query-api', '4.1.7');
        this.addMavenDependency('org.wso2.siddhi', 'siddhi-query-compiler', '4.1.7');
        this.addMavenDependency('org.wso2.siddhi', 'siddhi-annotations', '4.1.7');
        this.addMavenDependency('org.wso2.siddhi', 'siddhi-core', '4.1.7');
    },

    end() {
        this.log('End of WSO2 Siddhi generator');
    }
});

JHipster module uses composability, which is one of the main functions in Yeoman. The “composeWith” (this.composeWith()) method in Yeoman generators allows the generator to run parallel with another generator and it can use features from the other generator instead of having to do it all by itself. In the above example, our code composes with the “jhipster:modules” sub-generator and gets access to JHipster’s variables and functions.

As you can see in the code, we add  aJHipster function (under the install() phase) to add Maven dependencies into the pom.xml file (addMavenDependency). We also used JHipster global variables. Here are the short definitions for each variable.

baseName: the name of the application
packageName: the Java package name
angularAppName: the Angular application name
javaDir: the directory for the Java application, including the package folder
resourceDir: the directory containing the Java resources (always src/main/resources) webappDir: the directory containing the Web application (always src/main/webapp)

You can see Java Spring Boot classes and resource files add to the writing() phase and this is done by calling the template function (all the Java Spring Boot classes are described in the next section ). If you are interested in other functions available in the JHipster module, please visit https://cbornet.github.io/modules/creating_a_module.html.

2. Initializing the Generator

We initialize our generator with package.json. In the above code, Node invokes the require() function with a local file path as the function’s only argument and it gets the package.json file. This file is a Node.js module manifest. Our package.json file must contain the following:

{
  "name": "@xiges/generator-jhipster-siddhi",
  "version": "0.0.7",
  "description": "JHipster module, additional siddhi support in your JHipster application",
  "keywords": [
    "yeoman-generator",
    "jhipster-module",
    "siddhi"
  ],
  "homepage": "https://github.com/xiges/generator-jhipster-siddhi",
  "author": {
    "name": "Tharindu Vibuddha",
    "email": "tharinduvibuddha@gmail.com",
    "url": "https://github.com/xiges/generator-jhipster-siddhi"
  },
  "files": [
    "generators"
  ],
  "main": "generators/app/index.js",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/xiges/generator-jhipster-siddhi"
  },
  "dependencies": {
    "yeoman-generator": "1.1.1",
    "chalk": "1.1.3",
    "mkdirp": "0.5.1",
    "generator-jhipster": ">=4.1.0"
  },
  "devDependencies": {
    "fs-extra": "0.26.4",
    "gulp": "^3.9.0",
    "gulp-bump": "1.0.0",
    "gulp-eslint": "1.0.0",
    "gulp-exclude-gitignore": "1.0.0",
    "gulp-git": "1.6.1",
    "gulp-istanbul": "0.10.3",
    "gulp-mocha": "2.2.0",
    "gulp-nsp": "2.3.0",
    "gulp-plumber": "1.0.1",
    "gulp-rename": "^1.2.0",
    "gulp-sequence": "0.4.4",
    "gulp-shell": "^0.5.1",
    "mocha": "2.3.4",
    "yeoman-assert": "2.1.1",
    "yeoman-test": "1.6.0"
  },
  "scripts": {
    "test": "mocha test/*"
  },
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/xiges/generator-jhipster-siddhi/issues"
  }
}

I configured the following:

  • yo: CLI tool for running Yeoman generators.
  • generator-mocha: a generator for the mocha test-framework.
  • gulp: a front-end build tool.
  • mocha: mocha is a JavaScript test framework for Node.js programs.

3. Create a Gulp File

We use the Gulp file as our build system. Tools like Gulp are often referred to as “build tools” because they are tools for running the tasks for building a web application. Your Gulp file must be look like this.

const gulp = require('gulp');
const bumper = require('gulp-bump');
const eslint = require('gulp-eslint');
const git = require('gulp-git');
const shell = require('gulp-shell');
const fs = require('fs');
const sequence = require('gulp-sequence');
const path = require('path');
const mocha = require('gulp-mocha');
const istanbul = require('gulp-istanbul');
const nsp = require('gulp-nsp');
const plumber = require('gulp-plumber');

gulp.task('eslint', () => gulp.src(['gulpfile.js', 'generators/app/index.js', 'test/*.js'])
    // .pipe(plumber({errorHandler: handleErrors}))
        .pipe(eslint())
        .pipe(eslint.format())
        .pipe(eslint.failOnError())
);

gulp.task('nsp', (cb) => {
    nsp({ package: path.resolve('package.json') }, cb);
});

gulp.task('pre-test', () => gulp.src('generators/app/index.js')
    .pipe(istanbul({
        includeUntested: true
    }))
    .pipe(istanbul.hookRequire())
);

gulp.task('test', ['pre-test'], (cb) => {
    let mochaErr;

    gulp.src('test/*.js')
        .pipe(plumber())
        .pipe(mocha({ reporter: 'spec' }))
        .on('error', (err) => {
            mochaErr = err;
        })
        .pipe(istanbul.writeReports())
        .on('end', () => {
            cb(mochaErr);
        });
});

gulp.task('bump-patch', bump('patch'));
gulp.task('bump-minor', bump('minor'));
gulp.task('bump-major', bump('major'));

gulp.task('git-commit', () => {
    const v = `update to version ${version()}`;
    gulp.src(['./generators/**/*', './README.md', './package.json', './gulpfile.js', './.travis.yml', './travis/**/*'])
        .pipe(git.add())
        .pipe(git.commit(v));
});

gulp.task('git-push', (cb) => {
    const v = version();
    git.push('origin', 'master', (err) => {
        if (err) return cb(err);
        git.tag(v, v, (err) => {
            if (err) return cb(err);
            git.push('origin', 'master', {
                args: '--tags'
            }, cb);
            return true;
        });
        return true;
    });
});

gulp.task('npm', shell.task([
    'npm publish'
]));

function bump(level) {
    return function () {
        return gulp.src(['./package.json'])
            .pipe(bumper({
                type: level
            }))
            .pipe(gulp.dest('./'));
    };
}

function version() {
    return JSON.parse(fs.readFileSync('package.json', 'utf8')).version;
}

gulp.task('prepublish', ['nsp']);
gulp.task('default', ['static', 'test']);
gulp.task('deploy-patch', sequence('test', 'bump-patch', 'git-commit', 'git-push', 'npm'));
gulp.task('deploy-minor', sequence('test', 'bump-minor', 'git-commit', 'git-push', 'npm'));
gulp.task('deploy-major', sequence('test', 'bump-major', 'git-commit', 'git-push', 'npm'));

It’s often used to do front-end tasks like:

  • Spinning up a web server.
  • Reloading the browser automatically whenever a file is saved.
  • Using preprocessors like Sass or LESS.
  • Optimizing assets like CSS, JavaScript, and images.

So now we have finished creating a basic JHipster generator for WSO2 Siddhi. But now we have to configure Spring Boot with real-time analytics (WSO2 Siddhi).

Spring Boot + Real-Time Analytics

Spring Boot is a very popular Java-based framework for building web and enterprise-based applications. Spring framework provides a wide variety of features addressing modern business needs. As their docs taut, "Spring Boot makes it easy to create stand-alone, production-grade Spring-based applications that can 'just run.'" Spring Boot takes an opinionated view of the Spring platform and combines third-party libraries so developers can start with minimum fuss. Most Spring Boot applications need only a little bit of Spring configuration, so it’s easy to embed WSO2 Siddhi to Spring Boot. Let's see how it’s done.

Before we go any further, we have to understand the main concepts behind real-time analytics with WSO2 Siddhi. WSO2 Siddhi is a Java library which carries out real-time processing on complex events. The streaming SQL Language of Siddhi is being used to describe complex conditions from the data streams. Siddhi is able to perform both Stream and complex event processing. The below diagram shows the basic workflow of the WSO2 Siddhi 3.0.

WSO2 Siddhi Arhitecture

Now we are embedding Siddhi in a Java Spring Boot project, allowing us to use the Siddhi query language to carry out real-time processing of complex events without running a WSO2 CEP server.

Step 1: Implementing Business Service and Adding POST Rest Service for Siddhi Application

As a first step, we need to define a stream definition and Siddhi query. Stream definitions always define the format of your incoming events and the query is defined as below:

String definition = "define stream TempStream(roomNo int, temperature double,
deviceId long);"

String query = "@info(name = 'avgTemperature') " +
"from TempStream#window.time(60 sec) " +
"select avg(temperature) as temperature,deviceID " +"group by roomNo " +
"insert into AvgTempStream ;";

This Siddhi query stores incoming events for 60 econds, groups them by roomNo and calculates the average temperature. Then it inserts the results into a stream named AvgTempStream.

Step 2: Creating a Siddhi Runtime

This step involves creating a runtime representation of a siddhiAppRuntime by combining the stream definition and the Siddhi query you created in Step 1.

SiddhiManager siddhiManager = new SiddhiManager();
//Generating runtime
SiddhiAppRuntime siddhiAppRuntime = siddhiManager
.createSiddhiAppRuntime(definition+query);

The Siddhi Manager parses the Siddhi App and provides you with a Siddhi app runtime. This Siddhi app runtime is used to add callbacks and input handlers to the Siddhi app runtime.

Step 3: Registering a Callback

Siddhi has two types of callbacks:

  • Stream Callback — this subscribes to an event stream.
  • Query Callback — this subscribes to a query.

We need a callback to retrieve output events from the query. So we can register a callback to the Siddhi app runtime. When results are generated, they are sent to the receive method of this callback. Also, we can print the incoming events from an event printer which is added inside this callback.

For example:

siddhiAppRuntime.addCallback("AvgTempStream", new QueryCallback() {
            @Override
            public void receive(Event[] events) {
                EventPrinter.print(events);
            }
        });

Step 4: Sending Events

As a final step, you need to send events from the event stream to the query and you need to obtain an input handler as shown below:

//Retrieving input handler to push events into Siddhi        
InputHandler inputHandler =siddhiAppRuntime .getInputHandler("StockEventStream");         
//Starting event processing        
siddhiAppRuntime.start();         
//Sending events to Siddhi        
inputHandler.send(new Object[]{2, 23.0, 100L});

Refer to these files at the bottom of the article for the exact implementation of the Business Service and adding a POST Rest Service to our Siddhi application.

@Service
public class RealtimeAnalyticsServiceImpl extends StreamCallback implements
        RealtimeAnalyticsService,ApplicationListener<ContextRefreshedEvent> {

    private  static final Logger log = LoggerFactory.getLogger(RealtimeAnalyticsServiceImpl.class);
    private SiddhiManager siddhiManager;
    private SiddhiAppRuntime siddhiAppRuntime;
    private InputHandler temperatureInputHandler;
    private static final String script = "define stream TempStream(roomNo int, temperature double, deviceId long); " +
            " " +
            "@info(name = 'avgTemperature') " +
            "from TempStream#window.time(60 sec)  " +
            "select avg(temperature) as temperature,deviceId " +
            "group by roomNo " +
            "insert into AvgTempStream ;";

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {

        log.info("Realtime analytics engine starting up");
        siddhiManager = new SiddhiManager();
        Map<String,String> stockConfig = new HashMap<>();

        InMemoryConfigManager inMemoryConfigManager = new InMemoryConfigManager(stockConfig,Collections.emptyMap());
        siddhiManager.setConfigManager(inMemoryConfigManager);
        // siddhiManager.setExtension();
        siddhiAppRuntime = siddhiManager.createSiddhiAppRuntime(script);

        siddhiAppRuntime.addCallback("AvgTempStream",this);

        temperatureInputHandler = siddhiAppRuntime.getInputHandler("TempStream");

        siddhiAppRuntime.start();

    }



    @PreDestroy
    private void onApplicationExit(){

        log.info("Shutting down  WSO2 Siddhi");
        siddhiAppRuntime.shutdown();
        siddhiManager.shutdown();
    }

    @Override
    public void inboundDataEvent(String tempId,  TemperatureData data) {
        try {
            temperatureInputHandler.send(new Object[]{data.getRoomNo(),data.getTemperature(),data.getDeviceId()});
        } catch (InterruptedException e) {
            log.error("error occurred while feeding temperature data ", e);
        }
    }



    @Override
    public void receive(Event[] events) {



        // EventPrinter.print(events);
        log.debug("Avg Temperature",events);

    }
)
}
public interface RealtimeAnalyticsService {

    void inboundDataEvent (String temperatureId , TemperatureData data);
}
public class TemperatureData {
    private int roomNo;
    private double temperature;
    private Long deviceId;


    public int getRoomNo() {
        return  roomNo;
    }

    public void setRoomNo(int roomNo) {
        this.roomNo = roomNo;
    }

    public double getTemperature (){
        return temperature;
    }

    public void setTemperature(double temperature) {
        this.temperature = temperature;
    }


    public Long getDeviceId() {
        return deviceId;
    }

    public void setDeviceID(Long deviceId) {
        this.deviceId = deviceId;
    }


    @Override
    public String toString(){
        return "TemperatureData{" +
                "roomNo='" + roomNo + '\'' +
                ", temperature=" +temperature +
                ", deviceId='" + deviceId+ '\'' +
                '}';
    }


}
@RestController
public class TemperatureDataResource {
    private final Logger log = LoggerFactory.getLogger(TemperatureDataResource.class);
    @Autowired
    RealtimeAnalyticsService realtimeAnalyticsService;

    @PostMapping("/temperature-data")

    public ResponseEntity<Void> addTemperatureData(@PathVariable String tempId, @RequestBody TemperatureData data){

        TemperatureData temperatureData = new  TemperatureData();
        temperatureData.setRoomNo(data.getRoomNo());
        temperatureData.setDeviceID(data.getDeviceID());
        temperatureData.setTemperature(data.getTemperature());
        realtimeAnalyticsService.inboundDataEvent(tempId,data);
        return new ResponseEntity<Void>(HttpStatus.OK);
    }

}

Conclusion

Creating a JHipster module with WSO2 Siddhi is an easy way to simplify your microservice generation, especially if your microservice uses the same configuration. Since it is a module, it’s very easy to add functionalities and meet your needs. Our company uses this real-time analytics for making real-time predictions and tracking with our IoT products.

Below is the GitHub repository with the module used in this blog. Feel free to fork it and make changes to match your company requirements!

Now you can download JHipster generator for Siddhi by using this command

npm install g generator-jhipster-siddhi

Then run the module on a JHipster generated application.

yo jhipster-siddhi

Soon this module will be available in the JHipster marketplace (after the JHipster team verifies it).

Link for the source code - https://github.com/xiges/generator-jhipster-siddhi.

Link for the npm registry - https://www.npmjs.com/package/generator-jhipster-siddhi.

If you have any suggestions or enhancements you want to see in the module, please create an issue in the Xiges GitHub repository — https://github.com/xiges/generator-jhipster-siddhi/issues.

That’s all I have for this topic. Thanks for reading. Until next time, happy coding!

References

  • https://yeoman.io/authoring/composability.html
  • https://www.jhipster.tech/modules/creating-a-module
  • https://www.baeldung.com/jhipster
  • https://yeoman.io/authoring/file-system.html
  • https://gulpjs.com/docs/en/getting-started/javascript-and-gulpfiles
  • https://siddhi-io.github.io/siddhi/#try-siddhi-with-wso2-stream-processor
  • https://docs.wso2.com/display/CEP400/SiddhiQL+Guide+3.0
Spring Framework Spring Boot JHipster Analytics Database application Event

Published at DZone with permission of Tharindu Vibuddha Gangodagama. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • OpenVPN With Radius and Multi-Factor Authentication
  • Building a REST API With AWS Gateway and Python
  • Java Concurrency: LockSupport
  • Tackling the Top 5 Kubernetes Debugging Challenges

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: