Helidon: простой и быстрый Java фреймворк

Illustration of an animated sponge character with a cheerful expression popping out of a laptop screen, wearing a tie, next to a coffee mug, suggesting a work or office setting. Illustration of an animated sponge character with a cheerful expression popping out of a laptop screen, wearing a tie, next to a coffee mug, suggesting a work or office setting.

Современная разработка веб приложений зачастую основывается на использовании фреймворка Spring. Spring имеет большое количество модулей и готовых удобных решений, но время идет, и мир JVM меняется. Наиболее известными решениями для web-приложений являются следующие фреймворки:

Эти фреймворки являются свежим взглядом на разработку серверных приложений на Java. Они поддерживают разработку приложений с использованием подхода Cloud Native.

В этой статье я хочу рассмотреть фреймворк Helidon. Мне он понравился своей простотой и скоростью инициализации приложения. Сам фреймворк имеет 2 модификации:

Helidon SE

Helidon SE — это легковесное решение, где по сути вам предоставляется только HTTP сервер, который вы можете сконфигурировать самостоятельно. Он не включает в себя модули IOC, методы по работе с базами данных и прочее.

А так как внутри себя Helidon использует высокоэффективный сетевой фреймворк Netty, это позволяет ему достаточно быстро инициализироваться и предоставить функционал для минимального приложения с быстрым стартом.

Давайте попробуем написать самое простое приложение, которое будет отдавать текущее время при помощи Helidon SE. Система сборки у нас будет Gradle с конфигурацией Kotlin DSL. Также нам понадобится IDE — Intellij IDEA и JDK 16.

Давайте инициализируем проект. Для этого нам необходимо создать директорию с проектом, затем перейти в терминале в нее.

Далее выполнить команду для инициализации проекта при помощи Gradle

gradle init --type java-application --test-framework junit-jupiter --dsl kotlin

Ответив на уточняющие вопросы о названии проекта и корневом пакете, выполните команду ./gradlew build. В терминале должно вывестись сообщение об успешной сборке.

Теперь давайте откроем проект в IDE, и начнем подключать Helidon. Пока мы имеем пустой проект со следующей структурой:

.
├── app
│ ├── build.gradle.kts
│ └── src
│ ├── main
│ │ ├── java
│ │ │ └── dev
│ │ │ └── rmuhamedgaliev
│ │ │ └── App.java
│ │ └── resources
│ └── test
│ ├── java
│ │ └── dev
│ │ └── rmuhamedgaliev
│ │ └── AppTest.java
│ └── resources
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts

Gradle нам создал базовую структуру приложения с главным классом App.java

/*
 * This Java source file was generated by the Gradle 'init' task.
 */
package dev.rmuhamedgaliev;
public class App {
    public String getGreeting() {
        return "Hello World!";
    }
    public static void main(String[] args) {
        System.out.println(new App().getGreeting());
    }
}

Теперь давайте в зависимости добавим Helidon.

Для этого нам необходимо, согласно инструкции, добавить зависимости в app/build.gradle.kts и привести к следующему виду:

plugins {
    application
    id("org.kordamp.gradle.jandex") version "0.11.0"
}
repositories {
    mavenCentral()
}
val helidonVersion = "2.3.2"
dependencies {
    implementation(platform("io.helidon:helidon-dependencies:${helidonVersion}"))
    implementation("io.helidon.microprofile.bundles:helidon-microprofile")
    implementation("io.helidon.media:helidon-media-jackson")
    runtimeOnly("org.jboss:jandex")
    runtimeOnly("com.sun.activation:javax.activation:1.2.0")
    testCompileOnly("org.junit.jupiter:junit-jupiter-api:5.7.2")
    testImplementation("org.junit.jupiter:junit-jupiter:5.7.2")
}
application {
    // Define the main class for the application.
    mainClass.set("dev.rmuhamedgaliev.App")
}
tasks.test {
    // Use JUnit Platform for unit tests.
    useJUnitPlatform()
}

Далее нам необходимо создать наш сервер и сконфигурировать его. Для этого давайте заменим содержимое файла App.java на следующее:

/*
 * This Java source file was generated by the Gradle 'init' task.
 */
package dev.rmuhamedgaliev;
import io.helidon.config.Config;
import io.helidon.health.HealthSupport;
import io.helidon.health.checks.HealthChecks;
import io.helidon.media.jackson.JacksonSupport;
import io.helidon.metrics.MetricsSupport;
import io.helidon.webserver.Routing;
import io.helidon.webserver.WebServer;
public class App {
    public static void main(String[] args) {
// Инициализируем конфигурацию из файла resources/application.yaml
        Config config = Config.create();
// Создаем экземпляр сервера и передаем конфигурацию
        WebServer server = WebServer.builder(createRouting())
                .config(config.get("server"))
                .addMediaSupport(JacksonSupport.create())
                .build();
// Запускаем сервер и выводим информацию о адресе, где запущен сервер
        server.start().thenAccept(ws -> {
                    System.out.println("WEB server is up! http://localhost:" + ws.port() + "/time");
                    ws.whenShutdown().thenRun(() -> System.out.println("WEB server is DOWN. Good bye!"));
                })
                .exceptionallyAccept(t -> {
                    System.err.println("Startup failed: " + t.getMessage());
                    t.printStackTrace(System.err);
                });
    }
// Делаем роутинг для обращения
    private static Routing createRouting() {
// Добавляем сервис который дает информацию о системе - память и прочее
        HealthSupport health = HealthSupport.builder()
                .addLiveness(HealthChecks.healthChecks())
                .build();
// Включаем мониторинг метрик нашего сервера
        MetricsSupport metrics = MetricsSupport.create();
// Берем инстанс нашего сервиса предоставляющего время
        TimeService timeService = new TimeService();
        return Routing.builder()
                .register(health)
                .register(metrics)
                .register("/time", timeService)
                .build();
    }
}

Затем нам необходимо в директории app/src/main/resources/application.yaml указать порт и адрес сервера:

server:
  port: 8080
  host: 0.0.0.0

Теперь давайте создадим наш TimeService который будет давать информацию о времени:

package dev.rmuhamedgaliev;
import io.helidon.webserver.Routing;
import io.helidon.webserver.ServerRequest;
import io.helidon.webserver.ServerResponse;
import io.helidon.webserver.Service;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
public class TimeService implements Service {
    /**
     * Инициализируем методы сервиса, и указываем пути для обращения
     * @param rules
     */
    @Override
    public void update(Routing.Rules rules) {
        rules
                .get("/", this::getUTCTime)
                .get("/{timezone}", this::getTimeInTimezone);
    }
    private void getUTCTime(ServerRequest request, ServerResponse response) {
        response.send(getTimeInZone("UTC"));
    }
    private void getTimeInTimezone(ServerRequest request, ServerResponse response) {
        String timezone = request.path().param("timezone").replace("-", "/");
        response.send(getTimeInZone(timezone));
    }
    private String getTimeInZone(String timeZone) {
        return LocalDateTime.now(ZoneId.of(timeZone)).format(DateTimeFormatter.ISO_DATE_TIME);
    }
}

И все, теперь у нас должно получиться рабочее приложение, давайте запустим его:

./gradlew :app:run

В терминале вы должны увидеть примерно такой ответ:

╰─$ ./gradlew :app:run
> Task :app:run
Execution optimizations have been disabled for task ':app:run' to ensure correctness due to the following reasons:
  - Gradle detected a problem with the following location: '/Users/rinatmuhamedgaliev/Projects/MKDev/article/helidon-se-demo/app/build/resources/main'. Reason: Task ':app:run' uses this output of task ':app:jandex' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed. Please refer to https://docs.gradle.org/7.2/userguide/validation_problems.html#implicit_dependency for more details about this problem.
Sept 05, 2021 3:14:29 PM io.helidon.common.HelidonFeatures features
INFO: Helidon SE 2.3.2 features: [Config, Health, Metrics, WebServer]
Sept 05, 2021 3:14:29 PM io.helidon.webserver.NettyWebServer lambda$start$7
INFO: Channel '@default' started: [id: 0x21bc78b3, L:/[0:0:0:0:0:0:0:0]:8080]
WEB server is up! http://localhost:8080/time
<==========---> 80% EXECUTING [35s]
> :app:run

Теперь в браузере можете открыть следующие адреса:

Таким образом, мы получили базовый HTTP сервер с удобным созданием обработчиков и быстрым запуском.

Сам по себе Helidon умеет много, это вводная статья и тут показан самый простой пример. Если вам интересна тема, записывайтесь на консультацию, помогу вам погрузится в мир Java разработки!

Автор статьи предоставляет консультации и занимается индивидуальным обучением программированию на Java Нанять