diff --git a/frameworks/Java/loveqq/README.md b/frameworks/Java/loveqq/README.md new file mode 100644 index 00000000000..4eddc224f6d --- /dev/null +++ b/frameworks/Java/loveqq/README.md @@ -0,0 +1,60 @@ +# Loveqq MVC Benchmarking Test + +This is the Loveqq MVC portion of a [benchmarking test suite](../) comparing a variety of web development platforms. + +An embedded reactor-netty is used for the web server. + +### Plaintext Test + +* [Plaintext test source](src/main/java/com/kfyty/benchmark/example/controller/WebMvcController.java) + +### JSON Serialization Test + +* [JSON test source](src/main/java/com/kfyty/benchmark/example/controller/WebMvcController.java) + +### Database Query Test + +* [Database Query test source](src/main/java/com/kfyty/benchmark/example/controller/WebMvcController.java) + +### Database Queries Test + +* [Database Queries test source](src/main/java/com/kfyty/benchmark/example/controller/WebMvcController.java) + +### Database Update Test + +* [Database Update test source](src/main/java/com/kfyty/benchmark/example/controller/WebMvcController.java) + +### Template rendering Test + +* [Template rendering test source](src/main/java/com/kfyty/benchmark/example/controller/WebMvcController.java) + +## Versions + +* [Java OpenJDK 21](http://openjdk.java.net/) +* [loveqq-framework 1.1.6-M5](http://github.com/kfyty/loveqq-framework) + +## Test URLs + +### Plaintext Test + + http://localhost:8080/plaintext + +### JSON Encoding Test + + http://localhost:8080/json + +### Database Query Test + + http://localhost:8080/db + +### Database Queries Test + + http://localhost:8080/queries?queries=5 + +### Database Update Test + + http://localhost:8080/updates?queries=5 + +### Template rendering Test + + http://localhost:8080/fortunes diff --git a/frameworks/Java/loveqq/benchmark_config.json b/frameworks/Java/loveqq/benchmark_config.json new file mode 100644 index 00000000000..def02465949 --- /dev/null +++ b/frameworks/Java/loveqq/benchmark_config.json @@ -0,0 +1,30 @@ +{ + "framework": "loveqq", + "tests": [ + { + "default": { + "plaintext_url": "/plaintext", + "json_url": "/json", + "db_url": "/db", + "query_url": "/queries?queries=", + "update_url": "/updates?queries=", + "fortune_url": "/fortunes", + "port": 8080, + "approach": "Realistic", + "classification": "Fullstack", + "database": "Postgres", + "framework": "loveqq", + "language": "Java", + "flavor": "None", + "orm": "Micro", + "platform": "Netty", + "webserver": "reactor-netty", + "os": "Linux", + "database_os": "Linux", + "display_name": "loveqq", + "notes": "", + "versus": "None" + } + } + ] +} diff --git a/frameworks/Java/loveqq/config.toml b/frameworks/Java/loveqq/config.toml new file mode 100644 index 00000000000..5b7312c2ab8 --- /dev/null +++ b/frameworks/Java/loveqq/config.toml @@ -0,0 +1,19 @@ +[framework] +name = "loveqq" + +[main] +urls.plaintext = "/plaintext" +urls.json = "/json" +urls.db = "/db" +urls.query = "/queries?queries=" +urls.update = "/updates?queries=" +urls.fortune = "/fortunes" +approach = "Realistic" +classification = "Fullstack" +database = "Postgres" +database_os = "Linux" +os = "Linux" +orm = "Micro" +platform = "Netty" +webserver = "reactor-netty" +versus = "" diff --git a/frameworks/Java/loveqq/loveqq.dockerfile b/frameworks/Java/loveqq/loveqq.dockerfile new file mode 100644 index 00000000000..2a204a648ba --- /dev/null +++ b/frameworks/Java/loveqq/loveqq.dockerfile @@ -0,0 +1,14 @@ +FROM maven:3.9.7-amazoncorretto-21 as maven +WORKDIR /loveqq +COPY src src +COPY pom.xml pom.xml +RUN mvn package -P !default,!dev,!gpg + +FROM bellsoft/liberica-openjre-debian:22 +WORKDIR /loveqq +COPY --from=maven /loveqq/target/boot-lib boot-lib +COPY --from=maven /loveqq/target/loveqq-benchmark-1.0-SNAPSHOT.jar app.jar + +EXPOSE 8080 + +CMD ["java", "-server", "--add-opens=java.base/java.lang=ALL-UNNAMED", "--add-opens=java.base/java.lang.reflect=ALL-UNNAMED", "--add-opens=java.base/sun.reflect.annotation=ALL-UNNAMED", "-jar", "app.jar"] \ No newline at end of file diff --git a/frameworks/Java/loveqq/pom.xml b/frameworks/Java/loveqq/pom.xml new file mode 100644 index 00000000000..79a49cbe303 --- /dev/null +++ b/frameworks/Java/loveqq/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + + com.kfyty + loveqq-framework + 1.1.6-M5 + + + loveqq-benchmark + 1.0-SNAPSHOT + + + 21 + 42.7.7 + 1.3.6 + com.kfyty.benchmark.example.Main + + + + + com.kfyty + loveqq-boot + ${loveqq.framework.version} + + + + com.kfyty + loveqq-boot-starter-datasource + ${loveqq.framework.version} + + + + com.kfyty + loveqq-boot-starter-netty + ${loveqq.framework.version} + + + + com.kfyty + loveqq-boot-starter-logback + ${loveqq.framework.version} + + + + org.yaml + snakeyaml + + + + org.postgresql + postgresql + ${postgresql.version} + + + + io.jstach + jstachio + ${jstachio.version} + + + + io.jstach + jstachio-apt + ${jstachio.version} + provided + + + + com.kfyty + loveqq-boot-starter-test + ${loveqq.framework.version} + test + + + \ No newline at end of file diff --git a/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/Main.java b/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/Main.java new file mode 100644 index 00000000000..da61682df29 --- /dev/null +++ b/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/Main.java @@ -0,0 +1,14 @@ +package com.kfyty.benchmark.example; + +import com.kfyty.loveqq.framework.boot.K; +import com.kfyty.loveqq.framework.core.autoconfig.annotation.BootApplication; +import com.kfyty.loveqq.framework.web.core.autoconfig.annotation.EnableWebMvc; + +@EnableWebMvc +@BootApplication +public class Main { + + public static void main(String[] args) { + K.start(Main.class, args); + } +} diff --git a/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/controller/WebMvcController.java b/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/controller/WebMvcController.java new file mode 100644 index 00000000000..d2d9bef5b59 --- /dev/null +++ b/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/controller/WebMvcController.java @@ -0,0 +1,111 @@ +package com.kfyty.benchmark.example.controller; + +import com.kfyty.benchmark.example.model.Fortune; +import com.kfyty.benchmark.example.model.Fortunes; +import com.kfyty.benchmark.example.model.World; +import com.kfyty.benchmark.example.repository.DbRepository; +import com.kfyty.benchmark.example.utils.Utils; +import com.kfyty.loveqq.framework.web.core.annotation.GetMapping; +import com.kfyty.loveqq.framework.web.core.annotation.RestController; +import com.kfyty.loveqq.framework.web.core.annotation.bind.RequestParam; +import com.kfyty.loveqq.framework.web.core.http.ServerResponse; +import io.jstach.jstachio.JStachio; + +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +@RestController +public class WebMvcController { + private static final byte[] TEXT_BODY = "Hello, World!".getBytes(StandardCharsets.UTF_8); + + private final DbRepository dbRepository; + + public WebMvcController(DbRepository dbRepository) { + this.dbRepository = dbRepository; + } + + /** + * GET /plaintext HTTP/1.1 + */ + @GetMapping(value = "/plaintext", produces = "text/plain; charset=UTF-8") + public byte[] plaintext(ServerResponse response) { + response.setHeader("Content-Length", "13"); + return TEXT_BODY; + } + + /** + * GET /json HTTP/1.1 + */ + @GetMapping(value = "/json") + public Map json(ServerResponse response) { + response.setHeader("Content-Length", "27"); + return Map.of("message", "Hello, world!"); + } + + /** + * GET /db HTTP/1.1 + */ + @GetMapping("/db") + public World db() { + return dbRepository.getWorld(Utils.randomWorldNumber()); + } + + /** + * GET /queries?queries=10 HTTP/1.1 + */ + @GetMapping("/queries") + public World[] queries(@RequestParam(defaultValue = "1") String queries) { + return Utils.randomWorldNumbers().limit(parseQueryCount(queries)).mapToObj(dbRepository::getWorld).toArray(World[]::new); + } + + /** + * GET /updates?queries=10 HTTP/1.1 + */ + @GetMapping("/updates") + public List updates(@RequestParam(defaultValue = "1") String queries) { + List worlds = Utils.randomWorldNumbers() + .limit(parseQueryCount(queries)) + .mapToObj(id -> { + World world = dbRepository.getWorld(id); + int randomNumber; + do { + randomNumber = Utils.randomWorldNumber(); + } while (randomNumber == world.randomNumber); + world.randomNumber = randomNumber; + return world; + }) + .sorted(Comparator.comparingInt(w -> w.id)) + .toList(); + dbRepository.updateWorlds(worlds); + return worlds; + } + + /** + * GET /fortunes HTTP/1.1 + */ + @GetMapping(value = "/fortunes", produces = "text/html; charset=UTF-8") + public String fortunes() { + List fortunes = dbRepository.fortunes(); + fortunes.add(new Fortune(0, "Additional fortune added at request time.")); + + Collections.sort(fortunes); + + return JStachio.render(new Fortunes(fortunes)); + } + + private static int parseQueryCount(String textValue) { + if (textValue == null) { + return 1; + } + int parsedValue; + try { + parsedValue = Integer.parseInt(textValue); + } catch (NumberFormatException e) { + return 1; + } + return Math.min(500, Math.max(1, parsedValue)); + } +} diff --git a/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/filter/ResponseHeaderFilter.java b/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/filter/ResponseHeaderFilter.java new file mode 100644 index 00000000000..e14f0ad0ab5 --- /dev/null +++ b/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/filter/ResponseHeaderFilter.java @@ -0,0 +1,25 @@ +package com.kfyty.benchmark.example.filter; + +import com.kfyty.loveqq.framework.core.autoconfig.annotation.Component; +import com.kfyty.loveqq.framework.web.core.filter.Filter; +import com.kfyty.loveqq.framework.web.core.filter.FilterChain; +import com.kfyty.loveqq.framework.web.core.http.ServerRequest; +import com.kfyty.loveqq.framework.web.core.http.ServerResponse; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; + +import java.time.Clock; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +@Component +public class ResponseHeaderFilter implements Filter { + private static final Clock clock = Clock.systemDefaultZone(); + + @Override + public Publisher doFilter(ServerRequest request, ServerResponse response, FilterChain chain) { + response.setHeader("Server", "loveqq"); + response.setHeader("Date", DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(clock))); + return Mono.from(chain.doFilter(request, response)); + } +} diff --git a/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/model/Fortune.java b/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/model/Fortune.java new file mode 100644 index 00000000000..60254d706ba --- /dev/null +++ b/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/model/Fortune.java @@ -0,0 +1,19 @@ +package com.kfyty.benchmark.example.model; + +public final class Fortune implements Comparable{ + public int id; + public String message; + + public Fortune() { + } + + public Fortune(int id, String message) { + this.id = id; + this.message = message; + } + + @Override + public int compareTo(final Fortune other) { + return message.compareTo(other.message); + } +} diff --git a/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/model/Fortunes.java b/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/model/Fortunes.java new file mode 100644 index 00000000000..839d8512916 --- /dev/null +++ b/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/model/Fortunes.java @@ -0,0 +1,9 @@ +package com.kfyty.benchmark.example.model; + +import io.jstach.jstache.JStache; + +import java.util.List; + +@JStache(path = "fortunes.mustache") +public record Fortunes(List fortunes) { +} diff --git a/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/model/World.java b/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/model/World.java new file mode 100644 index 00000000000..fe0645eb720 --- /dev/null +++ b/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/model/World.java @@ -0,0 +1,14 @@ +package com.kfyty.benchmark.example.model; + +public final class World { + public int id; + public int randomNumber; + + public World() { + } + + public World(int id, int randomNumber) { + this.id = id; + this.randomNumber = randomNumber; + } +} \ No newline at end of file diff --git a/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/repository/DbRepository.java b/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/repository/DbRepository.java new file mode 100644 index 00000000000..5f7de0d2384 --- /dev/null +++ b/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/repository/DbRepository.java @@ -0,0 +1,15 @@ +package com.kfyty.benchmark.example.repository; + +import com.kfyty.benchmark.example.model.Fortune; +import com.kfyty.benchmark.example.model.World; + +import java.util.List; + +public interface DbRepository { + + World getWorld(int id); + + void updateWorlds(List worlds); + + List fortunes(); +} diff --git a/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/repository/JdbcDbRepository.java b/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/repository/JdbcDbRepository.java new file mode 100644 index 00000000000..794ee502fc6 --- /dev/null +++ b/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/repository/JdbcDbRepository.java @@ -0,0 +1,55 @@ +package com.kfyty.benchmark.example.repository; + +import com.kfyty.benchmark.example.model.Fortune; +import com.kfyty.benchmark.example.model.World; +import com.kfyty.loveqq.framework.core.autoconfig.annotation.Repository; +import com.kfyty.loveqq.framework.core.exception.ResolvableException; +import com.kfyty.loveqq.framework.core.generic.SimpleGeneric; +import com.kfyty.loveqq.framework.core.reflect.DefaultParameterizedType; +import com.kfyty.loveqq.framework.core.utils.JdbcUtil; + +import javax.sql.DataSource; +import java.sql.SQLException; +import java.util.LinkedList; +import java.util.List; + +@Repository +public class JdbcDbRepository implements DbRepository { + private final DataSource dataSource; + + public JdbcDbRepository(DataSource dataSource) { + this.dataSource = dataSource; + } + + @Override + public World getWorld(int id) { + try { + return JdbcUtil.query(dataSource, World.class, "SELECT id, randomnumber FROM world WHERE id = ?", id); + } catch (SQLException e) { + return null; + } + } + + @Override + public void updateWorlds(List worlds) { + try { + for (World world : worlds) { + JdbcUtil.execute(dataSource, "UPDATE world SET randomnumber = ? WHERE id = ?", world.randomNumber, world.id); + } + } catch (SQLException e) { + throw new ResolvableException(e); + } + } + + @Override + @SuppressWarnings("unchecked") + public List fortunes() { + try { + DefaultParameterizedType parameterizedType = new DefaultParameterizedType(List.class, new Class[]{Fortune.class}); + Object queried = JdbcUtil.query(dataSource, SimpleGeneric.from(parameterizedType), "SELECT id, message FROM fortune"); + return (List) queried; + } catch (SQLException e) { + return new LinkedList<>(); + } + } +} diff --git a/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/utils/Utils.java b/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/utils/Utils.java new file mode 100644 index 00000000000..2f6f2ac3b87 --- /dev/null +++ b/frameworks/Java/loveqq/src/main/java/com/kfyty/benchmark/example/utils/Utils.java @@ -0,0 +1,17 @@ +package com.kfyty.benchmark.example.utils; + +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.IntStream; + +public abstract class Utils { + private static final int MIN_WORLD_NUMBER = 1; + private static final int MAX_WORLD_NUMBER_PLUS_ONE = 10_001; + + public static int randomWorldNumber() { + return ThreadLocalRandom.current().nextInt(MIN_WORLD_NUMBER, MAX_WORLD_NUMBER_PLUS_ONE); + } + + public static IntStream randomWorldNumbers() { + return ThreadLocalRandom.current().ints(MIN_WORLD_NUMBER, MAX_WORLD_NUMBER_PLUS_ONE).distinct(); + } +} diff --git a/frameworks/Java/loveqq/src/main/resources/application.yml b/frameworks/Java/loveqq/src/main/resources/application.yml new file mode 100644 index 00000000000..1e414cdf380 --- /dev/null +++ b/frameworks/Java/loveqq/src/main/resources/application.yml @@ -0,0 +1,12 @@ +k: + server: + virtualThread: false + datasource: + type: com.zaxxer.hikari.HikariDataSource + driverClassName: org.postgresql.Driver + username: benchmarkdbuser + password: benchmarkdbpass + url: jdbc:postgresql://tfb-database:5432/hello_world?loggerLevel=OFF&disableColumnSanitiser=true&assumeMinServerVersion=16&sslmode=disable + hikari: + minIdle: 5 + maxPoolSize: 256 diff --git a/frameworks/Java/loveqq/src/main/resources/fortunes.mustache b/frameworks/Java/loveqq/src/main/resources/fortunes.mustache new file mode 100644 index 00000000000..3043546205b --- /dev/null +++ b/frameworks/Java/loveqq/src/main/resources/fortunes.mustache @@ -0,0 +1,20 @@ + + + + Fortunes + + + + + + + + {{#fortunes}} + + + + + {{/fortunes}} +
idmessage
{{id}}{{message}}
+ +