Wednesday, October 26, 2022

Quarkus Cassandra client CRUD

Quarkus A Kubernetes Native Java stack tailored for OpenJDK HotSpot and GraalVM, crafted from the best of breed Java libraries and standards.

Apache Cassandra is an open source NoSQL distributed database trusted by thousands of companies for scalability and high availability without compromising performance. Linear scalability and proven fault-tolerance on commodity hardware or cloud infrastructure make it the perfect platform for mission-critical data.


CREATING A QUARKUS APPLICATION












Integration Quarkus  with Cassandra.

  • Configuration Quarkus and Cassandra.
  • Define Datas models, Repository and Services.
  • Quarkus Resource.

Quarkus Rest  CRUD API 

MethodsUrls
GET/products
GET/products/{id}
POST/products
PUT/products/{id}
DELETE/products

Technology

  • Java 17
  • Quarkus 2.13.3.Final
  • Cassandra 4.0.7 
  • Maven 
  • Postman 
  • IntelliJ IDEA 
  • Docker

Project Structure


Configuration  Quarkus Spring Data PostgreSQL



pom.xml


<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.henry</groupId>
<artifactId>quarkus-cassandra-client</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<compiler-plugin.version>3.8.1</compiler-plugin.version>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>2.13.3.Final</quarkus.platform.version>
<skipITs>true</skipITs>
<surefire-plugin.version>3.0.0-M7</surefire-plugin.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>${quarkus.platform.artifact-id}</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-cassandra-bom</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/io.quarkus/quarkus-resteasy-reactive-jackson -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>
<dependency>
<groupId>com.datastax.oss.quarkus</groupId>
<artifactId>cassandra-quarkus-client</artifactId>
</dependency>

<dependency>
<groupId>com.datastax.oss</groupId>
<artifactId>java-driver-mapper-processor</artifactId>
<version>4.15.0</version>
</dependency>

<dependency>
<groupId>com.datastax.oss</groupId>
<artifactId>java-driver-mapper-runtime</artifactId>
</dependency>

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler-plugin.version}</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<source>17</source> <!-- (or higher) -->
<target>17</target> <!-- (or higher) -->
<annotationProcessorPaths>
<path>
<groupId>com.datastax.oss</groupId>
<artifactId>java-driver-mapper-processor</artifactId>
<version>4.15.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<systemPropertyVariables>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<properties>
<skipITs>false</skipITs>
<quarkus.package.type>native</quarkus.package.type>
</properties>
</profile>
</profiles>
</project>

application.properties: We can configure multiple keyspace.


quarkus.http.port=9000
quarkus.cassandra.contact-points=localhost:9042
quarkus.cassandra.local-datacenter=datacenter1
keyspace.one=inventory


Data class model


package org.henry.model;

import com.datastax.oss.driver.api.mapper.annotations.Entity;
import com.datastax.oss.driver.api.mapper.annotations.PartitionKey;

import java.util.UUID;

@Entity
public class Product {

@PartitionKey private UUID id;
private String description;

public Product() {}

public Product(UUID id, String description) {
this.id = id;
this.description = description;
}

public UUID getId() { return id; }

public void setId(UUID id) { this.id = id; }

public String getDescription() { return description; }

public void setDescription(String description) { this.description = description; }
}

Repository Interface


package org.henry.repository;

import com.datastax.oss.driver.api.core.PagingIterable;
import com.datastax.oss.driver.api.mapper.annotations.*;
import org.henry.model.Product;

import java.util.UUID;

@Dao
public interface ProductDao {
@Select
Product findById(UUID productId);

@Select
PagingIterable<Product> findAll();

@Update
void update(Product product);

@Insert
void save(Product product);

@Delete
void delete(Product product);
}

Mapper interface

By official documentation: Mapper annotations are used to mark the interface, and indicate what kind of request each method should execute.  

Generating the code


The mapper uses annotation processing: it hooks into the Java compiler to analyze annotations, and generate additional classes that implement the mapping logic. Annotation processing is a common technique in modern frameworks, and is generally well supported by build tools and IDEs; this is covered in detail in Configuring the annotation processor.


Pay attention to the compiler output: the mapper processor will sometimes generate warnings if annotations are used incorrectly.


Using the generated code


One of the classes generated during annotation processing is InventoryMapperBuilder


package org.henry.configuration;

import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.CqlSession;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.henry.mapper.InventoryMapper;
import org.henry.mapper.InventoryMapperBuilder;
import org.henry.repository.ProductDao;

import javax.inject.Singleton;

@Singleton
public class Config {

/**
* Call here multiple keyspace with respective DAO
* */
@ConfigProperty(name = "keyspace.one")
String keyspace1;

public CqlSession session(){
CqlSession session = CqlSession.builder().build();
return session;
}

public ProductDao productDao(){
InventoryMapper inventoryMapper = new InventoryMapperBuilder(session()).build();
ProductDao dao = inventoryMapper.productDao(CqlIdentifier.fromCql(keyspace1));
return dao;
}
}

package org.henry.mapper;

import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.mapper.annotations.DaoFactory;
import com.datastax.oss.driver.api.mapper.annotations.DaoKeyspace;
import com.datastax.oss.driver.api.mapper.annotations.Mapper;
import org.henry.repository.ProductDao;

@Mapper
public interface InventoryMapper {
@DaoFactory
ProductDao productDao(@DaoKeyspace CqlIdentifier keyspace);
}


Service Class


package org.henry.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.henry.configuration.Config;
import org.henry.model.Product;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

@ApplicationScoped
public class ProductService {

public static JsonMapper mapper = new JsonMapper();

@Inject
Config config;

public void save(Product product) throws JsonProcessingException {
config.productDao().save(new Product(UUID.randomUUID(), product.getDescription()));
}

public void update(Product product, UUID productId) {
Optional<Product> res = Optional.ofNullable(config.productDao().findById(productId));
if (res.isPresent()) {
res.get().setDescription(product.getDescription());
config.productDao().update(res.get());
}
}

public Product findById(UUID productId) {
return config.productDao().findById(productId);
}

public List<Product> getAll() {
return config.productDao().findAll().all();
}

public void delete(Product product) {
config.productDao().delete(product);
}

}


Quarkus Rest APIs Resource


package org.henry.resource;


import com.fasterxml.jackson.core.JsonProcessingException;
import org.henry.model.Product;
import org.henry.service.ProductService;

import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.util.List;
import java.util.UUID;

@Path("/products")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class ProductResource {

@Inject
ProductService productService;

@POST
public void save(Product product) throws JsonProcessingException {
productService.save(product);
}

@PUT
@Path("{productId}")
public void update(Product product, UUID productId) {
productService.update(product,productId);
}

@GET
@Path("{productId}")
public Product findById(UUID productId) {
return productService.findById(productId);
}

@GET
public List<Product> getAll() {
return productService.getAll();
}

@DELETE
public void delete(Product product) {
productService.delete(product);
}


}


Set up Cassandra


docker pull cassandra

 docker run --name cassandra -p 127.0.0.1:9042:9042 -p 127.0.0.1:9160:9160   -d cassandra

 docker exec -it cassandra /bin/bash

 #cqlsh






Check datacenter name:


cqlsh> use system;
cqlsh:system> select data_center from local;

 data_center
-------------
 datacenter1

(1 rows)
cqlsh:system>


Created keyspaces: inventory.

CREATE KEYSPACE inventory WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}; CREATE TABLE inventory.product(id uuid PRIMARY KEY, description text);


Run & Test


Run Quarkus application with next maven command : 
./mvnw compile quarkus:dev or ./mvnw test (Run test class) by console or IntelliJ.

POST.
http://localhost:9000/products

















PUT.
http://localhost:9000/products/{id}



















DELETE.
http://localhost:9000/products


















GET.
http://localhost:9000/products



















GET.
http://localhost:9000/products/{id}



















Source Code


Here on Github.





References.

https://quarkus.io/guides/cassandra
https://quarkus.io/guides/cdi-reference
https://quarkus.io/guides/config
https://docs.datastax.com/en/developer/java-driver/4.2/manual/mapper/
https://docs.datastax.com/en/developer/java-driver/4.2/manual/mapper/config/
https://docs.datastax.com/en/developer/java-driver/3.0/
https://cassandra.apache.org/_/index.html























No comments:

Post a Comment

Deploying a Spring Boot Application with Cloud SQL and Cloud Run on GCP

In this post, we'll explore how to provision Cloud SQL instances with different connectivity options using Terraform and then access the...