13 min to complete
In a previous lesson, we explained how a ScyllaDB Administrator restores and backs-up a cluster. As the number of mutants is on the rise, Division 3 decided that we must use more applications to connect to the mutant catalog and decided to hire Java developers to create powerful applications that can monitor the mutants. This lesson will explore how to connect to a ScyllaDB cluster using the Phantom library for Scala: a Scala-idiomatic wrapper over the standard Java driver.
When creating applications that communicate with a database such as ScyllaDB, it is crucial that the programming language being used includes support for database connectivity. Since ScyllaDB is compatible with Cassandra, we can use any of the available Cassandra libraries. For example, in Go, there is Gocql and Gocqlx. In Node.js, there is the cassandra-driver. For the JVM, we have the standard Java driver available. Scala applications on JVM can use it, but a library tailored for Scala’s features offers a more enjoyable and type-safe development experience. Since Division 3 wants to start investing in Scala, let’s begin by writing a sample Scala application.
Creating a Sample Scala Application
The sample application that we will create connects to a ScyllaDB cluster, displays the contents of the Mutant Catalog table, inserts and deletes data, and shows the table’s contents after each action. First, we will go through each section of the code used and then explain how to run the code in a Docker container that accesses the ScyllaDB Mutant Monitoring cluster. If you haven’t downloaded the code yet, you can do so by following the instructions in the section “Set-up a ScyllaDB Cluster” below.
You can see the file here: scylla-code-samples/mms/scala/scala-app/src/main/scala/com/scylla/mms/App.scala.
As mentioned, we’re using the Phantom library for working with ScyllaDB. The library’s recommended usage pattern involves modeling the database, table, and data service as classes. We’ll delve into those in a future lesson.
First, let’s focus on how we set up the connection itself. We start by importing the library:
This import brings all the necessary types and implicits into scope. The main entry point for our application is the App object’s main method. In it, we set up the connection to the cluster:
Next, in a list, we specify the DNS names through which the application contacts ScyllaDB and the keyspace to use.
Finally, we instantiate the MutantsDatabase and the MutantsService classes. These classes represent, respectively, the collection of tables in use by our application and a domain-specific interface to interact with those tables.
We’re not operating on low-level types like ResultSet or Row objects with the Phantom library, but rather on strongly-typed domain objects. Specifically, for this lesson, we will be working with a Mutant data type defined as follows:
You can find the file here: scylla-code-samples/mms/scala/scala-app/src/main/scala/com/scylla/mms/model/Mutant.scala
The MutantsService class contains implementations of the functionality required for this lesson. You can find it here:
scylla-code-samples/mms/scala/scala-app/src/main/scala/com/scylla/mms/service/MutantService.scala.
Let’s consider how we would fetch all the mutant definitions present in the table:
Phantom provides a domain-specific language for interacting with ScyllaDB. Instead of threading CQL strings through our code, we’re working with a strongly-typed interface that verifies, at compile-time, that the queries we are generating are correct (according to the data type definitions).
In this case, we are using the select method to fetch all Mutant records from ScyllaDB. Note that by default, Phantom uses Scala’s Future data type for representing all the ScyllaDB operations. Behind the scenes, Phantom uses a non-blocking network I/O to efficiently perform all of the queries. If you’d like to know more about Future, this is a good tutorial.
Moving forward, the following method allows us to store an instance of the Mutant data type into ScyllaDB:
Everything is strongly typed, and as such, we’re using actual instances of our data types to interact with ScyllaDB. This gives us increased maintainability of our codebase. If we refactor the Mutant data type, this code will no longer compile, and we’ll be forced to fix it.
And lastly, here’s a method that deletes a mutant with a specific name:
We can see that Phantom’s DSL can represent predicates in a type-safe fashion as well. We’re forced to use a matching type for comparing the firstName and lastName.
We can sequence the calls to these methods in our main entry point using a for comprehension on the Future values returned. The code below is from the following file:
scylla-code-samples/mms/scala/scala-app/src/main/scala/com/scylla/mms/App.scala
In this example, we execute all the futures sequentially (with some additional callbacks registered on them for printing out the information).
With the coding part done, let’s set up a ScyllaDB Cluster and then run the sample application in Docker.
Set-up a ScyllaDB Cluster
The example requires a single DC cluster. Follow this procedure to remove previous clusters and set up a new cluster.
Once the cluster is up, we’ll create the catalog keyspace and populate it with data.
The first task is to create the keyspace named catalog for the mutants’ catalog.
docker exec -it scylla-node1 cqlsh
CREATE KEYSPACE catalog WITH REPLICATION = { 'class' : 'NetworkTopologyStrategy','DC1' : 3};
Now that the keyspace is created, it is time to create a table to hold the mutant data.
use catalog;
CREATE TABLE mutant_data (
first_name text,
last_name text,
address text,
picture_location text,
PRIMARY KEY((first_name, last_name)));
Now let’s add a few mutants to the catalog with the following statements:
insert into mutant_data ("first_name","last_name","address","picture_location") VALUES ('Bob','Loblaw','1313 Mockingbird Lane', 'http://www.facebook.com/bobloblaw');
insert into mutant_data ("first_name","last_name","address","picture_location") VALUES ('Bob','Zemuda','1202 Coffman Lane', 'http://www.facebook.com/bzemuda');
insert into mutant_data ("first_name","last_name","address","picture_location") VALUES ('Jim','Jeffries','1211 Hollywood Lane', 'http://www.facebook.com/jeffries');
Building the Scala Example
If you previously built the Scala Docker, you can skip directly to the section Running the Scala Example below.
Otherwise, exit the CQL Shell if you’re still in it:
exit
To build the application in Docker, change into the scala subdirectory in scylla-code-samples:
cd scylla-code-samples/mms/scala
Now we can build and run the container:
docker build -t scala-app .
docker run -d --net=mms_web --name some-scala-app scala-app
To connect to the shell of the container, run the following command:
docker exec -it some-scala-app sh
Running the Scala Example
Finally, the sample Scala application can be run:
java -jar scala-app/target/scala-2.13/mms-scala-app-assembly-0.1.0-SNAPSHOT.jar
The output of the application will be:
Conclusion
In this lesson, we explained how to create a sample Scala application that executes a few basic CQL operations on a ScyllaDB cluster using the Phantom library. These were only the basics, and there are more exciting topics that Division 3 wants developers to explore. In the next lesson, we will review the structure of an application using the Phantom library.
In the meantime, please be safe out there and continue to monitor the mutants!
*This lesson was written with the help of Itamar Ravid. Itamar is a Distributed systems engineer with a decade of experience in a wide range of technologies. He focuses on JVM-based microservices in Scala using functional programming. Thank you, Itamar!