What is the right way to use Cassandra driver from a web application
Asked Answered
C

1

12

I want to build a RESTful API with Java and Cassandra 2.x (on Jersey framework). I'm new to both technologies so I would like to ask you is that the correct way to integrate and share Cassandra driver.

0. Get the driver though Maven

<dependency>
            <groupId>com.datastax.cassandra</groupId>
            <artifactId>cassandra-driver-core</artifactId>
            <version>2.0.3</version>
</dependency>

1. Wrap driver's functionality with a Client class:

package com.example.cassandra;

import com.datastax.driver.core.*;

public class Client {

    private Cluster cluster;
    private Session session;

    public Client(String node) {
        connect( node );
    }

    private void connect(String node) {
        cluster = Cluster.builder()
            .addContactPoint(node)
            .build();

        session = cluster.connect();
    }

    public ResultSet execute( String cql3 ) {
        return session.execute( cql3 );
    }

    public void close() {
      cluster.close();
    }

}

2. I insatiate the client in ContextListener and share it though context attribute

package com.example.listener;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import com.example.cassandra.Client;

public class ExampleContextListener implements ServletContextListener {

    Client cassandraClient;

    public void contextInitialized(ServletContextEvent servletContextEvent) {
        ServletContext ctx = servletContextEvent.getServletContext();

        cassandraClient = new Client( ctx.getInitParameter( "DBHost" ) );
        ctx.setAttribute( "DB", cassandraClient );
    }

    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        cassandraClient.close();
    }

}

3. Now I get the client from servlet's context and use it

Client client =  (Client) context.getAttribute("DB");
client.execute("USE testspace;");

ResultSet rs = client.execute("SELECT * from users;");
for (Row row : rs ) {
    output += row.getString("lname") + "|";
}

Is that the correct way to do it (both from performance and architectural point of view)?

Full example available on: https://github.com/lukaszkujawa/jersey-cassandra

Cheeseburger answered 10/7, 2014 at 23:38 Comment(1)
Hi, I was wondering if you could tell me the best way the you adopted in your web app?Folklore
D
11

I just developed what you are going to develop. What you wrote works but it's not my favourite approach. I'd rather create a Singleton (since 1 session is enough for an application). Following Joshua Bloch enum's singleton's pattern here is what I did

    public enum Cassandra {

        DB;

        private Session session;
        private Cluster cluster;
        private static final Logger LOGGER = LoggerFactory.getLogger(Cassandra.class);

        /**
         * Connect to the cassandra database based on the connection configuration provided.
         * Multiple call to this method will have no effects if a connection is already established
         * @param conf the configuration for the connection
         */
        public void connect(ConnectionCfg conf) {
            if (cluster == null && session == null) {
                cluster = Cluster.builder().withPort(conf.getPort()).withCredentials(conf.getUsername(), conf.getPassword()).addContactPoints(conf.getSeeds()).build();
                session = cluster.connect(conf.getKeyspace());
            }
            Metadata metadata = cluster.getMetadata();
            LOGGER.info("Connected to cluster: " + metadata.getClusterName() + " with partitioner: " + metadata.getPartitioner());
            metadata.getAllHosts().stream().forEach((host) -> {
                LOGGER.info("Cassandra datacenter: " + host.getDatacenter() + " | address: " + host.getAddress() + " | rack: " + host.getRack());
            });
        }

        /**
         * Invalidate and close the session and connection to the cassandra database
         */
        public void shutdown() {
            LOGGER.info("Shutting down the whole cassandra cluster");
            if (null != session) {
                session.close();
            }
            if (null != cluster) {
                cluster.close();
            }
        }

        public Session getSession() {
            if (session == null) {
                throw new IllegalStateException("No connection initialized");
            }
            return session;
        }
}

And in the context listener I call connect or shutdown. Since all exceptions in new driver are unchecked my tip for you is to create your own implementation of the Jersey ExceptionMapper mapping DriverException. One more thing, think about working with PreparedStatements rather than Strings so that Cassandra parse the query only once. In my application I followed the above patterns also for the queries (an enum singleton that prepare statements when loaded first time and then expose methods to use these statements).

HTH, Carlo

Demure answered 11/7, 2014 at 6:35 Comment(4)
Thank you again Carlo. As you can see I gave up with PHP and switched to Java. There won't be any issue with drivers here ;)Cheeseburger
Definitely no issue. The new driver is very well made and what Jersey offers out of the box for restful is just amazing. You'll not regret your choice! Enjoy, CarloDemure
Hi Carlo, you mean to say that only one singleton Cassandra session object is enough for one web application. if my application user count is 1000. will single session object satisfy my scenario? please clarify my query. thanksHein
Hi Gnana, I mean that you can create only one Session object. I've used it with more than 1000 users without any issue. Give a look here: docs.datastax.com/en/drivers/java/2.0/com/datastax/driver/core/… -- "Session instances are thread-safe and usually a single instance is enough per application" - HTH, CarloDemure

© 2022 - 2024 — McMap. All rights reserved.