Sfoglia il codice sorgente

Helidon - switch to pgclient and rocker (#6919)

Daniel Kec 3 anni fa
parent
commit
145942baa3

+ 2 - 3
frameworks/Java/helidon/README.md

@@ -3,8 +3,7 @@
 This is the Helidon portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
 
 There is currently one repository implementation.
-* [JdbcRepository](src/main/java/io/helidon/benchmark/models/JdbcRepository.java) is using JDBC and an io thread pool of size (2 * cores count) to prevent blocking netty's main event loop. It uses hikaricp to manage the connection pool. It is configured for a maximum of (2 * cores count) concurrent connections. See [About-Pool-Sizing](https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing) for more information. I am assuming that the DB is running on a computer with the same spec as the one running the app, which seems to be the case based on what is written [here](https://www.techempower.com/benchmarks/#section=environment&hw=ph&test=db&l=fjd9b3)
-
+* [JdbcRepository](src/main/java/io/helidon/benchmark/models/JdbcRepository.java) is using Vertx pg client.
 ### Plaintext Test
 
 * [Plaintext test source](src/main/java/io/helidon/benchmark/services/PlainTextService.java)
@@ -32,7 +31,7 @@ There is currently one repository implementation.
 ## Versions
 
 * [Java OpenJDK 11](http://openjdk.java.net/)
-* [Helidon 2.2.1](http://helidon.io/)
+* [Helidon 2.4.0](http://helidon.io/)
 
 ## Test URLs
 

+ 8 - 1
frameworks/Java/helidon/helidon.dockerfile

@@ -11,4 +11,11 @@ COPY --from=maven /helidon/target/benchmark.jar app.jar
 
 EXPOSE 8080
 
-CMD ["java", "-server", "-XX:-UseBiasedLocking", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-jar", "app.jar"]
+CMD java -server \
+    -XX:-UseBiasedLocking \
+    -XX:+UseNUMA \
+    -XX:+AggressiveOpts \
+    -XX:+UseParallelGC \
+    -Dio.netty.buffer.checkBounds=false \
+    -Dio.netty.buffer.checkAccessible=false \
+    -jar app.jar

+ 30 - 19
frameworks/Java/helidon/pom.xml

@@ -21,7 +21,7 @@
     <parent>
         <groupId>io.helidon.applications</groupId>
         <artifactId>helidon-se</artifactId>
-        <version>2.2.1</version>
+        <version>2.4.0</version>
         <relativePath/>
     </parent>
     <groupId>io.helidon</groupId>
@@ -32,19 +32,21 @@
 
     <properties>
         <mainClass>io.helidon.benchmark.Main</mainClass>
+        <rocker.version>1.3.0</rocker.version>
+        <vertx-pg-client.version>4.2.0</vertx-pg-client.version>
     </properties>
 
     <dependencyManagement>
         <dependencies>
             <dependency>
-                <groupId>io.reactivex.rxjava2</groupId>
-                <artifactId>rxjava</artifactId>
-                <version>2.2.8</version>
+                <groupId>io.vertx</groupId>
+                <artifactId>vertx-pg-client</artifactId>
+                <version>${vertx-pg-client.version}</version>
             </dependency>
             <dependency>
-                <groupId>com.github.spullara.mustache.java</groupId>
-                <artifactId>compiler</artifactId>
-                <version>0.9.6</version>
+                <groupId>com.fizzed</groupId>
+                <artifactId>rocker-runtime</artifactId>
+                <version>${rocker.version}</version>
             </dependency>
         </dependencies>
     </dependencyManagement>
@@ -63,20 +65,12 @@
             <artifactId>helidon-config-yaml</artifactId>
         </dependency>
         <dependency>
-            <groupId>io.reactivex.rxjava2</groupId>
-            <artifactId>rxjava</artifactId>
+            <groupId>io.vertx</groupId>
+            <artifactId>vertx-pg-client</artifactId>
         </dependency>
         <dependency>
-            <groupId>com.zaxxer</groupId>
-            <artifactId>HikariCP</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.github.spullara.mustache.java</groupId>
-            <artifactId>compiler</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.postgresql</groupId>
-            <artifactId>postgresql</artifactId>
+            <groupId>com.fizzed</groupId>
+            <artifactId>rocker-runtime</artifactId>
         </dependency>
     </dependencies>
 
@@ -91,6 +85,23 @@
                     </execution>
                 </executions>
             </plugin>
+            <plugin>
+                <groupId>com.fizzed</groupId>
+                <artifactId>rocker-maven-plugin</artifactId>
+                <version>${rocker.version}</version>
+                <executions>
+                    <execution>
+                        <id>generate-rocker-templates</id>
+                        <phase>generate-sources</phase>
+                        <goals>
+                            <goal>generate</goal>
+                        </goals>
+                        <configuration>
+                            <templateDirectory>src/main/resources</templateDirectory>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
         </plugins>
     </build>
 </project>

+ 14 - 38
frameworks/Java/helidon/src/main/java/io/helidon/benchmark/Main.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2021 Oracle and/or its affiliates.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,11 +16,9 @@
 
 package io.helidon.benchmark;
 
-import com.github.mustachejava.DefaultMustacheFactory;
-import com.github.mustachejava.Mustache;
-import com.github.mustachejava.MustacheFactory;
-import com.zaxxer.hikari.HikariConfig;
-import com.zaxxer.hikari.HikariDataSource;
+import java.io.IOException;
+import java.util.logging.LogManager;
+
 import io.helidon.benchmark.models.DbRepository;
 import io.helidon.benchmark.models.JdbcRepository;
 import io.helidon.benchmark.services.DbService;
@@ -31,47 +29,23 @@ import io.helidon.config.Config;
 import io.helidon.media.jsonp.JsonpSupport;
 import io.helidon.webserver.Routing;
 import io.helidon.webserver.WebServer;
-import io.reactivex.Scheduler;
-import io.reactivex.schedulers.Schedulers;
-
-import javax.sql.DataSource;
-import java.io.IOException;
-import java.util.concurrent.Executors;
-import java.util.logging.LogManager;
 
 /**
  * Simple Hello World rest application.
  */
 public final class Main {
 
+    private static final String SERVER_HEADER = "Server";
+    private static final String SERVER_NAME = "Helidon";
+
     /**
      * Cannot be instantiated.
      */
-    private Main() { }
-
-    private static Scheduler getScheduler() {
-        return Schedulers.from(Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2));
-    }
-
-    private static DataSource getDataSource(Config config) {
-        HikariConfig hikariConfig = new HikariConfig();
-        hikariConfig.setJdbcUrl(config.get("jdbcUrl").asString().get());
-        hikariConfig.setUsername(config.get("username").asString().get());
-        hikariConfig.setPassword(config.get("password").asString().get());
-        hikariConfig.setMaximumPoolSize(Runtime.getRuntime().availableProcessors() * 2);
-
-        return new HikariDataSource(hikariConfig);
+    private Main() {
     }
 
     private static DbRepository getRepository(Config config) {
-        DataSource dataSource = getDataSource(config.get("dataSource"));
-        Scheduler scheduler = getScheduler();
-        return new JdbcRepository(dataSource, scheduler);
-    }
-
-    private static Mustache getTemplate() {
-        MustacheFactory mf = new DefaultMustacheFactory();
-        return mf.compile("fortunes.mustache");
+        return new JdbcRepository(config);
     }
 
     /**
@@ -84,18 +58,19 @@ public final class Main {
 
         return Routing.builder()
                 .any((req, res) -> {
-                    res.headers().add("Server", "Helidon");
+                    res.headers().add(SERVER_HEADER, SERVER_NAME);
                     req.next();
                 })
                 .register(new JsonService())
                 .register(new PlainTextService())
                 .register(new DbService(repository))
-                .register(new FortuneService(repository, getTemplate()))
+                .register(new FortuneService(repository))
                 .build();
     }
 
     /**
      * Application main entry point.
+     *
      * @param args command line arguments.
      * @throws IOException if there are problems reading logging properties
      */
@@ -105,10 +80,11 @@ public final class Main {
 
     /**
      * Start the server.
+     *
      * @return the created {@link WebServer} instance
      * @throws IOException if there are problems reading logging properties
      */
-    protected static WebServer startServer() throws IOException {
+    private static WebServer startServer() throws IOException {
 
         // load logging configuration
         LogManager.getLogManager().readConfiguration(

+ 3 - 2
frameworks/Java/helidon/src/main/java/io/helidon/benchmark/models/DbRepository.java

@@ -1,10 +1,11 @@
 package io.helidon.benchmark.models;
 
-import io.reactivex.Single;
-
 import java.util.List;
 
+import io.helidon.common.reactive.Single;
+
 public interface DbRepository {
+
     Single<World> getWorld(int id);
 
     Single<World> updateWorld(World world);

+ 54 - 47
frameworks/Java/helidon/src/main/java/io/helidon/benchmark/models/JdbcRepository.java

@@ -1,67 +1,74 @@
 package io.helidon.benchmark.models;
 
-import io.reactivex.Scheduler;
-import io.reactivex.Single;
-
-import javax.sql.DataSource;
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
 import java.util.ArrayList;
 import java.util.List;
 
+import io.helidon.common.reactive.Single;
+import io.helidon.config.Config;
+
+import io.vertx.core.Vertx;
+import io.vertx.core.VertxOptions;
+import io.vertx.pgclient.PgConnectOptions;
+import io.vertx.pgclient.PgPool;
+import io.vertx.sqlclient.PoolOptions;
+import io.vertx.sqlclient.Row;
+import io.vertx.sqlclient.SqlClient;
+import io.vertx.sqlclient.Tuple;
+
 public class JdbcRepository implements DbRepository {
-    private final DataSource dataSource;
-    private final Scheduler scheduler;
+    private Vertx vertx;
+    private PgConnectOptions connectOptions;
+    private PoolOptions clientOptions;
+    private PoolOptions poolOptions;
+
+    private final ThreadLocal<SqlClient> client = ThreadLocal.withInitial(() -> PgPool.client(vertx, connectOptions, clientOptions));
+    private final ThreadLocal<SqlClient> pool = ThreadLocal.withInitial(() -> PgPool.pool(vertx, connectOptions, poolOptions));
 
-    public JdbcRepository(DataSource dataSource, Scheduler scheduler) {
-        this.dataSource = dataSource;
-        this.scheduler = scheduler;
+    public JdbcRepository(Config config) {
+        vertx = Vertx.vertx(new VertxOptions()
+                .setPreferNativeTransport(true));
+        connectOptions = new PgConnectOptions()
+                .setPort(config.get("port").asInt().orElse(5432))
+                .setCachePreparedStatements(config.get("cache-prepared-statements").asBoolean().orElse(true))
+                .setHost(config.get("host").asString().orElse("tfb-database"))
+                .setDatabase(config.get("db").asString().orElse("hello_world"))
+                .setUser(config.get("username").asString().orElse("benchmarkdbuser"))
+                .setPassword(config.get("password").asString().orElse("benchmarkdbpass"));
+
+        clientOptions = new PoolOptions().setMaxSize(1);
+        poolOptions = new PoolOptions().setMaxSize(4);
     }
 
     @Override
     public Single<World> getWorld(int id) {
-        return Single.fromCallable(() -> {
-            try (Connection connection = dataSource.getConnection()) {
-                    PreparedStatement statement = connection.prepareStatement("SELECT id, randomnumber FROM world WHERE id = ?");
-                    statement.setInt(1, id);
-                    ResultSet rs = statement.executeQuery();
-                    rs.next();
-                    World world = new World(rs.getInt(1), rs.getInt(2));
-                    statement.close();
-                    return world;
-            }
-        }).subscribeOn(this.scheduler);
+        return Single.create(client.get().preparedQuery("SELECT id, randomnumber FROM world WHERE id = $1")
+                .execute(Tuple.of(id))
+                .map(rows -> {
+                    Row r = rows.iterator().next();
+                    return new World(r.getInteger(0), r.getInteger(1));
+                })
+                .toCompletionStage());
     }
 
     @Override
     public Single<World> updateWorld(World world) {
-        return Single.fromCallable(() -> {
-                    try (Connection connection = dataSource.getConnection()) {
-                        PreparedStatement statement = connection.prepareStatement("UPDATE world SET randomnumber = ? WHERE id = ?");
-                        statement.setInt(1, world.randomNumber);
-                        statement.setInt(2, world.id);
-                        statement.execute();
-                        statement.close();
-                        return world;
-                    }
-        }).subscribeOn(this.scheduler);
+        return Single.create(pool.get().preparedQuery("UPDATE world SET randomnumber = $1 WHERE id = $2")
+                .execute(Tuple.of(world.id, world.id))
+                .toCompletionStage()
+                .thenApply(rows -> world));
     }
 
     @Override
     public Single<List<Fortune>> getFortunes() {
-        return Single.fromCallable(() -> {
-            try (Connection connection = dataSource.getConnection()) {
-                PreparedStatement statement = connection.prepareStatement("SELECT id, message FROM fortune");
-                ResultSet rs = statement.executeQuery();
-
-                List<Fortune> fortunes = new ArrayList<>();
-                while (rs.next()) {
-                    fortunes.add(new Fortune(rs.getInt(1), rs.getString(2)));
-                }
-                statement.close();
-                return fortunes;
-            }
-        }).subscribeOn(this.scheduler);
+        return Single.create(client.get().preparedQuery("SELECT id, message FROM fortune")
+                .execute()
+                .map(rows -> {
+                    List<Fortune> fortunes = new ArrayList<>(rows.size() + 1);
+                    for (Row r : rows) {
+                        fortunes.add(new Fortune(r.getInteger(0), r.getString(1)));
+                    }
+                    return fortunes;
+                })
+                .toCompletionStage());
     }
-}
+}

+ 3 - 1
frameworks/Java/helidon/src/main/java/io/helidon/benchmark/models/World.java

@@ -7,6 +7,8 @@ import java.util.Collections;
 
 public final class World {
 
+    private static final String ID_KEY = "id";
+    private static final String ID_RANDOM_NUMBER = "randomNumber";
     private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap());
 
     public int id;
@@ -18,6 +20,6 @@ public final class World {
     }
 
     public JsonObject toJson() {
-        return JSON.createObjectBuilder().add("id", id).add("randomNumber", randomNumber).build();
+        return JSON.createObjectBuilder().add(ID_KEY, id).add(ID_RANDOM_NUMBER, randomNumber).build();
     }
 }

+ 31 - 43
frameworks/Java/helidon/src/main/java/io/helidon/benchmark/services/DbService.java

@@ -1,39 +1,29 @@
 package io.helidon.benchmark.services;
 
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 import java.util.concurrent.ThreadLocalRandom;
 
 import javax.json.Json;
-import javax.json.JsonArray;
 import javax.json.JsonArrayBuilder;
 import javax.json.JsonBuilderFactory;
-import javax.json.JsonObject;
-import javax.json.JsonWriterFactory;
 
 import io.helidon.benchmark.models.DbRepository;
 import io.helidon.benchmark.models.World;
 import io.helidon.common.http.Parameters;
+import io.helidon.common.reactive.Multi;
 import io.helidon.webserver.Routing;
 import io.helidon.webserver.ServerRequest;
 import io.helidon.webserver.ServerResponse;
 import io.helidon.webserver.Service;
-import io.reactivex.Flowable;
-import io.reactivex.Single;
 
 public class DbService implements Service {
 
     private final DbRepository repository;
-    private JsonWriterFactory jsonWriterFactory;
-    private JsonBuilderFactory jsonBuilderFactory;
 
     public DbService(DbRepository repository) {
         this.repository = repository;
-
-        this.jsonWriterFactory = Json.createWriterFactory(null);
-        this.jsonBuilderFactory = Json.createBuilderFactory(Collections.emptyMap());
     }
 
     @Override
@@ -44,63 +34,61 @@ public class DbService implements Service {
     }
 
     private void db(final ServerRequest request,
-                          final ServerResponse response) {
+                    final ServerResponse response) {
         repository.getWorld(randomWorldNumber())
-                .map(World::toJson)
-                .subscribe(jsonObject -> response.send(jsonObject));
+                .forSingle(world -> response.send(world.toJson()));
     }
 
     private void queries(final ServerRequest request,
-                           final ServerResponse response) {
-        Flowable<JsonObject>[] worlds = new Flowable[parseQueryCount(request.queryParams())];
-        Arrays.setAll(worlds, i -> repository.getWorld(randomWorldNumber()).map(World::toJson).toFlowable());
+                         final ServerResponse response) {
+
+        Multi.range(0, parseQueryCount(request.queryParams()))
+                .flatMap(i -> repository.getWorld(randomWorldNumber()))
+                .map(World::toJson)
+                .reduce(JSON::createArrayBuilder, JsonArrayBuilder::add)
+                .map(JsonArrayBuilder::build)
+                .onError(response::send)
+                .forSingle(response::send);
 
-        marshall(worlds).subscribe(jsonObject -> response.send(jsonObject));
     }
 
     private void updates(final ServerRequest request,
                          final ServerResponse response) {
-        Flowable<JsonObject>[] worlds = new Flowable[parseQueryCount(request.queryParams())];
-
-        Arrays.setAll(worlds, i -> repository.getWorld(randomWorldNumber()).flatMapPublisher(world -> {
-            world.randomNumber = randomWorldNumber();
-            return repository.updateWorld(world).map(World::toJson).toFlowable();
-        }));
 
-        marshall(worlds).subscribe(jsonObject -> response.send(jsonObject));
-    }
-
-    private Single<JsonArray> marshall(Flowable<JsonObject>[] worlds) {
-        return Flowable.mergeArray(worlds)
-                .toList()
-                .map(this::buildArray)
-                .doOnError(Throwable::printStackTrace);
+        Multi.range(0, parseQueryCount(request.queryParams()))
+                .flatMap(i -> repository.getWorld(randomWorldNumber()), 1, false, 1)
+                .flatMap(world -> {
+                    world.randomNumber = randomWorldNumber();
+                    return repository.updateWorld(world);
+                }, 1, false, 1)
+                .map(World::toJson)
+                .reduce(JSON::createArrayBuilder, JsonArrayBuilder::add)
+                .map(JsonArrayBuilder::build)
+                .onError(response::send)
+                .forSingle(response::send);
     }
 
-    private JsonArray buildArray(List<JsonObject> jsonObjects) {
-        return jsonObjects.stream().reduce(
-                jsonBuilderFactory.createArrayBuilder(),
-                JsonArrayBuilder::add,
-                JsonArrayBuilder::addAll)
-                .build();
-    }
+    private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap());
 
     private int randomWorldNumber() {
         return 1 + ThreadLocalRandom.current().nextInt(10000);
     }
 
     private int parseQueryCount(Parameters parameters) {
-        Optional<String> textValue = parameters.first("queries");
+        List<String> values = parameters.all("queries");
 
-        if (textValue.isEmpty()) {
+        if (values.isEmpty()) {
             return 1;
         }
+
+        String first = values.get(0);
+
         int parsedValue;
         try {
-            parsedValue = Integer.parseInt(textValue.get());
+            parsedValue = Integer.parseInt(first, 10);
         } catch (NumberFormatException e) {
             return 1;
         }
         return Math.min(500, Math.max(1, parsedValue));
     }
-}
+}

+ 21 - 26
frameworks/Java/helidon/src/main/java/io/helidon/benchmark/services/FortuneService.java

@@ -1,51 +1,46 @@
 package io.helidon.benchmark.services;
 
-import static java.util.Comparator.comparing;
-
-import java.io.ByteArrayOutputStream;
-import java.io.OutputStreamWriter;
 import java.nio.charset.StandardCharsets;
-import java.util.Collections;
-
-import com.github.mustachejava.Mustache;
+import java.util.Comparator;
 
 import io.helidon.benchmark.models.DbRepository;
 import io.helidon.benchmark.models.Fortune;
 import io.helidon.common.http.MediaType;
+import io.helidon.webserver.Handler;
 import io.helidon.webserver.Routing;
 import io.helidon.webserver.ServerRequest;
 import io.helidon.webserver.ServerResponse;
 import io.helidon.webserver.Service;
 
-public class FortuneService implements Service {
+import com.fizzed.rocker.runtime.ArrayOfByteArraysOutput;
+import views.fortunes;
+
+public class FortuneService implements Service, Handler {
+
+    private static final Fortune ADDITIONAL_FORTUNE = new Fortune(0, "Additional fortune added at request time.");
 
     private final DbRepository repository;
-    private final Mustache template;
 
-    public FortuneService(DbRepository repository, Mustache template) {
+    public FortuneService(DbRepository repository) {
         this.repository = repository;
-        this.template = template;
     }
 
     @Override
     public void update(Routing.Rules rules) {
-        rules.get("/fortunes", this::fortunes);
+        rules.get("/fortunes", this);
     }
 
-    private void fortunes(ServerRequest request, ServerResponse response) {
+    @Override
+    public void accept(ServerRequest req, ServerResponse res) {
+        res.headers().contentType(MediaType.TEXT_HTML.withCharset(StandardCharsets.UTF_8.name()));
         repository.getFortunes()
-                .subscribe(fortunes -> {
-                    fortunes.add(new Fortune(0, "Additional fortune added at request time."));
-                    fortunes.sort(comparing(fortune -> fortune.message));
-
-                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
-                    OutputStreamWriter writer = new OutputStreamWriter(baos, StandardCharsets.UTF_8.name());
-
-                    template.execute(writer, Collections.singletonMap("fortunes", fortunes));
-                    writer.flush();
-
-                    response.headers().contentType(MediaType.TEXT_HTML.withCharset(StandardCharsets.UTF_8.name()));
-                    response.send(baos.toByteArray());
+                .forSingle(fortuneList -> {
+                    fortuneList.add(ADDITIONAL_FORTUNE);
+                    fortuneList.sort(Comparator.comparing(Fortune::getMessage));
+                    res.headers().contentType(MediaType.TEXT_HTML.withCharset(StandardCharsets.UTF_8.name()));
+                    res.send(fortunes.template(fortuneList)
+                            .render(ArrayOfByteArraysOutput.FACTORY)
+                            .toByteArray());
                 });
     }
-}
+}

+ 20 - 8
frameworks/Java/helidon/src/main/java/io/helidon/benchmark/services/JsonService.java

@@ -1,24 +1,36 @@
 package io.helidon.benchmark.services;
 
 import java.util.Collections;
+import java.util.Map;
 
 import javax.json.Json;
 import javax.json.JsonBuilderFactory;
 
+import io.helidon.webserver.Handler;
 import io.helidon.webserver.Routing;
+import io.helidon.webserver.ServerRequest;
+import io.helidon.webserver.ServerResponse;
 import io.helidon.webserver.Service;
 
-public class JsonService implements Service {
+public class JsonService implements Service, Handler {
 
-    private JsonBuilderFactory jsonBuilderFactory;
+    private static final String ATTR_NAME = "message";
+    private static final String ATTR_VALUE = "Hello, World!";
+    private static final Map<String, Object> ATTR_MAP = Map.of(ATTR_NAME, ATTR_VALUE);
 
-     public JsonService() {
-         this.jsonBuilderFactory = Json.createBuilderFactory(Collections.emptyMap());
-     }
+    private final JsonBuilderFactory jsonBuilderFactory;
+
+    public JsonService() {
+        this.jsonBuilderFactory = Json.createBuilderFactory(Collections.emptyMap());
+    }
 
     @Override
     public void update(Routing.Rules rules) {
-        rules.get("/json",
-                (req, res) -> res.send(jsonBuilderFactory.createObjectBuilder(Collections.singletonMap("message", "Hello, World!")).build()));
+        rules.get("/json", this);
+    }
+
+    @Override
+    public void accept(ServerRequest req, ServerResponse res) {
+        res.send(jsonBuilderFactory.createObjectBuilder(ATTR_MAP).build());
     }
-}
+}

+ 18 - 4
frameworks/Java/helidon/src/main/java/io/helidon/benchmark/services/PlainTextService.java

@@ -1,13 +1,27 @@
 package io.helidon.benchmark.services;
 
+import java.nio.charset.StandardCharsets;
+
+import io.helidon.common.http.Http;
+import io.helidon.common.http.MediaType;
+import io.helidon.webserver.Handler;
 import io.helidon.webserver.Routing;
+import io.helidon.webserver.ServerRequest;
+import io.helidon.webserver.ServerResponse;
 import io.helidon.webserver.Service;
 
-public class PlainTextService implements Service {
+public class PlainTextService implements Service, Handler {
+
+    private static final byte[] MESSAGE = "Hello, World!".getBytes(StandardCharsets.UTF_8);
+    private static final String MEDIA_TYPE = MediaType.TEXT_PLAIN.toString();
 
     @Override
     public void update(Routing.Rules rules) {
-        rules.get("/plaintext",
-                (req, res) -> res.send("Hello, World!"));
+        rules.get("/plaintext", this);
+    }
+
+    @Override
+    public void accept(ServerRequest req, ServerResponse res) {
+        res.addHeader(Http.Header.CONTENT_TYPE, MEDIA_TYPE).send(MESSAGE);
     }
-}
+}

+ 4 - 4
frameworks/Java/helidon/src/main/resources/application.yaml

@@ -2,7 +2,7 @@ server:
   port: 8080
   host: 0.0.0.0
 
-dataSource:
-  jdbcUrl: "jdbc:postgresql://tfb-database:5432/hello_world"
-  username: benchmarkdbuser
-  password: benchmarkdbpass
+host: "tfb-database"
+db: "hello_world"
+username: benchmarkdbuser
+password: benchmarkdbpass

+ 0 - 20
frameworks/Java/helidon/src/main/resources/fortunes.mustache

@@ -1,20 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <title>Fortunes</title>
-</head>
-<body>
-<table>
-    <tr>
-        <th>id</th>
-        <th>message</th>
-    </tr>
-    {{#fortunes}}
-        <tr>
-            <td>{{id}}</td>
-            <td>{{message}}</td>
-        </tr>
-    {{/fortunes}}
-</table>
-</body>
-</html>

+ 24 - 0
frameworks/Java/helidon/src/main/resources/views/fortunes.rocker.html

@@ -0,0 +1,24 @@
+@import io.helidon.benchmark.models.Fortune
+@import java.util.List
+@args (List<Fortune> fortunes)
+
+<!DOCTYPE html>
+<html>
+<head>
+<title>Fortunes</title>
+</head>
+<body>
+<table>
+    <tr>
+        <th>id</th>
+        <th>message</th>
+    </tr>
+    @for (f : fortunes) {
+    <tr>
+        <td>@f.getId()</td>
+        <td>@f.getMessage()</td>
+    </tr>
+    }
+</table>
+</body>
+</html>