Перейти к основному содержимому

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

· 9 мин. чтения

1. Обзор

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

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

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

В настоящее время фреймворк поддерживает две модели программирования для написания микросервисов: Helidon SE и Helidon MP.

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

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

3. Хелидон ЮВ

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

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

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

<dependency>
<groupId>io.helidon.webserver</groupId>
<artifactId>helidon-webserver</artifactId>
<version>0.10.4</version>
</dependency>

Чтобы создать простое веб-приложение, мы можем использовать один из следующих методов построения: WebServer.create(serverConfig, routing) или просто 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

Веб- сервер на самом деле является SPI, и нам нужно обеспечить реализацию во время выполнения. В настоящее время Helidon предоставляет реализацию NettyWebServer , основанную на Netty Core.

Вот зависимость Maven для этой реализации:

<dependency>
<groupId>io.helidon.webserver</groupId>
<artifactId>helidon-webserver-netty</artifactId>
<version>0.10.4</version>
<scope>runtime</scope>
</dependency>

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

http://localhost:9001/greet

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

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

3.2. API конфигурации _

Config API предоставляет инструменты для чтения данных конфигурации из источника конфигурации .

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

<dependency>
<groupId>io.helidon.config</groupId>
<artifactId>helidon-config</artifactId>
<version>0.10.4</version>
</dependency>

Чтобы прочитать данные конфигурации, нам просто нужно использовать построитель по умолчанию, который по умолчанию берет данные конфигурации из 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

Чтобы прочитать значения, мы можем использовать метод Config.get () с последующим удобным приведением к соответствующим типам 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 и application.properties. Для последних трех форматов требуется дополнительная связанная зависимость конфигурации. Например, чтобы использовать формат YAML, нам нужно добавить соответствующую зависимость конфигурации YAML :

<dependency>
<groupId>io.helidon.config</groupId>
<artifactId>helidon-config-yaml</artifactId>
<version>0.10.4</version>
</dependency>

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

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

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

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

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

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. API маршрутизации _

API маршрутизации предоставляет механизм, с помощью которого мы связываем HTTP-запросы с методами Java. Мы можем добиться этого, используя метод и путь запроса в качестве критериев соответствия или объект 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<Book> books = bookManager.getAll();
JsonArray jsonArray = from(books);
serverResponse.send(jsonArray);
}
//...
}

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

<dependency>
<groupId>io.helidon.webserver</groupId>
<artifactId>helidon-webserver-json</artifactId>
<version>0.10.4</version>
</dependency>

Наконец, мы используем метод register () построителя маршрутизации для привязки корневого пути к ресурсу. В этом случае пути , настроенные службой, имеют префикс корневого пути:

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

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

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

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

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

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

<dependency>
<groupId>io.helidon.security</groupId>
<artifactId>helidon-security</artifactId>
<version>0.10.4</version>
</dependency>
<dependency>
<groupId>io.helidon.security</groupId>
<artifactId>helidon-security-provider-http-auth</artifactId>
<version>0.10.4</version>
</dependency>
<dependency>
<groupId>io.helidon.security</groupId>
<artifactId>helidon-security-integration-webserver</artifactId>
<version>0.10.4</version>
</dependency>

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

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

Первое, что нужно сделать, это создать экземпляр Security . Мы можем сделать это либо программно для простоты :

Map<String, MyUser> 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 , который мы загружаем через Config API:

#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 , нам сначала нужно зарегистрировать его на веб -сервере с помощью метода 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 является реализацией Eclipse MicroProfile , а также предоставляет среду выполнения для запуска микросервисов на основе MicroProfile.

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

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

<dependency>
<groupId>io.helidon.microprofile.bundles</groupId>
<artifactId>helidon-microprofile-1.2</artifactId>
<version>0.10.4</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-binding</artifactId>
<version>2.26</version>
</dependency>

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

Затем мы добавим файл beans.xml в каталог src/main/resource/META-INF со следующим содержимым:

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
version="2.0" bean-discovery-mode="annotated">
</beans>

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

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

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

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, используя ее.

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