1. Обзор
В этой статье мы сосредоточимся на создании микросервиса на основе Eclipse MicroProfile.
Мы рассмотрим, как написать веб-приложение RESTful с использованием API-интерфейсов JAX-RS, CDI и JSON-P.
2. Микросервисная архитектура
Проще говоря, микросервисы — это стиль архитектуры программного обеспечения, который формирует полную систему как набор нескольких независимых сервисов.
Каждый из них фокусируется на одном функциональном периметре и взаимодействует с другими с помощью независимого от языка протокола, такого как REST.
3. Микропрофиль Eclipse
Eclipse MicroProfile — это инициатива, направленная на оптимизацию Enterprise Java для архитектуры микросервисов. Он основан на подмножестве API Jakarta EE WebProfile, поэтому мы можем создавать приложения MicroProfile так же, как мы создаем приложения Jakarta EE.
Цель MicroProfile — определить стандартные API-интерфейсы для создания микросервисов и предоставления переносимых приложений для нескольких сред выполнения MicroProfile.
4. Зависимости Maven
Все зависимости, необходимые для создания приложения Eclipse MicroProfile, предоставляются этой зависимостью BOM (Bill Of Materials):
<dependency>
<groupId>org.eclipse.microprofile</groupId>
<artifactId>microprofile</artifactId>
<version>1.2</version>
<type>pom</type>
<scope>provided</scope>
</dependency>
Область задана как указано
, потому что среда выполнения MicroProfile уже включает API и реализацию.
5. Модель представления
Начнем с создания класса быстрого ресурса:
public class Book {
private String id;
private String name;
private String author;
private Integer pages;
// ...
}
Как мы видим, в этом классе Book нет аннотации.
6. Использование CDI
Проще говоря, CDI — это API, обеспечивающий внедрение зависимостей и управление жизненным циклом. Это упрощает использование Enterprise bean-компонентов в веб-приложениях.
Давайте теперь создадим управляемый компонент CDI в качестве хранилища для представления книги:
@ApplicationScoped
public class BookManager {
private ConcurrentMap<String, Book> inMemoryStore
= new ConcurrentHashMap<>();
public String add(Book book) {
// ...
}
public Book get(String id) {
// ...
}
public List getAll() {
// ...
}
}
Мы аннотируем этот класс @ApplicationScoped
, потому что нам нужен только один экземпляр, состояние которого совместно используется всеми клиентами. Для этого мы использовали ConcurrentMap
как типобезопасное хранилище данных в памяти. Затем мы добавили методы для операций CRUD .
Теперь наш bean-компонент готов к CDI и может быть внедрен в bean-компонент BookEndpoint с помощью
аннотации @Inject
.
7. JAX-RS API
Чтобы создать REST-приложение с JAX-RS, нам нужно создать класс Application с аннотацией
@ApplicationPath
и ресурс с аннотацией @Path.
7.1. JAX RS-приложение
Приложение JAX-RS идентифицирует базовый URI, под которым мы предоставляем ресурс в веб-приложении.
Давайте создадим следующее приложение JAX-RS:
@ApplicationPath("/library")
public class LibraryApplication extends Application {
}
В этом примере все классы ресурсов JAX-RS в веб-приложении связаны с приложением LibraryApplication
, что делает их одним и тем же путем к библиотеке
, что является значением аннотации ApplicationPath.
Этот аннотированный класс сообщает среде выполнения JAX RS, что она должна автоматически находить ресурсы и предоставлять их.
7.2. Конечная точка JAX RS
Класс конечной точки
, также называемый классом ресурсов
, должен определять один ресурс, хотя технически возможно множество одинаковых типов.
Каждый класс Java с аннотацией @Path
или имеющий хотя бы один метод с аннотацией @Path или @HttpMethod
является конечной точкой.
Теперь мы создадим конечную точку JAX-RS, которая предоставляет это представление:
@Path("books")
@RequestScoped
public class BookEndpoint {
@Inject
private BookManager bookManager;
@GET
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response getBook(@PathParam("id") String id) {
return Response.ok(bookManager.get(id)).build();
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getAllBooks() {
return Response.ok(bookManager.getAll()).build();
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response add(Book book) {
String bookId = bookManager.add(book);
return Response.created(
UriBuilder.fromResource(this.getClass())
.path(bookId).build())
.build();
}
}
На этом этапе мы можем получить доступ к ресурсу BookEndpoint
по пути /library/books
в веб-приложении.
7.3. JAX RS Тип носителя JSON
JAX RS поддерживает множество типов мультимедиа для связи с клиентами REST, но Eclipse MicroProfile ограничивает использование JSON , поскольку указывает использование API JSOP-P. Таким образом, нам нужно аннотировать наши методы с помощью @Consumes(MediaType.APPLICATION_JSON)
и @Produces(MediaType.APPLICATION_JSON).
Аннотация @Consumes
ограничивает допустимые форматы — в этом примере принимается только формат данных JSON. Заголовок HTTP-запроса Content-Type
должен быть application/json
.
Та же идея лежит в основе аннотации @Produces
. Среда выполнения JAX RS должна маршалировать ответ в формате JSON. Заголовок HTTP-запроса Accept
должен быть application/json.
8. JSON-P
JAX RS Runtime поддерживает JSON-P из коробки, поэтому мы можем использовать JsonObject
в качестве входного параметра метода или возвращаемого типа.
Но в реальном мире мы часто работаем с классами POJO. Итак, нам нужен способ сделать сопоставление между JsonObject
и POJO. Вот где поставщик сущностей JAX RS начинает играть.
Для маршалинга входного потока JSON в POJO Book
, который вызывает метод ресурса с параметром типа Book,
нам нужно создать класс BookMessageBodyReader:
@Provider
@Consumes(MediaType.APPLICATION_JSON)
public class BookMessageBodyReader implements MessageBodyReader<Book> {
@Override
public boolean isReadable(
Class<?> type, Type genericType,
Annotation[] annotations,
MediaType mediaType) {
return type.equals(Book.class);
}
@Override
public Book readFrom(
Class type, Type genericType,
Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, String> httpHeaders,
InputStream entityStream) throws IOException, WebApplicationException {
return BookMapper.map(entityStream);
}
}
Мы делаем тот же процесс, чтобы демаршалировать поток вывода Book
в JSON, который вызывает метод ресурса с типом возвращаемого значения Book,
путем создания BookMessageBodyWriter:
@Provider
@Produces(MediaType.APPLICATION_JSON)
public class BookMessageBodyWriter
implements MessageBodyWriter<Book> {
@Override
public boolean isWriteable(
Class<?> type, Type genericType,
Annotation[] annotations,
MediaType mediaType) {
return type.equals(Book.class);
}
// ...
@Override
public void writeTo(
Book book, Class<?> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream) throws IOException, WebApplicationException {
JsonWriter jsonWriter = Json.createWriter(entityStream);
JsonObject jsonObject = BookMapper.map(book);
jsonWriter.writeObject(jsonObject);
jsonWriter.close();
}
}
Поскольку BookMessageBodyReader
и BookMessageBodyWriter снабжены
аннотациями @Provider
, они автоматически регистрируются средой выполнения JAX RS.
9. Сборка и запуск приложения
Приложение MicroProfile является переносимым и должно работать в любой совместимой среде выполнения MicroProfile. Мы объясним, как создать и запустить наше приложение в Open Liberty , но мы можем использовать любой совместимый Eclipse MicroProfile.
Мы настраиваем среду выполнения Open Liberty через конфигурационный файл server.xml
:
<server description="OpenLiberty MicroProfile server">
<featureManager>
<feature>jaxrs-2.0</feature>
<feature>cdi-1.2</feature>
<feature>jsonp-1.0</feature>
</featureManager>
<httpEndpoint httpPort="${default.http.port}" httpsPort="${default.https.port}"
id="defaultHttpEndpoint" host="*"/>
<applicationManager autoExpand="true"/>
<webApplication context-root="${app.context.root}" location="${app.location}"/>
</server>
Давайте добавим плагин Liberty -maven-plugin
в наш pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
<groupId>net.wasdev.wlp.maven.plugins</groupId>
<artifactId>liberty-maven-plugin</artifactId>
<version>2.1.2</version>
<configuration>
<assemblyArtifact>
<groupId>io.openliberty</groupId>
<artifactId>openliberty-runtime</artifactId>
<version>17.0.0.4</version>
<type>zip</type>
</assemblyArtifact>
<configFile>${basedir}/src/main/liberty/config/server.xml</configFile>
<packageFile>${package.file}</packageFile>
<include>${packaging.type}</include>
<looseApplication>false</looseApplication>
<installAppPackages>project</installAppPackages>
<bootstrapProperties>
<app.context.root>/</app.context.root>
<app.location>${project.artifactId}-${project.version}.war</app.location>
<default.http.port>9080</default.http.port>
<default.https.port>9443</default.https.port>
</bootstrapProperties>
</configuration>
<executions>
<execution>
<id>install-server</id>
<phase>prepare-package</phase>
<goals>
<goal>install-server</goal>
<goal>create-server</goal>
<goal>install-feature</goal>
</goals>
</execution>
<execution>
<id>package-server-with-apps</id>
<phase>package</phase>
<goals>
<goal>install-apps</goal>
<goal>package-server</goal>
</goals>
</execution>
</executions>
</plugin>
Этот плагин настраивается с набором свойств:
<properties>
<!--...-->
<app.name>library</app.name>
<package.file>${project.build.directory}/${app.name}-service.jar</package.file>
<packaging.type>runnable</packaging.type>
</properties>
Вышеупомянутая цель exec создает исполняемый файл jar, так что наше приложение будет независимым микросервисом, который можно развернуть и запустить изолированно. Мы также можем развернуть его как образ Docker.
Чтобы создать исполняемый jar-файл, выполните следующую команду:
mvn package
И для запуска нашего микросервиса мы используем эту команду:
java -jar target/library-service.jar
Это запустит среду выполнения Open Liberty и развернет наш сервис. Мы можем получить доступ к нашей конечной точке и получить все книги по этому URL-адресу:
curl http://localhost:9080/library/books
Результат JSON:
[
{
"id": "0001-201802",
"isbn": "1",
"name": "Building Microservice With Eclipse MicroProfile",
"author": "foreach",
"pages": 420
}
]
Чтобы получить одну книгу, мы запрашиваем этот URL:
curl http://localhost:9080/library/books/0001-201802
И результат JSON:
{
"id": "0001-201802",
"isbn": "1",
"name": "Building Microservice With Eclipse MicroProfile",
"author": "foreach",
"pages": 420
}
Теперь мы добавим новую книгу, взаимодействуя с API:
curl
-H "Content-Type: application/json"
-X POST
-d '{"isbn": "22", "name": "Gradle in Action","author": "foreach","pages": 420}'
http://localhost:9080/library/books
Как мы видим, статус ответа — 201, что указывает на то, что книга была успешно создана, а Location
— это URI, по которому мы можем получить к ней доступ:
< HTTP/1.1 201 Created
< Location: http://localhost:9080/library/books/0009-201802
10. Заключение
В этой статье показано, как создать простой микросервис на основе Eclipse MicroProfile, а также обсуждались JAX RS, JSON-P и CDI.
Код доступен на Github ; это проект на основе Maven, поэтому его должно быть просто импортировать и запускать как есть.