Deploying Polyglot (RDBMS + NoSQL) Applications on Openshift


Introduction

Platform as a Service (PaaS) takes care of infrastructure, middleware, configuration and other platform related worries from developers and allows them to focus on their key competence, i.e. designing and developing their domain applications. In this article I am going to demonstrate how to build a “Polyglot Application” involving data storage and retrieval in RDBMS and a NoSQL with minimal effort.

What is a Polyglot Application?

In the context of database applications, a polyglot application involves persistence is multiple databases.  This is no difficult when business objects involved are stored in RDBMS databases. Things get complex when these databases are heterogeneous in nature (e.g. combination of different NoSQLs and possibly RDBMS too). Take for instance a case of Users who has many addresses. User is required to be stored in MongoDB, while addresses need to be stored in RDBMS.

             (1-M)              
 User       --------->    Address
(MongoDB)                 (MySQL)

This kind of use case poses some really tough challenges like:

1. Learning Curve

NoSQL databases have their own API (and there are high level clients too). They have a long learning curve and working on them would usually require developers who already have a prior experience with them.

2. Development Complexity

Managing different business entities that are going to be saved in heterogeneous systems poses a challenge with development at data access layer. Your code will be using different APIs, which will make it difficult to develop, test and maintain your code.

3. Deployment Complexity

Different NoSQL databases have their own setup, structure and deployment mechanism. They need to be tuned for best performance and application needs. Managing these varied infrastructure on our own is challenging in terms of costs and complexity. A solution provider should be spending time on solving business problems in the best possible way, rather than dealing and struggling with challenges that these technologies pose.

So, What do we do?

First two of these problems could be solved by using Kundera as an object-datastore mapping library. Kundera supports Polyglot Persistence and as we’ll see, it requires almost NO effort in managing different entities for different databases. Third problem is solved by using a PaaS provider. Your PaaS provider would be responsible for managing databases, hardware resources, application servers etc as you develop your application. OpenShift from RedHat is an application platform in the cloud where application developers and teams can build, test, deploy, and run their applications. OpenShift provides different “cartridges” that you can add to your application to suit your needs. MongoDB and MySQL are among them. We shall build an application that reads and writes business entities into MySQL and MongoDB using Kundera.

Let’s get Ready

We’re now ready to build our polyglot application involving two entities, User and Address. User will be stored in MongoDB while Address in MySQL. Here are the steps you need to follow:

1. Signup and Login to OpenShift.
2. Add a Java Application based on Tomcat 7 (JBoss EWS 2.0 Cartridge)
3. Add Cartridges – MongoDB Nosql Database 2.2 and MySQL database 5.1
4. Write your NoSQL application.
5. Push your code to Git repository created on OpenShift. (Artifacts are deployed to servers automatically).
6. Test your application.
 

Signup and Login

1. Visit: https://www.openshift.com/
2. Click on SignUp and enter details. Verify and Login.
 

Create Application

1. Click on My Applications -> Add Application
2. Select Java in Drop down box.
3. Click on Tomcat (JBoss EWS 2.0)
4. Give your Application a name and click “Create Application”.
 
 

Add Application

PaasMongo_Create_Application_2

Add Cartridges

1. Click on My Aplications -> <Your Application Name>
2. Click “Add Cartridge”
3. Add “MySQL Database 5.1″, while adding this cartridge, note down Database authentication credentials and schema name, better take a screen print)
4. Repeat above step for adding Cartridge “MongoDB NoSQL database 2.2″.
 
 

PaasMongoAddCartridge

Write NoSQL application

We’ll demonstrate writing a simple cross-datastore read+write application on MySQL and MongoDB.

1. Add dependencies in pom.xml

Add Kundera repositories under repositories tag inside pom.xml:

<repository>
	<id>sonatype-nexus</id>
	<name>Kundera Public Repository</name>
	<url>https://oss.sonatype.org/content/repositories/releases</url>
	<releases>
		<enabled>true</enabled>
	</releases>
	<snapshots>
		<enabled>false</enabled>
	</snapshots>
</repository>
<repository>
	<id>kundera-missing</id>
	<name>Kundera Public Missing Resources Repository</name>
	<url>http://kundera.googlecode.com/svn/maven2/maven-missing-resources</url>
	<releases>
		<enabled>true</enabled>
	</releases>
	<snapshots>
		<enabled>true</enabled>
	</snapshots>
</repository>
Add following dependencies under dependencies tag inside pom.xml:
<dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 <version>5.1.6</version>
 </dependency>
 <!-- Kundera Dependency -->
 <dependency>
 <groupId>com.impetus.client</groupId>
 <artifactId>kundera-rdbms</artifactId>
 <version>2.6</version>
 </dependency>
 <dependency>
 <groupId>com.impetus.core</groupId>
 <artifactId>kundera-core</artifactId>
 <version>2.6</version>
 </dependency>
 <dependency>
 <groupId>com.impetus.client</groupId>
 <artifactId>kundera-mongo</artifactId>
 <version>2.6</version>
 </dependency>
 <dependency>
 <groupId>javax.servlet</groupId>
 <artifactId>servlet-api</artifactId>
 <version>2.5</version>
 </dependency>

2. Write Entity Classes

User.java


import javax.persistence.CascadeType;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;

@Entity
@Table(name = "User", schema = "PaaSMongo@mongo-openshift_pu")
public class User {
 @Id
 @Column(name = "ID")
 private int id;

@Column(name = "NAME")
 private String name;

@Column(name = "AGE")
 private int age;

@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
 @JoinColumn(name = "ADDRESS_ID")
 private Address address;

//Getters and setters omitted

}

Address.java


import javax.persistence.Column;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "ADDRESS", schema = "PaaSMongo")
public class Address
{
 @Id
 @Column(name = "ADDRESS_ID")
 private String addressId;

@Column(name = "STREET")
 private String street;

//Getters and setters omitted

}

3. Write persistence.xml


<persistence xmlns="http://java.sun.com/xml/ns/persistence"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence

https://raw.github.com/impetus-opensource/Kundera/Kundera-2.0.4/kundera-core/src/test/resources/META-INF/persistence_2_0.xsd"

 version="2.0">
 <!-- Persistence Units for Twitter application -->

<persistence-unit name="mongo-openshift_pu">
 <provider>com.impetus.kundera.KunderaPersistence</provider>
 <class>com.impetus.kundera.mongo.entities.User</class>
 <exclude-unlisted-classes>true</exclude-unlisted-classes>
 <properties>
 <property name="kundera.nodes" value="127.5.210.1" />
 <property name="kundera.port" value="27017" />
 <property name="kundera.keyspace" value="PaaSMongo" />
 <property name="kundera.dialect" value="mongodb" />
 <property name="kundera.client.lookup.class"
 value="com.impetus.client.mongodb.MongoDBClientFactory" />
 <property name="kundera.cache.provider.class"
 value="com.impetus.kundera.cache.ehcache.EhCacheProvider" />
 <property name="kundera.cache.config.resource" value="/ehcache-test.xml" />
 <property name="kundera.username" value="admin" />
 <property name="kundera.password" value="eaGbuGfS1ct4" />
 </properties>
 </persistence-unit>

<persistence-unit name="rdbms-openshift_pu">
 <provider>com.impetus.kundera.KunderaPersistence</provider>
 <class>com.impetus.kundera.mongo.entities.Address</class>
 <exclude-unlisted-classes>true</exclude-unlisted-classes>
 <properties>
 <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect" />
 <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
 <property name="hibernate.connection.url" value="jdbc:mysql://127.5.210.1:3306/PaaSMongo" />
 <property name="hibernate.connection.username" value="adminlRwnUcy" />
 <property name="hibernate.connection.password" value="jvsq6w131Y1p" />
 <property name="kundera.client.lookup.class" value="com.impetus.client.rdbms.RDBMSClientFactory" />
 <property name="hibernate.show_sql" value="true" />
 <property name="hibernate.format_sql" value="true" />
 </properties>
 </persistence-unit>

</persistence>

4. Write web.xml


<?xml version="1.0" encoding="UTF-8"?>

<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
 metadata-complete="false">

<listener>

 <listener-class>com.impetus.kundera.mongo.listener.StartUpListener</listener-class>
 </listener>

</web-app>

5. Write CRUD operations

OperationHandler.java


import java.util.List;

import com.impetus.kundera.mongo.entities.User;

public interface OperationHandler
{

void saveUser(User user);

User findUserById(Class clazz, Object id);

List<User> findUserByName(String clazz, String name);

List<User> findUserByAge(String clazz, int age);

List<User> findAllUser(String clazz);
}

OperationHandlerImpl.java


import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;

import com.impetus.kundera.mongo.entities.User;

public class OperationHandlerImpl implements OperationHandler {

private EntityManagerFactory emf;

private EntityManager em;

@Override
 public void saveUser(User user) {
 getEntityManager();
 em.persist(user);
 }

@Override
 public User findUserById(Class clazz, Object id) {
 getEntityManager();
 return em.find(User.class, id);
 }

@Override
 public List<User> findUserByName(String clazz, String name) {

getEntityManager();
 Query q = em.createQuery("Select u from " + clazz + " u "
 + "where u.name = " + name);
 return q.getResultList();

}

@Override
 public List<User> findUserByAge(String clazz, int age) {
 getEntityManager();
 Query q = em.createQuery("Select u from " + clazz + " u "
 + "where u.age = " + age);
 return q.getResultList();
 }

@Override
 public List<User> findAllUser(String clazz) {
 getEntityManager();
 Query q = em.createQuery("Select u from " + clazz + " u ");
 return q.getResultList();
 }

private EntityManager getEntityManager() {

if (emf == null) {
 emf = Persistence
 .createEntityManagerFactory("mongo-openshift_pu,rdbms-openshift_pu");
 em = emf.createEntityManager();

} else if (em == null) {
 em = emf.createEntityManager();
 }
 return em;
 }

public void removeAll(String clazz) {
 getEntityManager();
 Query q = em.createQuery("Delete from " + clazz + " u ");
 }

public void close() {
 em.close();
 emf.close();
 }

public void clear() {
 em.clear();
 }
}

6. Create Startup Listener


import javax.servlet.ServletContextEvent;</pre>
import javax.servlet.ServletContextListener;

import com.impetus.kundera.mongo.entities.Address;
import com.impetus.kundera.mongo.entities.User;
import com.impetus.kundera.mongo.handler.OperationHandlerForUser;

public class StartUpListener implements ServletContextListener {
 OperationHandlerForUser handler = null;
 int userId1;
 int userId2;

@Override
 public void contextDestroyed(ServletContextEvent arg0) {

 }

@Override
 public void contextInitialized(ServletContextEvent arg0) {
 handler = new OperationHandlerImpl();
 persist();
 find();
 }

private void persist() {
 Double d = Math.random();
 Long t1 = System.currentTimeMillis();
 Address address1 = new Address();
 address1.setAddressId(t1.toString());
 address1.setStreet("AAAAAAA");

Address address2 = new Address();
 address2.setAddressId(t1.toString() + "1");
 address2.setStreet("BBBBBBB");

User user1 = new User();
 userId1 = d.intValue();
 user1.setPersonId(d.intValue());
 user1.setPersonName("Kuldeep");
 user1.setAge(24);
 user1.setAddress(address1);

User user2 = new User();
 userId2 = d.intValue() + 1;
 user2.setPersonId(d.intValue() + 1);
 user2.setPersonName("Amresh");
 user2.setAge(30);
 user2.setAddress(address2);

handler.saveUser(user1);
 handler.saveUser(user2);
 handler.clear();

}

private void find() {
 User user1 = handler.findUserById(User.class, userId1);
 System.out.println(user1.getPersonName());
 System.out.println(user1.getAge());
 System.out.println(user1.getAddress().getAddressId());
 System.out.println(user1.getAddress().getStreet());

User user2 = handler.findUserById(User.class, userId2);
 System.out.println(user2.getPersonName());
 System.out.println(user2.getAge());
 System.out.println(user2.getAddress().getAddressId());
 System.out.println(user2.getAddress().getStreet());
 }
}

Push code and test Application

1. Copy your public keys in operating system to My Account -> Public Keys
2. Clone your git repository.
3. Commit code and push.
4. Test data written into database from MySQL and MongoDB console and check output printed in Tomcat logs.
 

Conclusion

As we see, Kundera + Openshift bring a lot of ease in terms of development and deployment. All we needed was a few clicks on Openshift web page and a simple configuration in persistence.xml. The thing we actually focused on was writing entities and CRUD code. Kundera also supports REST Based Access for data stored in multiple datastores. This requires adding a JAX-RS servlet entry in web-application deployment descriptor. This opens a brand new avenue for you to expose your data to different applications.

About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s