├── images ├── REST-UI.png ├── Reactive-UI-1.png ├── Reactive-UI-2.png ├── combined-1-final.png ├── graalvm-jvm-final.png ├── graalvm-native-final.png ├── REST-UI.png:Zone.Identifier ├── Reactive-UI-2.png:Zone.Identifier └── Reactive-UI-1.png:Zone.Identifier ├── src ├── main │ ├── resources │ │ ├── init_script.cql │ │ ├── META-INF │ │ │ └── resources │ │ │ │ ├── fruits.html │ │ │ │ ├── reactive-fruits.html │ │ │ │ └── index.html │ │ └── application.properties │ ├── docker │ │ ├── Dockerfile.native │ │ ├── Dockerfile.jvm │ │ └── Dockerfile.distroless │ └── java │ │ └── com │ │ └── datastax │ │ └── oss │ │ └── quarkus │ │ └── demo │ │ ├── FruitService.java │ │ ├── ReactiveFruitService.java │ │ ├── FruitDao.java │ │ ├── FruitDto.java │ │ ├── FruitResource.java │ │ ├── ReactiveFruitResource.java │ │ ├── ReactiveFruitDao.java │ │ ├── FruitMapper.java │ │ └── Fruit.java └── test │ └── java │ └── com │ └── datastax │ └── oss │ └── demo │ ├── CassandraTestProfile.java │ └── FruitResourceIT.java ├── post.lua ├── combine-graphs.sh ├── run-test.sh ├── pom.xml └── README.md /images/REST-UI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastaxdevs/Cassandra-Quarkus-Demo/main/images/REST-UI.png -------------------------------------------------------------------------------- /images/Reactive-UI-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastaxdevs/Cassandra-Quarkus-Demo/main/images/Reactive-UI-1.png -------------------------------------------------------------------------------- /images/Reactive-UI-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastaxdevs/Cassandra-Quarkus-Demo/main/images/Reactive-UI-2.png -------------------------------------------------------------------------------- /images/combined-1-final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastaxdevs/Cassandra-Quarkus-Demo/main/images/combined-1-final.png -------------------------------------------------------------------------------- /images/graalvm-jvm-final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastaxdevs/Cassandra-Quarkus-Demo/main/images/graalvm-jvm-final.png -------------------------------------------------------------------------------- /images/graalvm-native-final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastaxdevs/Cassandra-Quarkus-Demo/main/images/graalvm-native-final.png -------------------------------------------------------------------------------- /images/REST-UI.png:Zone.Identifier: -------------------------------------------------------------------------------- 1 | [ZoneTransfer] 2 | LastWriterPackageFamilyName=Microsoft.ScreenSketch_8wekyb3d8bbwe 3 | ZoneId=3 4 | -------------------------------------------------------------------------------- /images/Reactive-UI-2.png:Zone.Identifier: -------------------------------------------------------------------------------- 1 | [ZoneTransfer] 2 | LastWriterPackageFamilyName=Microsoft.MSPaint_8wekyb3d8bbwe 3 | ZoneId=3 4 | -------------------------------------------------------------------------------- /images/Reactive-UI-1.png:Zone.Identifier: -------------------------------------------------------------------------------- 1 | [ZoneTransfer] 2 | LastWriterPackageFamilyName=Microsoft.ScreenSketch_8wekyb3d8bbwe 3 | ZoneId=3 4 | -------------------------------------------------------------------------------- /src/main/resources/init_script.cql: -------------------------------------------------------------------------------- 1 | CREATE KEYSPACE IF NOT EXISTS k1 WITH replication = {'class':'SimpleStrategy', 'replication_factor':1}; 2 | CREATE TABLE IF NOT EXISTS k1.fruit(name text PRIMARY KEY, description text); -------------------------------------------------------------------------------- /post.lua: -------------------------------------------------------------------------------- 1 | -- example HTTP POST script which demonstrates setting the 2 | -- HTTP method, body, and adding a header 3 | 4 | wrk.method = "POST" 5 | wrk.body = "{\"name\": \"Apple\", \"Description\": \"red and tasty\"}" 6 | wrk.headers["Content-Type"] = "application/json" 7 | -------------------------------------------------------------------------------- /src/test/java/com/datastax/oss/demo/CassandraTestProfile.java: -------------------------------------------------------------------------------- 1 | package com.datastax.oss.demo; 2 | 3 | import io.quarkus.test.junit.QuarkusTestProfile; 4 | import java.util.Collections; 5 | import java.util.Map; 6 | 7 | public class CassandraTestProfile implements QuarkusTestProfile { 8 | 9 | @Override 10 | public Map getConfigOverrides() { 11 | return Collections.singletonMap( 12 | "quarkus.cassandra.contact-points", 13 | "${quarkus.cassandra.docker_host}:${quarkus.cassandra.docker_port}"); 14 | } 15 | 16 | @Override 17 | public String getConfigProfile() { 18 | return "cassandra"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /combine-graphs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo -n "Combining graphs ..." 4 | 5 | #cat results/graalvm-jvm_1000.txt results/graalvm-jvm_3000.txt results/graalvm-jvm_5000.txt results/graalvm-native_1000.txt results/graalvm-native_3000.txt results/graalvm-native_5000.txt | wrk2img --log -n graalvm-jvm-1000,graalvm-jvm-3000,graalvm-jvm-5000,graalvm-native-1000,graalvm-native-3000,graalvm-native-5000 results/combined-final.png >> /dev/null 6 | cat results/graalvm-jvm_final.txt results/graalvm-native_final.txt | wrk2img --log -n graalvm-jvm,graalvm-native results/combined-1-final.png >> /dev/null 7 | #echo "" 8 | echo " created graph!" 9 | echo "Done." 10 | -------------------------------------------------------------------------------- /src/main/docker/Dockerfile.native: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode 3 | # 4 | # Before building the docker image run: 5 | # 6 | # mvn package -Dnative -Dquarkus.native.container-build=true 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.native -t quarkus/cassandra-client . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/cassandra-client 15 | # 16 | ### 17 | #FROM registry.access.redhat.com/ubi8/ubi-minimal 18 | FROM container-registry.oracle.com/os/oraclelinux:8-slim 19 | 20 | WORKDIR /work/ 21 | 22 | COPY target/*-runner /work/application 23 | RUN chmod 775 /work 24 | 25 | EXPOSE 8080 26 | CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] -------------------------------------------------------------------------------- /src/main/docker/Dockerfile.jvm: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode 3 | # 4 | # Before building the docker image run: 5 | # 6 | # mvn package 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.jvm -t quarkus/cassandra-client-jvm . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/cassandra-client-jvm 15 | # 16 | ### 17 | #FROM container-registry.oracle.com/java/jdk:11-oraclelinux8 18 | #FROM oraclejdk11-slim 19 | FROM fabric8/java-alpine-openjdk11-jre 20 | 21 | ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" 22 | 23 | ENV AB_ENABLED=jmx_exporter 24 | 25 | #COPY target/lib/* /deployments/lib/ 26 | COPY target/*-runner.jar /deployments/app.jar 27 | ENTRYPOINT [ "/deployments/run-java.sh" ] -------------------------------------------------------------------------------- /src/main/java/com/datastax/oss/quarkus/demo/FruitService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright DataStax, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.datastax.oss.quarkus.demo; 17 | 18 | import java.util.List; 19 | import javax.enterprise.context.ApplicationScoped; 20 | import javax.inject.Inject; 21 | 22 | /** A service that manages {@link Fruit} objects, leveraging the {@link FruitDao} DAO. */ 23 | @ApplicationScoped 24 | public class FruitService { 25 | 26 | @Inject FruitDao dao; 27 | 28 | public void save(Fruit fruit) { 29 | dao.update(fruit); 30 | } 31 | 32 | public List getAll() { 33 | return dao.findAll().all(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/oss/quarkus/demo/ReactiveFruitService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright DataStax, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.datastax.oss.quarkus.demo; 17 | 18 | import io.smallrye.mutiny.Multi; 19 | import io.smallrye.mutiny.Uni; 20 | import javax.enterprise.context.ApplicationScoped; 21 | import javax.inject.Inject; 22 | 23 | /** 24 | * A service that manages {@link Fruit} objects using reactive-style programming. This service 25 | * leverages the {@link ReactiveFruitDao} DAO. 26 | */ 27 | @ApplicationScoped 28 | public class ReactiveFruitService { 29 | 30 | @Inject ReactiveFruitDao fruitDao; 31 | 32 | public Uni add(Fruit fruit) { 33 | return fruitDao.update(fruit); 34 | } 35 | 36 | public Multi getAll() { 37 | return fruitDao.findAll(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /run-test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ -z "$1" ] 4 | then 5 | echo "Oops, pass an arg with a name for output" 6 | echo "Example: ./run-test.sh graalvm-jvm" 7 | exit 1 8 | fi 9 | 10 | name="$1" 11 | 12 | echo -n "Performing benchmark tests ... " 13 | wrk --script post.lua --latency --rate 100 --connections 5 --threads 2 --duration 60s http://localhost:8080/reactive-fruits.html > results/"$name"_1000.txt 14 | wrk --script post.lua --latency --rate 100 --connections 5 --threads 2 --duration 60s http://localhost:8080/reactive-fruits.html > results/"$name"_1001.txt 15 | wrk --script post.lua --latency --rate 100 --connections 5 --threads 2 --duration 60s http://localhost:8080/reactive-fruits.html > results/"$name"_1002.txt 16 | wrk --script post.lua --latency --rate 100 --connections 5 --threads 2 --duration 60s http://localhost:8080/reactive-fruits.html > results/"$name"_1003.txt 17 | wrk --script post.lua --latency --rate 100 --connections 5 --threads 2 --duration 60s http://localhost:8080/reactive-fruits.html > results/"$name"_final.txt 18 | 19 | echo " done." 20 | cat results/"$name"_1000.txt results/"$name"_1001.txt results/"$name"_1002.txt results/"$name"_1003.txt results/"$name"_final.txt | wrk2img --log -n "$name"-1000,"$name"-1001,"$name"-1002,"$name"-1003,"$name"_final results/"$name"-final.png >> /dev/null 21 | #cat results/"$name"_final.txt | wrk2img --log -n graalvm-jvm-1000 results/"$name"-final.png 22 | echo "" 23 | echo -n "Creating graph ..." 24 | echo " done." 25 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/oss/quarkus/demo/FruitDao.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright DataStax, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.datastax.oss.quarkus.demo; 17 | 18 | import com.datastax.oss.driver.api.core.PagingIterable; 19 | import com.datastax.oss.driver.api.mapper.annotations.Dao; 20 | import com.datastax.oss.driver.api.mapper.annotations.Select; 21 | import com.datastax.oss.driver.api.mapper.annotations.Update; 22 | 23 | /** 24 | * A DAO for retrieving {@link Fruit} instances. 25 | * 26 | * @see Defining 28 | * DAOs with the DataStax Java driver object mapper 29 | */ 30 | @Dao 31 | public interface FruitDao { 32 | 33 | /** 34 | * Creates or updates the given {@link Fruit} in the database. 35 | * 36 | * @param fruit The {@link Fruit} to create or update. Cannot be null. 37 | */ 38 | @Update 39 | void update(Fruit fruit); 40 | 41 | /** 42 | * Finds all the fruits. 43 | * 44 | * @return An {@linkplain PagingIterable iterable} containing all the results found in the 45 | * database. 46 | */ 47 | @Select 48 | PagingIterable findAll(); 49 | } 50 | -------------------------------------------------------------------------------- /src/main/docker/Dockerfile.distroless: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode 3 | # 4 | # Before building the docker image run: 5 | # 6 | # mvn package -Dnative -Dquarkus.native.container-build=true 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.distroless -t quarkus/cassandra-client-distroless . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/cassandra-client-distroless 15 | # 16 | ### 17 | FROM ghcr.io/graalvm/graalvm-ce:java11-21.0.0 as graalvm 18 | 19 | RUN microdnf -y install wget unzip zip \ 20 | && gu install native-image 21 | 22 | RUN mkdir -p /tmp/ssl \ 23 | && cp /usr/lib64/libstdc++.so.6.0.25 /tmp/ssl/libstdc++.so.6 \ 24 | && cp /usr/lib64/libgcc_s-8-20191121.so.1 /tmp/ssl/libgcc_s.so.1 \ 25 | && cp /usr/lib64/libz.so.1 /tmp/ssl/libz.so.1 26 | 27 | COPY . /home/app/cassandra 28 | WORKDIR /home/app/cassandra 29 | 30 | RUN \ 31 | # Install SDKMAN 32 | curl -s "https://get.sdkman.io" | bash; \ 33 | source "$HOME/.sdkman/bin/sdkman-init.sh"; \ 34 | # Install Maven 35 | sdk install maven; \ 36 | # Install GraalVM Native Image 37 | gu install native-image; 38 | 39 | RUN source "$HOME/.sdkman/bin/sdkman-init.sh" \ 40 | && mvn --version \ 41 | && native-image --version \ 42 | && mvn -DskipTests -Pnative clean package 43 | 44 | FROM frolvlad/alpine-glibc 45 | 46 | ENV LD_LIBRARY_PATH=/usr/local/lib:/usr/glibc-compat/lib:/opt/libs/lib:/usr/lib:/lib:/lib64 47 | 48 | RUN apk -U upgrade \ 49 | && apk add bash 50 | COPY --from=graalvm /tmp/ssl/ / 51 | ENV LD_LIBRARY_PATH / 52 | 53 | EXPOSE 8080 54 | COPY --from=graalvm /home/app/cassandra/target/*-runner app 55 | ENTRYPOINT ["/app"] -------------------------------------------------------------------------------- /src/main/java/com/datastax/oss/quarkus/demo/FruitDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright DataStax, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.datastax.oss.quarkus.demo; 17 | 18 | import java.util.Objects; 19 | 20 | /** A DTO (Data Transfer Object) used to convey information from a {@link Fruit} domain object. */ 21 | public class FruitDto { 22 | 23 | private String name; 24 | private String description; 25 | 26 | public FruitDto() {} 27 | 28 | public FruitDto(String name, String description) { 29 | this.name = name; 30 | this.description = description; 31 | } 32 | 33 | public String getName() { 34 | return name; 35 | } 36 | 37 | public void setName(String name) { 38 | this.name = name; 39 | } 40 | 41 | public String getDescription() { 42 | return description; 43 | } 44 | 45 | public void setDescription(String description) { 46 | this.description = description; 47 | } 48 | 49 | @Override 50 | public boolean equals(Object o) { 51 | if (this == o) return true; 52 | if (o == null || getClass() != o.getClass()) return false; 53 | 54 | FruitDto fruitDto = (FruitDto) o; 55 | 56 | if (!Objects.equals(name, fruitDto.name)) return false; 57 | return Objects.equals(description, fruitDto.description); 58 | } 59 | 60 | @Override 61 | public int hashCode() { 62 | return Objects.hash(description, name); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/oss/quarkus/demo/FruitResource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright DataStax, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.datastax.oss.quarkus.demo; 17 | 18 | import java.util.List; 19 | import java.util.stream.Collectors; 20 | import javax.inject.Inject; 21 | import javax.ws.rs.Consumes; 22 | import javax.ws.rs.GET; 23 | import javax.ws.rs.POST; 24 | import javax.ws.rs.Path; 25 | import javax.ws.rs.Produces; 26 | import javax.ws.rs.core.MediaType; 27 | 28 | /** 29 | * A REST resource exposing endpoints for creating and retrieving {@link Fruit} objects in the 30 | * database, leveraging the {@link FruitService} component. 31 | */ 32 | @Path("/fruits") 33 | @Produces(MediaType.APPLICATION_JSON) 34 | @Consumes(MediaType.APPLICATION_JSON) 35 | public class FruitResource { 36 | 37 | @Inject FruitService fruitService; 38 | 39 | @GET 40 | public List getAll() { 41 | return fruitService.getAll().stream().map(this::convertToDto).collect(Collectors.toList()); 42 | } 43 | 44 | @POST 45 | public void add(FruitDto fruit) { 46 | fruitService.save(convertFromDto(fruit)); 47 | } 48 | 49 | private FruitDto convertToDto(Fruit fruit) { 50 | return new FruitDto(fruit.getName(), fruit.getDescription()); 51 | } 52 | 53 | private Fruit convertFromDto(FruitDto fruitDto) { 54 | return new Fruit(fruitDto.getName(), fruitDto.getDescription()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/oss/quarkus/demo/ReactiveFruitResource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright DataStax, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.datastax.oss.quarkus.demo; 17 | 18 | import io.smallrye.mutiny.Multi; 19 | import io.smallrye.mutiny.Uni; 20 | import javax.inject.Inject; 21 | import javax.ws.rs.Consumes; 22 | import javax.ws.rs.GET; 23 | import javax.ws.rs.POST; 24 | import javax.ws.rs.Path; 25 | import javax.ws.rs.Produces; 26 | import javax.ws.rs.core.MediaType; 27 | 28 | /** 29 | * A REST resource exposing reactive endpoints for creating and retrieving {@link Fruit} objects in 30 | * the database, leveraging the {@link ReactiveFruitService} component. 31 | */ 32 | @Path("/reactive-fruits") 33 | @Produces(MediaType.APPLICATION_JSON) 34 | @Consumes(MediaType.APPLICATION_JSON) 35 | public class ReactiveFruitResource { 36 | 37 | @Inject ReactiveFruitService service; 38 | 39 | @GET 40 | public Multi getAll() { 41 | return service.getAll().map(this::convertToDto); 42 | } 43 | 44 | @POST 45 | public Uni add(FruitDto fruitDto) { 46 | return service.add(convertFromDto(fruitDto)); 47 | } 48 | 49 | private FruitDto convertToDto(Fruit fruit) { 50 | return new FruitDto(fruit.getName(), fruit.getDescription()); 51 | } 52 | 53 | private Fruit convertFromDto(FruitDto fruitDto) { 54 | return new Fruit(fruitDto.getName(), fruitDto.getDescription()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/oss/quarkus/demo/ReactiveFruitDao.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright DataStax, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.datastax.oss.quarkus.demo; 17 | 18 | import com.datastax.oss.driver.api.mapper.annotations.Dao; 19 | import com.datastax.oss.driver.api.mapper.annotations.Select; 20 | import com.datastax.oss.driver.api.mapper.annotations.Update; 21 | import com.datastax.oss.quarkus.runtime.api.reactive.mapper.MutinyMappedReactiveResultSet; 22 | import io.smallrye.mutiny.Uni; 23 | 24 | /** 25 | * A DAO for retrieving {@link Fruit} instances using reactive-style programming and Mutiny types. 26 | * 27 | * @see Defining 29 | * DAOs with the DataStax Java driver object mapper 30 | */ 31 | @Dao 32 | public interface ReactiveFruitDao { 33 | 34 | /** 35 | * Creates or updates the given {@link Fruit} in the database. 36 | * 37 | * @param fruit The {@link Fruit} to create or update. Cannot be null. 38 | * @return A {@link Uni} that will complete when the query completes. 39 | */ 40 | @Update 41 | Uni update(Fruit fruit); 42 | 43 | /** 44 | * Finds all the fruits. 45 | * 46 | * @return A {@linkplain MutinyMappedReactiveResultSet Mutiny publisher} that will emit all the 47 | * results found in the database. 48 | */ 49 | @Select 50 | MutinyMappedReactiveResultSet findAll(); 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/oss/quarkus/demo/FruitMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright DataStax, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.datastax.oss.quarkus.demo; 17 | 18 | import com.datastax.oss.driver.api.mapper.annotations.DaoFactory; 19 | import com.datastax.oss.driver.api.mapper.annotations.Mapper; 20 | 21 | /** 22 | * An object mapper defining all the DAOs for this application. 23 | * 24 | *

Classes annotated with {@link Mapper} are the main entry point for the DataStax Java driver 25 | * object mapper. 26 | * 27 | *

An application-scoped {@link FruitMapper} bean will be automatically created and can be 28 | * injected everywhere in your application, see {@link FruitMapperProducer}. 29 | * 30 | * @see Using 32 | * the Mapper interface 33 | */ 34 | @Mapper 35 | public interface FruitMapper { 36 | 37 | /** 38 | * Creates a new {@link FruitDao}. It will operate on the same keyspace as the Quarkus session. 39 | * 40 | *

The application-scoped {@link FruitDao} bean produced by this method can be automatically 41 | * injected everywhere in your application. 42 | * 43 | * @return a new {@link FruitDao}. 44 | */ 45 | @DaoFactory 46 | FruitDao fruitDao(); 47 | 48 | /** 49 | * Creates a new {@link ReactiveFruitDao}. It will operate on the same keyspace as the Quarkus 50 | * session. 51 | * 52 | *

The application-scoped {@link FruitDao} bean produced by this method can be automatically 53 | * injected everywhere in your application. 54 | * 55 | * @return a new {@link ReactiveFruitDao}. 56 | */ 57 | @DaoFactory 58 | ReactiveFruitDao reactiveFruitDao(); 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/datastax/oss/quarkus/demo/Fruit.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright DataStax, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.datastax.oss.quarkus.demo; 17 | 18 | import com.datastax.oss.driver.api.mapper.annotations.Entity; 19 | import com.datastax.oss.driver.api.mapper.annotations.PartitionKey; 20 | import com.datastax.oss.driver.api.mapper.annotations.PropertyStrategy; 21 | import java.util.Objects; 22 | 23 | /** 24 | * Represents the name and description of a fruit. 25 | * 26 | * @see Defining 28 | * entities with the DataStax Java driver object mapper 29 | */ 30 | @Entity 31 | @PropertyStrategy(mutable = false) 32 | public class Fruit { 33 | 34 | @PartitionKey private final String name; 35 | 36 | private final String description; 37 | 38 | public Fruit(String name, String description) { 39 | this.name = name; 40 | this.description = description; 41 | } 42 | 43 | /** @return The fruit name. */ 44 | public String getName() { 45 | return name; 46 | } 47 | 48 | /** @return The fruit description. */ 49 | public String getDescription() { 50 | return description; 51 | } 52 | 53 | @Override 54 | public boolean equals(Object o) { 55 | if (this == o) return true; 56 | if (o == null || getClass() != o.getClass()) return false; 57 | Fruit that = (Fruit) o; 58 | return Objects.equals(description, that.description) && Objects.equals(name, that.name); 59 | } 60 | 61 | @Override 62 | public int hashCode() { 63 | return Objects.hash(description, name); 64 | } 65 | 66 | @Override 67 | public String toString() { 68 | return String.format("Fruit{name='%s', description='%s'}", name, description); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/java/com/datastax/oss/demo/FruitResourceIT.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright DataStax, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.datastax.oss.demo; 17 | 18 | import static io.restassured.RestAssured.given; 19 | import static io.restassured.RestAssured.when; 20 | import static org.assertj.core.api.Assertions.assertThat; 21 | import static org.hamcrest.Matchers.notNullValue; 22 | 23 | import com.datastax.oss.quarkus.demo.FruitDto; 24 | import com.datastax.oss.quarkus.test.CassandraTestResource; 25 | import io.quarkus.test.common.QuarkusTestResource; 26 | import io.quarkus.test.junit.QuarkusTest; 27 | import io.quarkus.test.junit.TestProfile; 28 | import io.restassured.http.ContentType; 29 | import javax.ws.rs.core.Response; 30 | import org.junit.jupiter.api.Test; 31 | 32 | @QuarkusTest 33 | @QuarkusTestResource(CassandraTestResource.class) 34 | @TestProfile(CassandraTestProfile.class) 35 | public class FruitResourceIT { 36 | 37 | @Test 38 | public void should_save_and_retrieve_entity() { 39 | // given 40 | FruitDto expected = new FruitDto("apple", "this was created via IT test"); 41 | 42 | // when creating, then 43 | given() 44 | .contentType(ContentType.JSON) 45 | .body(expected) 46 | .when() 47 | .post("/fruits") 48 | .then() 49 | .statusCode(Response.Status.NO_CONTENT.getStatusCode()) 50 | .body(notNullValue()); 51 | 52 | // when retrieving, then 53 | FruitDto[] actual = 54 | when() 55 | .get("/fruits") 56 | .then() 57 | .statusCode(Response.Status.OK.getStatusCode()) 58 | .body(notNullValue()) 59 | .extract() 60 | .body() 61 | .as(FruitDto[].class); 62 | assertThat(actual).contains(expected); 63 | } 64 | 65 | @Test 66 | public void should_save_and_retrieve_entity_reactive() { 67 | // given 68 | FruitDto expected = new FruitDto("banana", "this was created via reactive IT test"); 69 | 70 | // when creating, then 71 | given() 72 | .contentType(ContentType.JSON) 73 | .body(expected) 74 | .when() 75 | .post("/reactive-fruits") 76 | .then() 77 | .statusCode(Response.Status.NO_CONTENT.getStatusCode()) 78 | .body(notNullValue()); 79 | 80 | // when retrieving, then 81 | FruitDto[] actual = 82 | when() 83 | .get("/reactive-fruits") 84 | .then() 85 | .statusCode(Response.Status.OK.getStatusCode()) 86 | .body(notNullValue()) 87 | .extract() 88 | .body() 89 | .as(FruitDto[].class); 90 | assertThat(actual).contains(expected); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/fruits.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Fruit REST service 6 | 7 | 8 | 9 | 69 | 70 | 71 | 72 |

73 |

REST Service - Fruit

74 | 75 |

Add a fruit

76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | 84 |
85 | 86 |

Fruit List

87 |
88 |
Name
89 |
Description
90 |
91 |
92 |
{{ fruit.name }}
93 |
{{ fruit.description }}
94 |
95 |
96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/reactive-fruits.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Fruit Async REST service 6 | 7 | 8 | 9 | 69 | 70 | 71 | 72 |
73 |

Async REST Service - Fruit

74 | 75 |

Add a fruit

76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | 84 |
85 | 86 |

Fruit List

87 |
88 |
Name
89 |
Description
90 |
91 |
92 |
{{ fruit.name }}
93 |
{{ fruit.description }}
94 |
95 |
96 | 97 | 98 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright DataStax, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | quarkus.package.type=uber-jar 18 | 19 | # Connecting to Apache Cassandra (R) or DataStax Enterprise (DSE) 20 | quarkus.cassandra.contact-points=172.17.201.21:9042 21 | quarkus.cassandra.local-datacenter=datacenter1 22 | quarkus.cassandra.keyspace=k1 23 | 24 | # Connecting to DataStax Astra 25 | # https://docs.datastax.com/en/developer/java-driver/latest/manual/cloud/ 26 | #quarkus.cassandra.cloud.secure-connect-bundle=/path/to/secure-connect-bundle.zip 27 | 28 | # Authentication 29 | # See https://docs.datastax.com/en/developer/java-driver/latest/manual/core/authentication/ 30 | #quarkus.cassandra.auth.username= 31 | #quarkus.cassandra.auth.password= 32 | 33 | # Health Checks 34 | quarkus.cassandra.health.enabled=true 35 | 36 | # Metrics 37 | # See https://docs.datastax.com/en/developer/java-driver/latest/manual/core/metrics/ 38 | quarkus.cassandra.metrics.enabled=true 39 | quarkus.cassandra.metrics.session.enabled=\ 40 | bytes-sent,\ 41 | bytes-received,\ 42 | connected-nodes,\ 43 | cql-requests,\ 44 | cql-client-timeouts 45 | quarkus.cassandra.metrics.node.enabled=\ 46 | pool.open-connections,\ 47 | pool.in-flight,\ 48 | bytes-sent,\ 49 | bytes-received,\ 50 | cql-messages,\ 51 | errors.request.unsent,\ 52 | errors.request.aborted,\ 53 | errors.request.write-timeouts,\ 54 | errors.request.read-timeouts,\ 55 | errors.request.unavailables,\ 56 | errors.request.others,\ 57 | retries.total,\ 58 | retries.aborted,\ 59 | retries.read-timeout,\ 60 | retries.write-timeout,\ 61 | retries.unavailable,\ 62 | retries.other,\ 63 | ignores.total,\ 64 | ignores.aborted,\ 65 | ignores.read-timeout,\ 66 | ignores.write-timeout,\ 67 | ignores.unavailable,\ 68 | ignores.other,\ 69 | errors.connection.init,\ 70 | errors.connection.auth 71 | 72 | # Request properties 73 | #quarkus.cassandra.request.timeout=PT10S 74 | #quarkus.cassandra.request.consistency-level=LOCAL_QUORUM 75 | #quarkus.cassandra.request.serial-consistency-level=LOCAL_SERIAL 76 | #quarkus.cassandra.request.page-size=5000 77 | #quarkus.cassandra.request.default-idempotence=true 78 | 79 | # Protocol settings 80 | #quarkus.cassandra.protocol.compression=none 81 | 82 | # Startup and Initialization settings 83 | #quarkus.cassandra.init.eager-init=false 84 | #quarkus.cassandra.init.eager-init-timeout=PT10S 85 | #quarkus.cassandra.init.print-eager-init-info=true 86 | #quarkus.cassandra.init.reconnect-on-init=true 87 | #quarkus.cassandra.init.resolve-contact-points=false 88 | #quarkus.cassandra.init.use-quarkus-event-loop=true 89 | 90 | # Logging 91 | #quarkus.log.level=INFO 92 | #quarkus.log.min-level=DEBUG 93 | #quarkus.log.category."com.datastax.oss.quarkus".level=INFO 94 | #quarkus.log.category."com.datastax.oss.driver".level=INFO 95 | #quarkus.log.category."com.datastax.dse.driver".level=INFO 96 | 97 | # More info about how to configure the DataStax Java driver: 98 | # https://docs.datastax.com/en/developer/java-driver/latest/manual/core/configuration/ 99 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cassandra-client - 1.0-SNAPSHOT 6 | 99 | 100 | 101 | 102 | 105 | 106 |
107 |
108 |

Congratulations, you have created a new Quarkus application.

109 | 110 |

Why do you see this?

111 | 112 |

This page is served by Quarkus. The source is in 113 | src/main/resources/META-INF/resources/index.html.

114 | 115 |

What can I do from here?

116 | 117 |

If not already done, run the application in dev mode using: mvn quarkus:dev. 118 |

119 |
    120 |
  • Add REST resources, Servlets, functions and other services in src/main/java.
  • 121 |
  • Your static assets are located in src/main/resources/META-INF/resources.
  • 122 |
  • Configure your application in src/main/resources/application.properties. 123 |
  • 124 |
125 | 126 |

How do I get rid of this page?

127 |

Just delete the src/main/resources/META-INF/resources/index.html file.

128 |
129 |
130 |
131 |

Application

132 |
    133 |
  • GroupId: com.datastax.oss.quarkus
  • 134 |
  • ArtifactId: cassandra-quarkus-client
  • 135 |
  • Version: 1.0-SNAPSHOT
  • 136 |
  • Quarkus Version: 1.3.0.Final
  • 137 |
138 |
139 |
140 |

Next steps

141 | 146 |
147 |
148 |
149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 4.0.0 21 | com.datastax.oss.quarkus 22 | cassandra-quarkus-quickstart 23 | 1.0.1 24 | Cassandra Quarkus :: Quickstart 25 | Quickstart Application for the Quarkus extension for Apache Cassandra(R) 26 | https://github.com/datastax/cassandra-quarkus/quickstart 27 | 2020 28 | 29 | 3.22.0 30 | 3.8.1 31 | 1.4.2.Final 32 | true 33 | 11 34 | 11 35 | UTF-8 36 | UTF-8 37 | 1.1.3 38 | quarkus-bom 39 | io.quarkus.platform 40 | 2.13.3.Final 41 | 3.0.0-M5 42 | 43 | 44 | 45 | 46 | ${quarkus.platform.group-id} 47 | ${quarkus.platform.artifact-id} 48 | ${quarkus.platform.version} 49 | pom 50 | import 51 | 52 | 53 | ${quarkus.platform.group-id} 54 | quarkus-cassandra-bom 55 | ${quarkus.platform.version} 56 | pom 57 | import 58 | 59 | 60 | 61 | 62 | 63 | io.quarkus 64 | quarkus-resteasy-reactive 65 | 66 | 67 | io.quarkus 68 | quarkus-resteasy-reactive-jackson 69 | 70 | 71 | com.datastax.oss.quarkus 72 | cassandra-quarkus-client 73 | 74 | 75 | com.datastax.oss 76 | java-driver-mapper-runtime 77 | 78 | 79 | com.datastax.oss 80 | java-driver-metrics-micrometer 81 | 82 | 83 | io.quarkus 84 | quarkus-micrometer 85 | 86 | 87 | io.quarkus 88 | quarkus-micrometer-registry-prometheus 89 | 90 | 91 | io.quarkus 92 | quarkus-smallrye-openapi 93 | 94 | 95 | io.quarkus 96 | quarkus-smallrye-health 97 | 98 | 99 | org.mapstruct 100 | mapstruct 101 | ${mapstruct.version} 102 | 103 | 104 | io.quarkus 105 | quarkus-kubernetes 106 | 107 | 108 | io.quarkus 109 | quarkus-junit5 110 | test 111 | 112 | 113 | io.quarkus 114 | quarkus-junit5-mockito 115 | test 116 | 117 | 118 | io.rest-assured 119 | rest-assured 120 | test 121 | 122 | 123 | com.datastax.oss.quarkus 124 | cassandra-quarkus-test-framework 125 | test 126 | 127 | 128 | org.assertj 129 | assertj-core 130 | ${assertj.version} 131 | test 132 | 133 | 134 | io.quarkus 135 | quarkus-container-image-jib 136 | 137 | 138 | 139 | 140 | 141 | ${quarkus.platform.group-id} 142 | quarkus-maven-plugin 143 | ${quarkus.platform.version} 144 | true 145 | 146 | 147 | 148 | build 149 | generate-code 150 | generate-code-tests 151 | 152 | 153 | 154 | 155 | 156 | maven-compiler-plugin 157 | ${compiler-plugin.version} 158 | 159 | 160 | 161 | com.datastax.oss.quarkus 162 | cassandra-quarkus-mapper-processor 163 | ${quarkus-cassandra.version} 164 | 165 | 166 | org.mapstruct 167 | mapstruct-processor 168 | ${mapstruct.version} 169 | 170 | 171 | 172 | 173 | 174 | maven-surefire-plugin 175 | ${surefire-plugin.version} 176 | 177 | 178 | org.jboss.logmanager.LogManager 179 | ${maven.home} 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | native 188 | 189 | 190 | native 191 | 192 | 193 | 194 | 195 | 196 | maven-failsafe-plugin 197 | ${surefire-plugin.version} 198 | 199 | 200 | 201 | integration-test 202 | verify 203 | 204 | 205 | 206 | ${project.build.directory}/${project.build.finalName}-runner 207 | org.jboss.logmanager.LogManager 208 | ${maven.home} 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | native 218 | 219 | 220 | 221 | 222 | 223 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cassandra Client with Quarkus 2 | 3 | Apache Cassandra® is a free and open-source, distributed, wide column store, NoSQL database management system designed to handle large amounts of data across many commodity servers, providing high availability with no single point of failure. 4 | 5 | In this guide, we'll demonstrate how you can create a REST service to access a Cassandra database and then create a native image executable of the application. 6 | 7 | ### Credits 8 | This example is based on an article that appears [here](https://quarkus.io/version/1.7/guides/cassandra). 9 | 10 | ### Preqs 11 | NOTE: Java 11 is used for this example, however, Java 8 would work as well with a slight adjustment to the `pom.xml`. 12 | 13 | ### Connecting to Apache Cassandra 14 | 15 | To connect to the Cassandra database, there are three primary properties to configure: 16 | * `contact-points` to access the Cassandra database 17 | * `local-datacenter` which is required by the driver 18 | * Optionally, the `keyspace` to bind 19 | 20 | A sample configuration (in **src/main/resources/application.properties**) should follow this format: 21 | 22 | ``` 23 | quarkus.cassandra.contact-points={cassandra_ip}:9042 24 | quarkus.cassandra.local-datacenter={dc_name} 25 | quarkus.cassandra.keyspace={keyspace} 26 | ``` 27 | 28 | **NOTE:** To configure the IP for Linux on WSL2, identify the IP by executing the following command: 29 | 30 | ``` 31 | $ ping $(hostname).local 32 | PING sws-ryzen (172.30.112.1) 56(84) bytes of data. 33 | ``` 34 | 35 | In this example, we are using a single instance running on localhost *(IP address from above)*, and the keyspace containing our data is k1: 36 | 37 | ``` 38 | quarkus.cassandra.contact-points=172.30.112.1:9042 39 | quarkus.cassandra.local-datacenter=datacenter1 40 | quarkus.cassandra.keyspace=k1 41 | ``` 42 | 43 | ### Running the Cassandra Database 44 | For simplicity, we'll use a container to run the Cassandra database. To start the Cassandra database container, execute: 45 | ``` 46 | $ docker run --name local-cassandra-instance -p 9042:9042 -d cassandra 47 | ``` 48 | 49 | Next you need to create the keyspace and table that will be used by the application. If you are using Docker (`podman` is good alternative), run the following commands: 50 | 51 | ``` 52 | $ docker exec -it local-cassandra-instance cqlsh -e "CREATE KEYSPACE IF NOT EXISTS k1 WITH replication = {'class':'SimpleStrategy', 'replication_factor':1}" 53 | $ docker exec -it local-cassandra-instance cqlsh -e "CREATE TABLE IF NOT EXISTS k1.fruit(name text PRIMARY KEY, description text)" 54 | ``` 55 | 56 | You can also use the CQLSH utility to interactively interrogate your database: 57 | 58 | ``` 59 | $ docker exec -it local-cassandra-instance cqlsh 60 | Connected to Test Cluster at 127.0.0.1:9042. 61 | [cqlsh 5.0.1 | Cassandra 3.11.10 | CQL spec 3.4.4 | Native protocol v4] 62 | Use HELP for help. 63 | cqlsh> select * from k1.fruit; 64 | 65 | name | description 66 | ------+------------- 67 | 68 | (0 rows) 69 | cqlsh> exit 70 | ``` 71 | 72 | ### Building the Project 73 | To build the project, run: 74 | ``` 75 | $ mvn clean package 76 | ``` 77 | **NOTE:** We created an `uber-jar` of the application, you can choose to create a `fast-jar` instead. 78 | 79 | To start the `jar` application, execute: 80 | ``` 81 | $ java -jar ./target/cassandra-quarkus-quickstart-*-runner.jar 82 | __ ____ __ _____ ___ __ ____ ______ 83 | --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 84 | -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ 85 | --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 86 | 2021-04-09 13:31:59,913 INFO [com.dat.oss.dri.int.cor.DefaultMavenCoordinates] (main) DataStax Java driver for Apache Cassandra(R) (com.datastax.oss:java-driver-core) version 4.10.0 87 | 2021-04-09 13:31:59,952 INFO [com.dat.oss.qua.run.int.qua.CassandraClientRecorder] (main) Enabling Cassandra metrics using Micrometer. 88 | 2021-04-09 13:32:00,214 WARN [io.qua.run.con.ConfigChangeRecorder] (main) Build time property cannot be changed at runtime: 89 | - quarkus.package.type was 'native' at build time and is now 'uber-jar' 90 | 2021-04-09 13:32:00,460 INFO [io.quarkus] (main) cassandra-quarkus-quickstart 1.0.1 on JVM (powered by Quarkus 1.13.1.Final) started in 0.893s. Listening on: http://0.0.0.0:8080 91 | 2021-04-09 13:32:00,462 INFO [io.quarkus] (main) Profile prod activated. 92 | 2021-04-09 13:32:00,462 INFO [io.quarkus] (main) Installed features: [cassandra-client, cdi, micrometer, mutiny, resteasy, resteasy-jsonb, resteasy-mutiny, smallrye-context-propagation, smallrye-health] 93 | 94 | ``` 95 | 96 | Or run the application in dev mode: 97 | ``` 98 | $ mvn clean quarkus:dev 99 | ``` 100 | 101 | ### Adding Data and Accessing the UI 102 | Add some data to the database: 103 | ``` 104 | $ http POST http://localhost:8080/fruits name="apple" description="red and tasty" 105 | HTTP/1.1 204 No Content 106 | ``` 107 | **NOTE:** These examples use [http](https://httpie.io/) but of course, `curl` will work too. 108 | 109 | Then check to make certain the data posted properly: 110 | ``` 111 | $ http http://localhost:8080/fruits --body 112 | [ 113 | { 114 | "description": "red and tasty", 115 | "name": "apple" 116 | } 117 | ] 118 | ``` 119 | 120 | To access the REST UI, browse to: [http://localhost:8080/fruits.html](http://localhost:8080/fruits.html) 121 | 122 | ![](images/REST-UI.png) 123 | 124 | To access the Reactive UI, browse to: 125 | [http://localhost:8080/reactive-fruits.html](http://localhost:8080/reactive-fruits.html) 126 | 127 | ![](images/Reactive-UI-1.png) 128 | 129 | You can also add fruit to the database via the UI: 130 | 131 | ![](images/Reactive-UI-2.png) 132 | 133 | ### Application Health Check 134 | You can access the `/q/health` endpoint of your application to retrieve information about the connection validation status: 135 | 136 | ``` 137 | $ http http://localhost:8080/q/health --body 138 | { 139 | "checks": [ 140 | { 141 | "data": { 142 | "clusterName": "Test Cluster", 143 | "cqlVersion": "3.4.4", 144 | "datacenter": "datacenter1", 145 | "numberOfNodes": 1, 146 | "releaseVersion": "3.11.10" 147 | }, 148 | "name": "DataStax Apache Cassandra Driver health check", 149 | "status": "UP" 150 | } 151 | ], 152 | "status": "UP" 153 | } 154 | ``` 155 | 156 | ### Create a Native Image Executable 157 | To build a native image, execute the following command: 158 | 159 | ``` 160 | $ mvn package -Dnative 161 | ``` 162 | 163 | Once the compilation has finished, you can run the native executable by executing the following command: 164 | 165 | ``` 166 | $ ./target/cassandra-quarkus-quickstart-*-runner 167 | __ ____ __ _____ ___ __ ____ ______ 168 | --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 169 | -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ 170 | --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 171 | 2021-04-09 13:34:09,037 INFO [io.quarkus] (main) cassandra-quarkus-quickstart 1.0.1 native (powered by Quarkus 1.13.1.Final) started in 0.018s. Listening on: http://0.0.0.0:8080 172 | 2021-04-09 13:34:09,037 INFO [io.quarkus] (main) Profile prod activated. 173 | 2021-04-09 13:34:09,038 INFO [io.quarkus] (main) Installed features: [cassandra-client, cdi, micrometer, mutiny, resteasy, resteasy-jsonb, resteasy-mutiny, smallrye-context-propagation, smallrye-health] 174 | 175 | ``` 176 | You can then browse to [http://localhost:8080/fruits.html](http://localhost:8080/fruits.html) or [http://localhost:8080/reactive-fruits.html](http://localhost:8080/reactive-fruits.html) and access the application as noted earlier. 177 | 178 | Since you're undoubtedly curious, let's compare the JAR and native image file sizes: 179 | 180 | ``` 181 | $ ls -lah target/cassandra-quarkus-quickstart-1.0.1* 182 | -rwxr-xr-x 1 sseighma sseighma 73M Apr 9 12:44 target/cassandra-quarkus-quickstart-1.0.1-runner 183 | -rw-r--r-- 1 sseighma sseighma 35K Apr 9 12:42 target/cassandra-quarkus-quickstart-1.0.1.jar 184 | ``` 185 | Using [upx](https://upx.github.io/), you can further reduce the size of the native image executable: 186 | ``` 187 | $ upx -7 -k target/cassandra-quarkus-quickstart-1.0.1-runner 188 | Ultimate Packer for eXecutables 189 | Copyright (C) 1996 - 2020 190 | UPX 3.96 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 23rd 2020 191 | 192 | File size Ratio Format Name 193 | -------------------- ------ ----------- ----------- 194 | 75751464 -> 21118728 27.88% linux/amd64 cassandra-quarkus-quickstart-1.0.1-runner 195 | 196 | Packed 1 file. 197 | ``` 198 | 199 | Comparing once again, you can see we reduced the native image executable size from **73MB** to **21MB**. 200 | ``` 201 | $ ls -lah target/cassandra-quarkus-quickstart-1.0.1* 202 | -rwxr-xr-x 1 sseighma sseighma 21M Apr 9 12:44 target/cassandra-quarkus-quickstart-1.0.1-runner 203 | -rwxr-xr-x 1 sseighma sseighma 73M Apr 9 12:44 target/cassandra-quarkus-quickstart-1.0.1-runne~ 204 | -rw-r--r-- 1 sseighma sseighma 35K Apr 9 12:42 target/cassandra-quarkus-quickstart-1.0.1.jar 205 | ``` 206 | 207 | ### Creating Container Images 208 | 209 | We'll demonstrate a few options to build container images for the application: 210 | 211 | * Using the uber-jar 212 | * Using Native Image 213 | * Using a Distroless Image 214 | 215 | First, let's build a container using the **uber-jar** version of the application: 216 | 217 | ``` 218 | $ docker build -f src/main/docker/Dockerfile.jvm -t quarkus/cassandra-client-jvm . 219 | ``` 220 | Run the container: 221 | 222 | ``` 223 | $ docker run -i --rm -p 8080:8080 quarkus/cassandra-client-jvm 224 | exec java -Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager -javaagent:/opt/agent-bond/agent-bond.jar=jmx_exporter{{9779:/opt/agent-bond/jmx_exporter_config.yml}} -XX:+ExitOnOutOfMemoryError -cp . -jar /deployments/app.jar 225 | __ ____ __ _____ ___ __ ____ ______ 226 | --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 227 | -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ 228 | --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 229 | 2021-04-09 16:29:01,847 INFO [com.dat.oss.dri.int.cor.DefaultMavenCoordinates] (main) DataStax Java driver for Apache Cassandra(R) (com.datastax.oss:java-driver-core) version 4.10.0 230 | 2021-04-09 16:29:02,000 INFO [com.dat.oss.qua.run.int.qua.CassandraClientRecorder] (main) Enabling Cassandra metrics using Micrometer. 231 | 2021-04-09 16:29:02,354 INFO [io.quarkus] (main) cassandra-quarkus-quickstart 1.0.1 on JVM (powered by Quarkus 1.13.1.Final) started in 0.789s. Listening on: http://0.0.0.0:8080 232 | 2021-04-09 16:29:02,354 INFO [io.quarkus] (main) Profile prod activated. 233 | 2021-04-09 16:29:02,355 INFO [io.quarkus] (main) Installed features: [cassandra-client, cdi, micrometer, mutiny, resteasy, resteasy-jsonb, resteasy-mutiny, smallrye-context-propagation, smallrye-health] 234 | 235 | ``` 236 | Notice it starts in ~**800ms**. 237 | 238 | Now let's build a container with the **native image** executable: 239 | 240 | ``` 241 | $ docker build -f src/main/docker/Dockerfile.native -t quarkus/cassandra-client-native . 242 | ``` 243 | Run the native image container: 244 | ``` 245 | $ docker run -i --rm -p 8080:8080 quarkus/cassandra-client-native 246 | __ ____ __ _____ ___ __ ____ ______ 247 | --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 248 | -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ 249 | --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 250 | 2021-04-09 16:31:10,907 INFO [io.quarkus] (main) cassandra-quarkus-quickstart 1.0.1 native (powered by Quarkus 1.13.1.Final) started in 0.015s. Listening on: http://0.0.0.0:8080 251 | 2021-04-09 16:31:10,907 INFO [io.quarkus] (main) Profile prod activated. 252 | 2021-04-09 16:31:10,907 INFO [io.quarkus] (main) Installed features: [cassandra-client, cdi, micrometer, mutiny, resteasy, resteasy-jsonb, resteasy-mutiny, smallrye-context-propagation, smallrye-health] 253 | 254 | ``` 255 | Notice the native image version starts considerably faster at ~**15ms**. 256 | 257 | If we use the `upx` compressed version of the native image executable, the container image is reduced in size from **179MB** to **132MB** with little impact on startup times. 258 | ``` 259 | $ docker images | grep native 260 | quarkus/cassandra-client-native latest d51068444298 14 seconds ago 132MB 261 | ``` 262 | 263 | 264 | Another option to shrink your container images is to consider using a distroless image. To build a distroless image, run: 265 | ``` 266 | $ docker build -f src/main/docker/Dockerfile.distroless -t quarkus/cassandra-client-distroless . 267 | ``` 268 | 269 | Here we compare the image size of each option: 270 | ``` 271 | $ docker images 272 | quarkus/cassandra-client-distroless latest ae519318cd36 2 hours ago 96MB 273 | quarkus/cassandra-client-native latest 1077993b8418 2 hours ago 132MB 274 | quarkus/cassandra-client-jvm latest 7043af3565af 2 hours ago 222MB 275 | ``` 276 | 277 | ### Benchmarking Exercise 278 | 279 | Thought I would include an option to benchmark the application, primarily for fun and to understand how to create a basic benchmark test. 280 | 281 | I'll be using [wrk2](https://github.com/giltene/wrk2) for the actual benchmarks and an extension called [wrk2img](https://github.com/PPACI/wrk2img) to create some nice graphs of our benchmark results. 282 | 283 | Assuming you have both tools installed, let's get started! 284 | 285 | Included with this repo is a script called `run-test.sh` to run the `wrk` benchmark: 286 | ``` 287 | #!/usr/bin/env bash 288 | 289 | if [ -z "$1" ] 290 | then 291 | echo "Oops, pass an arg with a name for output" 292 | echo "Example: ./run-test.sh graalvm-jvm" 293 | exit 1 294 | fi 295 | 296 | name="$1" 297 | 298 | echo -n "Performing benchmark tests ... " 299 | wrk --script post.lua --latency --rate 1000 --connections 5 --threads 2 --duration 60s http://localhost:8080/reactive-fruits.html > results/"$name"_1000.txt 300 | wrk --script post.lua --latency --rate 1000 --connections 5 --threads 2 --duration 60s http://localhost:8080/reactive-fruits.html > results/"$name"_1001.txt 301 | wrk --script post.lua --latency --rate 1000 --connections 5 --threads 2 --duration 60s http://localhost:8080/reactive-fruits.html > results/"$name"_1002.txt 302 | wrk --script post.lua --latency --rate 1000 --connections 5 --threads 2 --duration 60s http://localhost:8080/reactive-fruits.html > results/"$name"_final.txt 303 | echo " done." 304 | cat results/"$name"_1000.txt results/"$name"_1001.txt results/"$name"_1002.txt results/"$name"_1003.txt results/"$name"_final.txt | wrk2img --log -n "$name"-1000,"$name"-1001,"$name"-1002,"$name"-1003,"$name_final" results/"$name"-final.png >> /dev/null 305 | echo "" 306 | echo -n "Creating graph ..." 307 | echo " done." 308 | ``` 309 | 310 | You can adjust the parameters of `wrk` script to fit your environment. We're running four consecutive tests, keeping only the last test for the final graph (see below). 311 | 312 | First, let's start the application in JVM mode: 313 | ``` 314 | $ java -jar ./target/cassandra-quarkus-quickstart-*-runner.jar 315 | ``` 316 | 317 | It's recommended you `warm-up` the JVM by executing a few `time http` commands to access the endpoints. Plus, the `time http` command can help you understand the time it takes to make a round trip. 318 | 319 | Example: 320 | ``` 321 | $ time http POST http://localhost:8080/fruits name="apple" description="red and tasty" 322 | HTTP/1.1 204 No Content 323 | 324 | real 0m0.155s 325 | user 0m0.141s 326 | sys 0m0.011s 327 | ``` 328 | After the JVM is warm, go ahead and start the test. You'll also need to pass an argument for the output file names. In this example, I have the application running in JVM mode, so I named the output files accordingly (`graalvm-jvm`): 329 | ``` 330 | $ ./run-test.sh graalvm-jvm 331 | Performing benchmark tests ... done. 332 | 333 | Creating graph ... done. 334 | ``` 335 | To view the results, check out the `.txt` files in the `results/` directory. In addition, you'll find a `.png` file with a latency graph representation of the tests. 336 | 337 | ![](images/graalvm-jvm-final.png) 338 | 339 | Stop the JVM application and restart it using the native image version. 340 | 341 | ``` 342 | $ target/cassandra-quarkus-quickstart-*-runner 343 | ``` 344 | 345 | Now, re-run the benchmark test making certain to rename the output file names (in this example, `graalvm-native`): 346 | 347 | ``` 348 | $ ./run-test.sh graalvm-native 349 | Performing benchmark tests ... done. 350 | 351 | Creating graph ... done. 352 | ``` 353 | Once again, view the results in the `results/` directory and open the native image latency `.png` graph. 354 | 355 | ![](images/graalvm-native-final.png) 356 | 357 | Wouldn't it be helpful to compare the results of both tests together in one graph? You're in luck, there's another script (`combine-graphs.sh`) that will combine the results from both tests into one graph. Run the script to create the graph: 358 | 359 | ``` 360 | $ ./combine-graphs.sh 361 | Combining graphs ... created graph! 362 | Done. 363 | ``` 364 | Open the graph to view the combined results: 365 | 366 | ![](images/combined-1-final.png) 367 | 368 | NOTE: The graph reflects only the last tests (after the JVM was warm) in order to provide an accurate result. 369 | 370 | ### Summary 371 | 372 | Accessing a Cassandra database from a client application is easy with Quarkus and the Cassandra extension, which provides configuration and native image support for the DataStax Java driver for Apache Cassandra. 373 | --------------------------------------------------------------------------------