Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 937
Lukasz Kujawa Avatar asked Jul 10 '14 23:07

Lukasz Kujawa


People also ask

How does Cassandra driver work?

The driver architecture is based on layers. At the bottom lies the driver core. This core handles everything related to the connections to a Cassandra cluster (for example, connection pool, discovering new nodes, etc.) and exposes a simple, relatively low-level API on top of which higher level layers can be built.

What is Cassandra driver?

A modern, feature-rich and highly tunable Node. js client library for Apache Cassandra and DSE using exclusively Cassandra's binary protocol and Cassandra Query Language.

What is the JDBC URL for Cassandra?

jdbc:Cassandra:URL=http://myCassandra.com;User=myUser;Password=myPassword; Click Test Connection.


1 Answers

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

like image 144
Carlo Bertuccini Avatar answered Sep 22 '22 08:09

Carlo Bertuccini