Микросервисы с Oracle Helidon

Микросервисы с Oracle Helidon

1. обзор

Helidon - это новая среда микросервисов Java, исходный код которой был недавно открыт Oracle. Он использовался внутри в проектах Oracle под названием J4C (Java for Cloud).

В этом руководстве мы рассмотрим основные концепции фреймворка, а затем перейдем к созданию и запуску микросервиса на основе Helidon.

2. Модель программирования

В настоящее времяthe framework supports two programming models for writing microservices: Helidon SE and Helidon MP.

В то время как Helidon SE разработан как микросхема, поддерживающая модель реактивного программирования, Helidon MP, с другой стороны, является средой исполнения Eclipse MicroProfile, которая позволяет сообществу Java EE запускать микросервисы в переносимом режиме.

В обоих случаях микросервис Helidon - это приложение Java SE, которое запускает простой HTTP-сервер из основного метода.

3. Helidon SE

В этом разделе мы более подробно познакомимся с основными компонентами Helidon SE: WebServer, Config и Security.

3.1. Настройка веб-сервера

Чтобы начать работу сWebServer API,, нам нужно добавить необходимыйMaven dependency в файлpom.xml:


    io.helidon.webserver
    helidon-webserver
    0.10.4

Чтобы иметь простое веб-приложение,we can use one of the following builder methods: WebServer.create(serverConfig, routing) or just WebServer.create(routing). Последний принимает конфигурацию сервера по умолчанию, позволяющую серверу работать на случайном порту.

Вот простое веб-приложение, работающее на заранее определенном порту. Мы также зарегистрировали простой обработчик, который будет отвечать приветственным сообщением на любой HTTP-запрос с путем "/greet' и методомGET":

public static void main(String... args) throws Exception {
    ServerConfiguration serverConfig = ServerConfiguration.builder()
      .port(9001).build();
    Routing routing = Routing.builder()
      .get("/greet", (request, response) -> response.send("Hello World !")).build();
    WebServer.create(serverConfig, routing)
      .start()
      .thenAccept(ws ->
          System.out.println("Server started at: http://localhost:" + ws.port())
      );
}

Последняя строка - запуск сервера и ожидание обслуживания HTTP-запросов. Но если мы запустим этот пример кода в основном методе, мы получим ошибку:

Exception in thread "main" java.lang.IllegalStateException:
  No implementation found for SPI: io.helidon.webserver.spi.WebServerFactory

WebServer на самом деле является SPI, и нам нужно предоставить реализацию во время выполнения. В настоящее времяHelidon provides the NettyWebServer implementation основан на ядреNetty.

ВотMaven dependency для этой реализации:


    io.helidon.webserver
    helidon-webserver-netty
    0.10.4
    runtime

Теперь мы можем запустить основное приложение и проверить его работу, вызвав настроенную конечную точку:

http://localhost:9001/greet

В этом примере мы настроили и порт, и путь, используя шаблон компоновщика.

Helidon SE также позволяет использовать шаблон конфигурации, в котором данные конфигурации предоставляются APIConfig. Это тема следующего раздела.


3.2. APIConfig

The Config API provides tools for reading configuration data from a configuration source.

Helidon SE предоставляет реализации для многих источников конфигурации. Реализация по умолчанию предоставляетсяhelidon-config, где источником конфигурации является файлapplication.properties, расположенный в пути к классам:


    io.helidon.config
    helidon-config
    0.10.4

Чтобы прочитать данные конфигурации, нам просто нужно использовать построитель по умолчанию, который по умолчанию берет данные конфигурации изapplication.properties:

Config config = Config.builder().build();

Давайте создадим файлapplication.properties в каталогеsrc/main/resource со следующим содержанием:

server.port=9080
web.debug=true
web.page-size=15
user.home=C:/Users/app

To read the values we can use the Config.get() method с последующим удобным приведением к соответствующим типам Java:

int port = config.get("server.port").asInt();
int pageSize = config.get("web.page-size").asInt();
boolean debug = config.get("web.debug").asBoolean();
String userHome = config.get("user.home").asString();

Фактически, построитель по умолчанию загружает первый найденный файл в следующем порядке приоритета:application.yaml, application.conf, application.json, and application.properties. Последние три формата требуют дополнительной связанной конфигурации. Например, чтобы использовать формат YAML, нам нужно добавить связанную зависимость YAMLconfig:


    io.helidon.config
    helidon-config-yaml
    0.10.4

Затем мы добавляемapplication.yml:

server:
  port: 9080
web:
  debug: true
  page-size: 15
user:
  home: C:/Users/app

Точно так же, чтобы использовать CONF, который является упрощенным форматом JSON, или форматами JSON, нам нужно добавить зависимостьhelidon-config-hocon.

Обратите внимание, что данные конфигурации в этих файлах могут быть переопределены переменными среды и свойствами системы Java.

We can also control the default builder behavior, отключив переменную среды и свойства системы или явно указав источник конфигурации:

ConfigSource configSource = ConfigSources.classpath("application.yaml").build();
Config config = Config.builder()
  .disableSystemPropertiesSource()
  .disableEnvironmentVariablesSource()
  .sources(configSource)
  .build();

В дополнение к чтению данных конфигурации из пути к классам, мы также можем использовать две конфигурации внешних источников, то есть, git и etcd. Для этого нам понадобятся зависимостиhelidon-config-git иhelidon-git-etcd.

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

3.3. APIRouting

The Routing API provides the mechanism by which we bind HTTP requests to Java methods. Мы можем добиться этого, используя метод запроса и путь в качестве критериев соответствия или объектRequestPredicate для использования дополнительных критериев.

Итак, для настройки маршрута мы можем просто использовать метод HTTP в качестве критерия:

Routing routing = Routing.builder()
  .get((request, response) -> {} );

Или мы можем объединить метод HTTP с путем запроса:

Routing routing = Routing.builder()
  .get("/path", (request, response) -> {} );

Мы также можем использоватьRequestPredicate для большего контроля. Например, мы можем проверить существующий заголовок или тип содержимого:

Routing routing = Routing.builder()
  .post("/save",
    RequestPredicate.whenRequest()
      .containsHeader("header1")
      .containsCookie("cookie1")
      .accepts(MediaType.APPLICATION_JSON)
      .containsQueryParameter("param1")
      .hasContentType("application/json")
      .thenApply((request, response) -> { })
      .otherwise((request, response) -> { }))
      .build();

До сих пор мы предоставили обработчики в функциональном стиле. Мы также можем использовать классService, который позволяет писать обработчики более изощренным образом.

Итак, давайте сначала создадим модель для объекта, с которым мы работаем, классаBook:

public class Book {
    private String id;
    private String name;
    private String author;
    private Integer pages;
    // ...
}

Мы можем создать службы REST для классаBook, реализовав методService.update(). Это позволяет настроить подпути одного и того же ресурса:

public class BookResource implements Service {

    private BookManager bookManager = new BookManager();

    @Override
    public void update(Routing.Rules rules) {
        rules
          .get("/", this::books)
          .get("/{id}", this::bookById);
    }

    private void bookById(ServerRequest serverRequest, ServerResponse serverResponse) {
        String id = serverRequest.path().param("id");
        Book book = bookManager.get(id);
        JsonObject jsonObject = from(book);
        serverResponse.send(jsonObject);
    }

    private void books(ServerRequest serverRequest, ServerResponse serverResponse) {
        List books = bookManager.getAll();
        JsonArray jsonArray = from(books);
        serverResponse.send(jsonArray);
    }
    //...
}

Мы также настроили тип мультимедиа как JSON, поэтому для этой цели нам понадобится зависимостьhelidon-webserver-json:


    io.helidon.webserver
    helidon-webserver-json
    0.10.4

Наконец,we use the register() method of the Routing builder to bind the root path to the resource.. В этом случае,Paths, настроенные службой, имеют префикс корневого пути:

Routing routing = Routing.builder()
  .register(JsonSupport.get())
  .register("/books", new BookResource())
  .build();

Теперь мы можем запустить сервер и проверить конечные точки:

http://localhost:9080/books
http://localhost:9080/books/0001-201810

3.4. Безопасность

В этом разделеwe’re going to secure our resources using the Security module.

Начнем с объявления всех необходимых зависимостей:


    io.helidon.security
    helidon-security
    0.10.4


    io.helidon.security
    helidon-security-provider-http-auth
    0.10.4


    io.helidon.security
    helidon-security-integration-webserver
    0.10.4

Зависимостиhelidon-security,helidon-security-provider-http-auth иhelidon-security-integration-webserver доступны в Maven Central.

Модуль безопасности предлагает множество провайдеров для аутентификации и авторизации. For this example, we’ll use the HTTP basic authentication provider, поскольку это довольно просто, но процесс для других провайдеров почти такой же.

The first thing to do is create a Security instance. We can do it either programmatically for simplicity:

Map users = //...
UserStore store = user -> Optional.ofNullable(users.get(user));

HttpBasicAuthProvider httpBasicAuthProvider = HttpBasicAuthProvider.builder()
  .realm("myRealm")
  .subjectType(SubjectType.USER)
  .userStore(store)
  .build();

Security security = Security.builder()
  .addAuthenticationProvider(httpBasicAuthProvider)
  .build();

Или мы можем использовать подход конфигурации.

В этом случае мы объявим всю конфигурацию безопасности в файлеapplication.yml, который мы загружаем через APIConfig:

#Config 4 Security ==> Mapped to Security Object
security:
  providers:
  - http-basic-auth:
      realm: "helidon"
      principal-type: USER # Can be USER or SERVICE, default is USER
      users:
      - login: "user"
        password: "user"
        roles: ["ROLE_USER"]
      - login: "admin"
        password: "admin"
        roles: ["ROLE_USER", "ROLE_ADMIN"]

  #Config 4 Security Web Server Integration ==> Mapped to WebSecurity Object
  web-server:
    securityDefaults:
      authenticate: true
    paths:
    - path: "/user"
      methods: ["get"]
      roles-allowed: ["ROLE_USER", "ROLE_ADMIN"]
    - path: "/admin"
      methods: ["get"]
      roles-allowed: ["ROLE_ADMIN"]

И чтобы загрузить его, нам нужно просто создать объектConfig, а затем вызвать методSecurity.fromConfig():

Config config = Config.create();
Security security = Security.fromConfig(config);

Когда у нас есть экземплярSecurity, нам сначала нужно зарегистрировать его вWebServer, используя методWebSecurity.from():

Routing routing = Routing.builder()
  .register(WebSecurity.from(security).securityDefaults(WebSecurity.authenticate()))
  .build();

Мы также можем создать экземплярWebSecurity напрямую, используя подход конфигурации, с помощью которого мы загружаем как конфигурацию безопасности, так и конфигурацию веб-сервера:

Routing routing = Routing.builder()
  .register(WebSecurity.from(config))
  .build();

Теперь мы можем добавить несколько обработчиков для путей/user и/admin, запустить сервер и попытаться получить к ним доступ:

Routing routing = Routing.builder()
  .register(WebSecurity.from(config))
  .get("/user", (request, response) -> response.send("Hello, I'm Helidon SE"))
  .get("/admin", (request, response) -> response.send("Hello, I'm Helidon SE"))
  .build();

4. Helidon MP

Helidon MP is an implementation of Eclipse MicroProfile, а также предоставляет среду выполнения для запуска микросервисов на основе MicroProfile.

Поскольку у нас уже естьan article about Eclipse MicroProfile, мы проверим этот исходный код и изменим его для работы на Helidon MP.

После проверки кода мы удалим все зависимости и плагины и добавим зависимости Helidon MP в файл POM:


    io.helidon.microprofile.bundles
    helidon-microprofile-1.2
    0.10.4


    org.glassfish.jersey.media
    jersey-media-json-binding
    2.26

Зависимостиhelidon-microprofile-1.2 иjersey-media-json-binding доступны в Maven Central.

Затемwe’ll add the beans.xml file under the src/main/resource/META-INF directory с таким содержимым:


В классеLibraryApplication переопределите методgetClasses(), чтобы сервер не сканировал ресурсы:

@Override
public Set> getClasses() {
    return CollectionsHelper.setOf(BookEndpoint.class);
}

Наконец, создайте метод main и добавьте этот фрагмент кода:

public static void main(String... args) {
    Server server = Server.builder()
      .addApplication(LibraryApplication.class)
      .port(9080)
      .build();
    server.start();
}

Вот и все. Теперь мы можем вызывать все ресурсы книги.

5. Заключение

В этой статье мы изучили основные компоненты Helidon, а также показали, как настроить Helidon SE и MP. Поскольку Helidon MP - это просто среда исполнения Eclipse MicroProfile, мы можем использовать любой существующий микросервис на основе MicroProfile, используя его.

Как всегда, код всех приведенных выше примеров можно найтиover on GitHub.