ScyllaDB is a highly scalable NoSQL database, fully compatible with Cassandra Query Language (CQL). Pairing it with Quarkus, a lightweight, cloud-native Java framework, can help small teams build efficient and scalable applications for Kubernetes environments. This article will guide you through setting up ScyllaDB with Quarkus, Kafka integration, Hexagonal architecture using jMolecules, and a robust monitoring solution.


Why Choose Quarkus for ScyllaDB?

Quarkus is designed for cloud-native development, making it an excellent choice for Kubernetes deployments. Its lightweight runtime, fast startup times, and rich ecosystem of extensions simplify development and integration with ScyllaDB. Here’s why it’s ideal for small teams:

  • Developer Productivity: Live coding (hot reload) reduces feedback loop time.
  • Cloud-Native: Built-in Kubernetes integration for seamless deployment.
  • Performance: Optimized for low memory usage and fast startup times.

Setting Up ScyllaDB with Quarkus

1. Add the Cassandra Client Extension

Add the Quarkus Cassandra extension to your project to enable interaction with ScyllaDB:

For Gradle:

implementation("io.quarkus:quarkus-cassandra-client")

2. Configure ScyllaDB in application.properties

Provide connection details for your ScyllaDB cluster:

quarkus.cassandra.contact-points=127.0.0.1:9042
quarkus.cassandra.local-datacenter=datacenter1
quarkus.cassandra.keyspace=my_keyspace
quarkus.cassandra.request.timeout=10s

Replace 127.0.0.1:9042 with your ScyllaDB cluster’s contact points.

3. Use the Cassandra Client in Your Application

Inject the CqlSession object to interact with ScyllaDB:

import com.datastax.oss.driver.api.core.cql.CqlSession;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
 
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
 
@ApplicationScoped
public class ScyllaDbService {
 
    @Inject
    CqlSession session;
 
    public void insertData() {
        String query = "INSERT INTO my_table (id, name) VALUES (?, ?)";
        session.execute(SimpleStatement.newInstance(query, "123", "Test"));
    }
 
    public void fetchData() {
        String query = "SELECT id, name FROM my_table";
        session.execute(SimpleStatement.newInstance(query))
                .all()
                .forEach(row -> System.out.println(row.getString("name")));
    }
}

4. Optimize Connection Pooling

Fine-tune connection pooling for performance:

quarkus.cassandra.session.builder.connection.pool.local.max=10
quarkus.cassandra.session.builder.connection.pool.remote.max=5
quarkus.cassandra.session.builder.idle.timeout=30s

Integrating Kafka with Quarkus

Apache Kafka is a powerful event streaming platform and a great fit for modern architectures. Quarkus provides seamless Kafka integration through its quarkus-kafka-client extension.

1. Add Kafka Extension

Include the Kafka extension in your project:

implementation("io.quarkus:quarkus-smallrye-reactive-messaging-kafka")

2. Configure Kafka in application.properties

Provide Kafka broker details:

mp.messaging.incoming.my-topic.connector=smallrye-kafka
mp.messaging.incoming.my-topic.topic=my_topic
mp.messaging.incoming.my-topic.value.deserializer=org.apache.kafka.common.serialization.StringDeserializer
 
mp.messaging.outgoing.my-topic.connector=smallrye-kafka
mp.messaging.outgoing.my-topic.topic=my_topic
mp.messaging.outgoing.my-topic.value.serializer=org.apache.kafka.common.serialization.StringSerializer

3. Implement a Kafka Listener

Set up a reactive messaging listener:

import org.eclipse.microprofile.reactive.messaging.Incoming;
import javax.enterprise.context.ApplicationScoped;
 
@ApplicationScoped
public class KafkaConsumer {
 
    @Incoming("my-topic")
    public void consume(String message) {
        System.out.println("Received message: " + message);
    }
}

4. Send Messages to Kafka

Publish messages to Kafka using the reactive messaging API:

import org.eclipse.microprofile.reactive.messaging.Channel;
import org.eclipse.microprofile.reactive.messaging.Emitter;
import javax.inject.Inject;
 
@ApplicationScoped
public class KafkaProducer {
 
    @Inject
    @Channel("my-topic")
    Emitter<String> emitter;
 
    public void sendMessage(String message) {
        emitter.send(message);
    }
}

Adopting Hexagonal Architecture with jMolecules

Hexagonal architecture (also known as Ports and Adapters) promotes separation of concerns, making applications easier to maintain and extend. Using jMolecules helps enforce this pattern.

1. Define Domain Model

Create your domain model with jMolecules annotations:

import org.jmolecules.ddd.annotation.AggregateRoot;
import org.jmolecules.ddd.annotation.Entity;
 
@AggregateRoot
public class Order {
 
    private String id;
    private List<Item> items;
 
    public Order(String id, List<Item> items) {
        this.id = id;
        this.items = items;
    }
 
    public void addItem(Item item) {
        this.items.add(item);
    }
}
 
@Entity
public class Item {
    private String name;
    private int quantity;
 
    public Item(String name, int quantity) {
        this.name = name;
        this.quantity = quantity;
    }
}

2. Implement Adapters

Create adapters for infrastructure and application layers:

Application Service:

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
 
@ApplicationScoped
public class OrderService {
 
    @Inject
    OrderRepository orderRepository;
 
    public void createOrder(String id, List<Item> items) {
        Order order = new Order(id, items);
        orderRepository.save(order);
    }
}

Infrastructure Adapter:

import javax.enterprise.context.ApplicationScoped;
 
@ApplicationScoped
public class OrderRepositoryImpl implements OrderRepository {
 
    @Override
    public void save(Order order) {
        // Save logic (e.g., using ScyllaDB)
    }
}

Monitoring Solution

For robust monitoring, consider using Prometheus and Grafana, along with tools like Loki for log aggregation.

1. Add Prometheus Extension

Include the Prometheus extension in your project:

implementation("io.quarkus:quarkus-smallrye-metrics")

2. Expose Metrics

Quarkus automatically exposes metrics at /q/metrics. Configure Kubernetes ServiceMonitor to scrape these metrics.

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: quarkus-service-monitor
spec:
  selector:
    matchLabels:
      app: quarkus-app
  endpoints:
  - port: http
    path: /q/metrics

3. Add Logging with Loki

Use Loki for centralized logging. Integrate Loki with Quarkus:

  1. Configure JSON logging in application.properties:

    quarkus.log.console.json=true
    quarkus.log.level=INFO
  2. Forward logs to Loki using tools like Fluentd or Promtail.

4. Visualize in Grafana

  • Import dashboards for Prometheus and Loki in Grafana.
  • Use pre-built Quarkus dashboards for detailed insights into application performance.

Deploying ScyllaDB in Kubernetes

1. Use ScyllaDB Operator

The ScyllaDB Operator simplifies managing clusters in Kubernetes:

apiVersion: scylla.scylladb.com/v1
kind: ScyllaCluster
metadata:
  name: scylla-cluster
spec:
  version: "5.0.0"
  datacenter:
    name: dc1
    racks:
      - name: rack1
        members: 3

Deploy the manifest with:

kubectl apply -f scylla-cluster.yaml

2. Resource Optimization

Define resource requests and limits for each pod:

resources:
  requests:
    memory: "128Mi"
    cpu: "500m"
  limits:
    memory: "512Mi"
    cpu: "1"

By combining Quarkus, ScyllaDB, Kafka, Hexagonal architecture with jMolecules, and robust monitoring tools, small teams can efficiently build and maintain scalable, cloud-native applications on Kubernetes. This comprehensive approach ensures high performance, maintainability, and observability for modern software systems.