Add Quarkus example (#226)

* Add Quarkus example

Signed-off-by: ruromero <rromerom@redhat.com>

* Rename project and remove unrelated files

Signed-off-by: ruromero <rromerom@redhat.com>
This commit is contained in:
Ruben Romero Montes 2020-09-07 10:34:11 +02:00 committed by GitHub
parent c3904fbff4
commit 29c9eaa23f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 446 additions and 0 deletions

View File

@ -15,6 +15,7 @@
<modules>
<module>kafka</module>
<module>restful-ws-quarkus</module>
<module>vertx</module>
</modules>

View File

@ -0,0 +1,85 @@
# Cloudevents Restful WS Quarkus example
This sample application has a `/users` REST endpoint in which you can manage the different users.
The way to create users is through CloudEvents. Here is an example POST:
```shell script
curl -v http://localhost:8080/users \
-H "Ce-Specversion: 1.0" \
-H "Ce-Type: User" \
-H "Ce-Source: io.cloudevents.examples/user" \
-H "Ce-Id: 536808d3-88be-4077-9d7a-a3f162705f78" \
-H "Content-Type: application/json" \
-H "Ce-Subject: SUBJ-0001" \
-d @examples/user.json
> POST /users HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.69.1
> Accept: */*
> Ce-Specversion: 1.0
> Ce-Type: User
> Ce-Source: io.cloudevents.examples/user
> Ce-Id: 536808d3-88be-4077-9d7a-a3f162705f78
> Content-Type: application/json
> Ce-Subject: SUBJ-0001
> Content-Length: 88
< HTTP/1.1 201 Created
< Content-Length: 0
< Location: http://localhost:8080/users
```
In order to also show how to create and send CloudEvents, generated events will be periodically sent
each 2 seconds through HTTP to the same endpoint using a REST client.
Check the following URL to view the existing users:
```shell script
$ curl http://localhost:8080/users
{
"user1": {
"username": "user1",
"firstName": "firstName1",
"lastName": "lastName1",
"age": 1
},
"user2": {
"username": "user2",
"firstName": "firstName2",
"lastName": "lastName2",
"age": 2
}
}
```
## Build and execution
This project uses Quarkus, the Supersonic Subatomic Java Framework.
If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ .
### Running the application in dev mode
You can run your application in dev mode that enables live coding using:
```
mvn quarkus:dev
```
### Packaging and running the application
The application can be packaged using `mvn package`.
It produces the `cloudevents-restful-ws-quarkus-example-1.0-SNAPSHOT-runner.jar` file in the `/target` directory.
Be aware that its not an _über-jar_ as the dependencies are copied into the `target/lib` directory.
The application is now runnable using `java -jar target/cloudevents-restful-ws-quarkus-example-1.0-SNAPSHOT-runner.jar`.
### Creating a native executable
You can create a native executable using: `mvn package -Pnative`.
Or, if you don't have GraalVM installed, you can run the native executable build in a container using: `mvn package -Pnative -Dquarkus.native.container-build=true`.
You can then execute your native executable with: `./target/cloudevents-restful-ws-quarkus-example-1.0-SNAPSHOT-runner`
If you want to learn more about building native executables, please consult https://quarkus.io/guides/building-native-image.

View File

@ -0,0 +1,6 @@
{
"username": "jsmith",
"firstName": "John",
"lastName": "Smith",
"age": 37
}

View File

@ -0,0 +1,118 @@
<?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">
<parent>
<artifactId>cloudevents-examples</artifactId>
<groupId>io.cloudevents</groupId>
<version>2.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloudevents-restful-ws-quarkus-example</artifactId>
<properties>
<quarkus-plugin.version>1.7.1.Final</quarkus-plugin.version>
<quarkus.platform.artifact-id>quarkus-universe-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
<quarkus.platform.version>1.7.1.Final</quarkus.platform.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>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client</artifactId>
</dependency>
<dependency>
<groupId>io.cloudevents</groupId>
<artifactId>cloudevents-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.cloudevents</groupId>
<artifactId>cloudevents-http-restful-ws</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.cloudevents</groupId>
<artifactId>cloudevents-json-jackson</artifactId>
<version>${project.version}</version>
</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>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>prepare</goal>
<goal>prepare-tests</goal>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${maven-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>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<quarkus.package.type>native</quarkus.package.type>
</properties>
</profile>
</profiles>
</project>

View File

@ -0,0 +1,18 @@
package io.cloudevents.examples.quarkus.client;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import io.cloudevents.CloudEvent;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@Path("/users")
@RegisterRestClient
public interface UserClient {
@POST
@Produces(MediaType.APPLICATION_JSON)
void emit(CloudEvent event);
}

View File

@ -0,0 +1,59 @@
package io.cloudevents.examples.quarkus.client;
import java.net.URI;
import java.time.Duration;
import java.util.UUID;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
import javax.ws.rs.core.MediaType;
import io.cloudevents.CloudEvent;
import io.cloudevents.core.builder.CloudEventBuilder;
import io.cloudevents.examples.quarkus.model.User;
import io.quarkus.runtime.StartupEvent;
import io.smallrye.mutiny.Multi;
import io.vertx.core.json.Json;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ApplicationScoped
public class UserEventsGenerator {
private static final Logger LOGGER = LoggerFactory.getLogger(UserEventsGenerator.class);
@Inject
@RestClient
UserClient userClient;
public void init(@Observes StartupEvent startupEvent) {
Multi.createFrom().ticks().every(Duration.ofSeconds(2))
.onItem()
.transform(this::createEvent)
.subscribe()
.with(event -> {
LOGGER.info("try to emit user: {}", event.getId());
userClient.emit(event);
});
}
private CloudEvent createEvent(long id) {
return CloudEventBuilder.v1()
.withSource(URI.create("example"))
.withType("io.cloudevents.examples.quarkus.user")
.withId(UUID.randomUUID().toString())
.withDataContentType(MediaType.APPLICATION_JSON)
.withData(createUserAsByteArray(id))
.build();
}
private byte[] createUserAsByteArray(Long id) {
User user = new User()
.setAge(id.intValue())
.setUsername("user" + id)
.setFirstName("firstName" + id)
.setLastName("lastName" + id);
return Json.encode(user).getBytes();
}
}

View File

@ -0,0 +1,55 @@
package io.cloudevents.examples.quarkus.model;
public class User {
private String username;
private String firstName;
private String lastName;
private Integer age;
public String getUsername() {
return username;
}
public User setUsername(String username) {
this.username = username;
return this;
}
public String getFirstName() {
return firstName;
}
public User setFirstName(String firstName) {
this.firstName = firstName;
return this;
}
public String getLastName() {
return lastName;
}
public User setLastName(String lastName) {
this.lastName = lastName;
return this;
}
public Integer getAge() {
return age;
}
public User setAge(Integer age) {
this.age = age;
return this;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", age=" + age +
'}';
}
}

View File

@ -0,0 +1,66 @@
package io.cloudevents.examples.quarkus.resources;
import java.util.HashMap;
import java.util.Map;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import io.cloudevents.CloudEvent;
import io.cloudevents.examples.quarkus.model.User;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.Json;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Path("/users")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class UserResource {
private static final Logger LOGGER = LoggerFactory.getLogger(UserResource.class);
@Context
UriInfo uriInfo;
private Map<String, User> users = new HashMap<>();
@GET
@Path("/{username}")
public User get(@PathParam("username") String username) {
if (users.containsKey(username)) {
return users.get(username);
}
throw new NotFoundException();
}
@GET
public Map<String, User> list() {
return users;
}
@POST
public Response create(CloudEvent event) {
if (event == null || event.getData() == null) {
throw new BadRequestException("Invalid data received. Null or empty event");
}
User user = Json.decodeValue(Buffer.buffer(event.getData()), User.class);
if (users.containsKey(user.getUsername())) {
throw new BadRequestException("Username already exists: " + user.getUsername());
}
LOGGER.info("Received User: {}", user);
users.put(user.getUsername(), user);
return Response
.created(uriInfo.getAbsolutePathBuilder().build(event.getId()))
.build();
}
}

View File

@ -0,0 +1,9 @@
# Configuration file
# key = value
## The Rest client will send events to the local UserResource
io.cloudevents.examples.quarkus.client.UserClient/mp-rest/url=http://localhost:8080
io.cloudevents.examples.quarkus.client.UserClient/mp-rest/scope=javax.inject.Singleton
quarkus.index-dependency.cloudevents.group-id=io.cloudevents
quarkus.index-dependency.cloudevents.artifact-id=cloudevents-http-restful-ws

View File

@ -0,0 +1,9 @@
package io.cloudevents.examples.quarkus;
import io.quarkus.test.junit.NativeImageTest;
@NativeImageTest
public class NativeUserResourceTestIT extends UserResourceTest {
// Execute the same tests but in native mode.
}

View File

@ -0,0 +1,20 @@
package io.cloudevents.examples.quarkus;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
@QuarkusTest
public class UserResourceTest {
@Test
public void testUserEndpoint() {
given()
.when().get("/users")
.then()
.statusCode(200);
}
}