Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: move to quarkus #165

Merged
merged 9 commits into from
Jan 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
52 changes: 27 additions & 25 deletions README.adoc
Original file line number Diff line number Diff line change
@@ -1,38 +1,37 @@
= apim samples

image:https://img.shields.io/badge/vert.x-4.3.6-purple.svg[link="https://vertx.io"]
This application provides an API sample
that I use to play with https://github.com/gravitee-io/gravitee-api-management[Gravitee APIM].

This application provide API sample that I use to play with https://github.com/gravitee-io/gravitee-api-management[Gravite APIM].

== Building

To launch your tests:
To launch tests:
----
./gradlew clean test
./gradlew test
----

To run your application:
To run application in Dev mode:
----
./gradlew clean run
./gradlew --console=plain quarkusDev
----

== Configuration

Configuration is provided through environment variables. Available variables are defined
link:app/src/main/kotlin/io/apim/samples/Configuration.kt[here]

[,java]
To build the application:
----
include::app/src/main/kotlin/io/apim/samples/Configuration.kt[lines=2..]
./gradlew build docker
----

== Configuration

Quarkus handles the configuration of the application.

== Available endpoints

It starts 3 http servers
It starts a single http server to handle

- one used to handle regular HTTP request. (default port 8888)
- one used to handle WebSockets. (default port 8890)
- one used to handle GRPC. (default port 8892)
- regular HTTP request.
- WebSockets.
- GRPC services.

=== HTTP /echo

Expand Down Expand Up @@ -77,7 +76,7 @@ This endpoint can receive WebSocket request. It will copy the request received i

Using https://github.com/vi/websocat[websocat]:
----
websocat -1 ws://localhost:8890/ws/echo
websocat -1 ws://localhost:8888/ws/echo
{"message": "Hello"}
----

Expand All @@ -95,28 +94,31 @@ will respond

=== GRPC

The server provide an adapted example of the Route Guide from https://github.com/grpc/grpc-java/tree/master/examples[gRPC examples]
The application provides 2 gRPC services adapted from https://github.com/grpc/grpc-java/tree/master/examples[gRPC examples]:

- the Route Guide service
- the Greeter service

The service shows the various kind of gRPC service calls:
The Route Guide service shows the various kind of gRPC service calls:

- simple RPC
- server-side streaming RPC
- client-side streaming RPC
- bidirectional streaming RPC

The proto file is available at
Proto files are available at

- link:app/src/main/resources/grpc/route_guide.proto[here]
- or it can be downloaded using the HTTP server: http://localhost:8888/grpc/route_guide.proto
- link:app-quarkus/src/main/proto[here]
- or it can be downloaded using the HTTP server: http://localhost:8888/proto/route_guide.proto or http://localhost:8888/proto/helloworld.proto

==== Example

Using https://github.com/fullstorydev/grpcurl[grpcurl].
(The server does not expose Reflection service, therefore we need to provide the protofile to the client)
(The server exposes Reflection service, therefore no need to provide the protofile to the client)

[source,bash]
----
grpcurl -d '{"latitude": 413628156, "longitude": -749015468}' -import-path app/src/main/resources/grpc -proto route_guide.proto -plaintext localhost:8892 routeguide.RouteGuide/GetFeature
grpcurl -d '{"latitude": 413628156, "longitude": -749015468}' -plaintext localhost:8888 routeguide.RouteGuide/GetFeature
----

will respond
Expand Down
96 changes: 96 additions & 0 deletions app-quarkus/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
###
#
# This image uses the `run-java.sh` script to run the application.
# This scripts computes the command line to execute your Java application, and
# includes memory/GC tuning.
#
# You can configure the behavior using the following environment properties:
#
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
# in JAVA_OPTS (example: "-Dsome.property=foo")
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
# used to calculate a default maximal heap memory based on a containers restriction.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
# of the container available memory as set here. The default is `50` which means 50%
# of the available memory is used as an upper boundary. You can skip this mechanism by
# setting this value to `0` in which case no `-Xmx` option is added.
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
# is used to calculate a default initial heap memory based on the maximum heap memory.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
# is used as the initial heap size. You can skip this mechanism by setting this value
# to `0` in which case no `-Xms` option is added (example: "25")
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
# This is used to calculate the maximum value of the initial heap memory. If used in
# a container without any memory constraints for the container then this option has
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
# here. The default is 4096MB which means the calculated value of `-Xms` never will
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
# when things are happening. This option, if set to true, will set
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
# true").
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
# (example: "20")
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
# (example: "40")
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
# (example: "4")
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
# previous GC times. (example: "90")
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
# contain the necessary JRE command-line options to specify the required GC, which
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
FROM eclipse-temurin:21-jre-alpine AS builder

ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'

RUN apk -U upgrade \
&& apk add --no-cache curl \
&& rm -rf /var/cache/apk/*

ARG RUN_JAVA_VERSION=1.3.8
RUN curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /tmp/run-java.sh

RUN mkdir -m 774 -p /quarkus-app/app \
&& mkdir -m 774 -p /quarkus-app/lib \
&& mkdir -m 774 -p /quarkus-app/quarkus \
&& mv /tmp/run-java.sh /quarkus-app/run-java.sh \
&& chmod 774 /quarkus-app/run-java.sh \
&& chown -R guest:users /quarkus-app

# Make four distinct layers so if there are application changes the library layers can be re-used
COPY --chown=guest:users ./quarkus/ /quarkus-app/quarkus/
COPY --chown=guest:users ./*.jar /quarkus-app/
COPY --chown=guest:users ./lib/ /quarkus-app/lib/
COPY --chown=guest:users ./app/ /quarkus-app/app/

FROM eclipse-temurin:21-jre-alpine

ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'

COPY --from=builder --chown=guest:users --chmod=775 /quarkus-app /app

WORKDIR /app
USER guest

EXPOSE 8080
ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV JAVA_APP_JAR="quarkus-run.jar"
ENTRYPOINT [ "./run-java.sh" ]
92 changes: 92 additions & 0 deletions app-quarkus/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import com.palantir.gradle.docker.DockerExtension

plugins {
alias(libs.plugins.axion)
alias(libs.plugins.docker)
alias(libs.plugins.kotlin.allopen)
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.quarkus)
}

repositories {
mavenCentral()
maven {
url = uri("https://packages.confluent.io/maven/")
name = "Confluent"
content {
includeGroup("io.confluent")
includeGroup("org.apache.kafka")
}
}
}

scmVersion {
tag {
prefix.set("")
}
}
project.version = scmVersion.version

dependencies {
implementation(enforcedPlatform(libs.quarkus.bom))
implementation(enforcedPlatform(libs.mutiny.clients.bom))

implementation(libs.avro)
implementation(libs.kafka.serializer.avro)
implementation(libs.kotlin.faker)
implementation(libs.slf4j.api)
implementation("io.quarkus:quarkus-arc")
implementation("io.quarkus:quarkus-grpc")
implementation("io.quarkus:quarkus-kotlin")
implementation("io.quarkus:quarkus-reactive-routes")
implementation("io.quarkus:quarkus-resteasy-reactive")
implementation("io.quarkus:quarkus-smallrye-health")
implementation("io.quarkus:quarkus-vertx")
implementation("io.quarkus:quarkus-websockets")
implementation("io.vertx:vertx-lang-kotlin")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

testImplementation(libs.junit.jupiter.api)
testImplementation(libs.bundles.strikt)
testImplementation("io.rest-assured:rest-assured")
testImplementation("io.rest-assured:kotlin-extensions")
testImplementation("io.quarkus:quarkus-junit5")
testImplementation("io.smallrye.reactive:smallrye-mutiny-vertx-web-client")

testRuntimeOnly(libs.junit.jupiter.engine)
}

java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

allOpen {
annotation("javax.ws.rs.Path")
annotation("javax.enterprise.context.ApplicationScoped")
annotation("io.quarkus.test.junit.QuarkusTest")
}

tasks.withType<Test> {
systemProperty("java.util.logging.manager", "org.jboss.logmanager.LogManager")
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions.jvmTarget = JavaVersion.VERSION_17.toString()
kotlinOptions.javaParameters = true
}

tasks.register("copyProto", Copy::class.java) {
from("src/main/proto")
into(layout.buildDirectory.dir("resources/main/META-INF/resources/proto"))
}

tasks.withType<ProcessResources> {
dependsOn("copyProto")
}

configure<DockerExtension> {
name = "${rootProject.name}:${project.version}"
files(tasks.findByName("quarkusBuild")?.outputs?.files)
tag("DockerHub", "jgiovaresco/${name}")
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package io.apim.samples.rest
package io.apim.samples.core

import io.vertx.core.MultiMap
import io.vertx.ext.web.impl.ParsableMIMEValue
import io.vertx.rxjava3.core.MultiMap

/** Transform a MultiMap into a simple map. Multiple values are joined in a string separated with ; */
fun MultiMap.toSimpleMap() = this.entries()
.groupBy { it.key }
.groupBy { it.key.lowercase() }
.mapValues { it.value.joinToString(";") { h -> h.value } }

fun ParsableMIMEValue.isText(): Boolean {
Expand All @@ -15,7 +15,3 @@ fun ParsableMIMEValue.isText(): Boolean {
fun ParsableMIMEValue.isJson(): Boolean {
return this.component() == "application" && this.subComponent().contains("json")
}

fun ParsableMIMEValue.isAvro(): Boolean {
return this.component() == "avro" || this.subComponent().contains("avro")
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.apim.samples.avro
package io.apim.samples.core.avro

import io.github.serpro69.kfaker.Faker
import org.apache.avro.Schema
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.apim.samples.avro
package io.apim.samples.core.avro

interface AvroSerDe {
fun serialize(data: Any?): ByteArray
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.apim.samples.core.avro

import io.apim.samples.core.avro.impl.AvroSerDeConfluent
import io.apim.samples.core.avro.impl.AvroSerDeSimple
import io.apim.samples.core.avro.impl.JsonSerDe
import jakarta.enterprise.context.ApplicationScoped
import org.apache.avro.Schema

enum class SerializationFormat {
CONFLUENT, SIMPLE
}

@ApplicationScoped
class SerDeFactory {
fun newAvroSerDe(schema: Schema, format: SerializationFormat): AvroSerDe = when (format) {
SerializationFormat.SIMPLE -> AvroSerDeSimple(schema)
SerializationFormat.CONFLUENT -> AvroSerDeConfluent(schema)
}

fun newJsonSerDe(schema: Schema): JsonSerDe = JsonSerDe(schema)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.apim.samples.avro
package io.apim.samples.core.avro.impl

import io.apim.samples.core.avro.AvroSerDe
import io.confluent.kafka.serializers.KafkaAvroDeserializer
import io.confluent.kafka.serializers.KafkaAvroSerializer
import org.apache.avro.Schema
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.apim.samples.avro
package io.apim.samples.core.avro.impl

import io.apim.samples.core.avro.AvroSerDe
import org.apache.avro.Schema
import org.apache.avro.generic.GenericDatumReader
import org.apache.avro.generic.GenericDatumWriter
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.apim.samples.avro
package io.apim.samples.core.avro.impl

import org.apache.avro.Schema
import org.apache.avro.generic.GenericDatumReader
Expand All @@ -19,7 +19,6 @@ class JsonSerDe(private val schema: Schema) {
encoder.flush()
output.flush()


return output.toString(StandardCharsets.UTF_8)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.apim.samples.ports.grpc

import io.grpc.examples.helloworld.Greeter
import io.grpc.examples.helloworld.HelloReply
import io.grpc.examples.helloworld.HelloRequest
import io.quarkus.grpc.GrpcService
import io.smallrye.mutiny.Uni

@GrpcService
class GreeterGrpcService() : Greeter {
override fun sayHello(request: HelloRequest): Uni<HelloReply> {
var name = request.name
if(name.isBlank()) {
name = "Stranger"
}

return Uni.createFrom().item(HelloReply.newBuilder().setMessage("Hello $name").build())
}
}