本快速教程将向您展示如何使用最新版本的Eclipse MicroProfile API构建您的下一个微服务。这是一篇基于以前John D Ament 的文章的修订版,更新了MicroProfile 1.3的一些新功能。
Eclipse MicroProfile旨在为由多个微服务组成的Java应用程序提供一个增长型的API集。该项目最近引起了很多关注,也包括Oracle和IBM在内的越来越多的企业支持者。现在有很多提供API的服务器和框架,这意味着您可以继续使用相同的API和性能来选择最好的工具来运行您的微服务。本文是一个使用MicroProfile API构建下一个微服务的快速教程。
MicroProfile由JavaEE的核心技术构建,现在称为Jakarta EE技术:
向他们添加一组可以让你的微服务准备好云计算的规范,其中包括:
这些规范组在一起成了Eclipse MicroProfile 1.3。
那么你如何利用这些项目呢?这个快速指南可以教你编写你的第一个应用程序。MicroProfile仅指定了API和行为,但不包含指定的功能。这是由Payara Micro提供的功能实现的。使用Payara Micro,您可以从命令行运行WAR文件,但也可以组装单个可执行JAR文件。还有很多其他的实现,你可以在MicroProfile实现列表中找到它们。
如果您选择使用Payara Micro运行微服务,请首先创建一个可生成WAR文件的Web项目。如果你的项目使用Maven或Gradle,你可以设置一个标准的Web应用程序项目(使用war打包或war插件)。构建WAR文件后,您可以从https://www.payara.fish/downloads下载Payara Micro,并通过以下命令从命令行运行应用程序:
java -jar payara-micro.jar application.war
然后,将MicroProfile附属项添加到您的项目中。
Maven的:
<dependency>
<groupId>org.eclipse.microprofile</groupId>
<artifactId>microprofile</artifactId>
<version>1.3</version>
<type>pom</type>
<scope>provided</scope>
</dependency>
Gradle:
dependencies { providedCompile 'org.eclipse.microprofile:microprofile:1.3'}
}
这个附属项引入了所有必需的API来构建您的应用程序。那么典型的微服务是什么样的?
首先,我们有我们的rest控制器,这对Java EE开发人员来说应该非常熟悉:
@Path("/api/books") // just a basic JAX-RS resource
@Counted // track the number of times this endpoint is invoked
@RequestScoped
public class BooksController {
@Inject //use CDI to inject a service
private BookService bookService;
@GET
@RolesAllowed("read-books")
// uses common annotations to declare a role required
public Books findAll() {
return bookService.getAll();
}
}
对于小型服务器,控制器也可以包含服务逻辑。但是,在我们的示例中,它通常会将业务逻辑的处理委托给另一个服务bean,如bookService。
如果我们进一步深入图书服务,我们可以开始看到可配置性如何工作:
@ApplicationScoped
public class BookService {
@Inject
// JPA is not provided out of the box, but most providers support it at
// some level. Worst case, create your own producer for the field
private EntityManager entityManager;
@Inject
// use configuration to control how much data you want to supply at
// a given time
@ConfigProperty(name = "max.books.per.page", defaultValue = "20")
private int maxBooks;
public Books getAll() {
List < Book > bookList = entityManager
.createQuery("select b from Book b", Book.class)
.setMaxResults(maxBooks) // use that configuration to do a paginated look up
.getResultList();
return new Books(bookList);
}
}
可以使用注入点上的@ConfigProperty注释将配置值简单地注入到服务中。该配置是基于配置名称提供的,该配置名称被用作从容器中检索配置值的关键字。其他可选属性也可以被提供,例如defaultValue,如果给定名称没有配置,则使用该属性。即使是名字属性也是可选的。如果未提供,则将根据类和字段名称生成,以便稍后可以提供配置值。
所以配置也可以像这样注入:
@Inject
@ConfigProperty
private int maxBooks
如果未提供默认值,则在应用程序启动时,必须使用根据指定算法生成的名称配置。
配置与bookService分离,可以由应用程序内部的配置提供,甚至可以在应用程序启动时由外部源(例如系统属性)提供。
接下来,我们假设我们也想要处理书籍的创建,出版过程。我们希望确保服务的安全,以便只允许具有特定角色的呼叫者执行此过程。
根据JWT标准,MicroProfile提供基于JSON令牌的解决方案。我们可以将JsonWebToken对象注入到我们的服务中,并通过调用getClaim方法轻松找出调用者是否具有所需的角色:
@Inject
private JsonWebToken jsonWebToken;
然后在一个方法中:
boolean createAny = jsonWebToken.getClaim("create.books.for.other.authors");
if (!createAny) {
throw new NotAuthorizedException("Cannot create book, wrong author");
}
然后调用者需要添加一个有效的JWT令牌和所需的声明到REST调用的头部。
用一个完整的发布服务来支持可能看起来像这样:
@RequestScoped
public class PublishBookService {
@Inject
// we can inject a JsonWebToken, a Principal specific to the JWT specification
private JsonWebToken jsonWebToken;
// we could also inject individual ClaimValue objects.
@Inject
private AuthorService authorService;
@Inject
private EntityManager entityManager;
@Timeout(500)
// we want to limit how long it takes to publish and if it
// exceeds, return an exception to the caller.
public BookId publish(PublishBook publishBook) {
// we check the standard claim of subject
if (!publishBook.getAuthor().equals(jsonWebToken.getSubject())) {
// as well as a custom claim as a boolean
boolean createAny = jsonWebToken.getClaim("create.books.for.other.authors");
if (!createAny) {
throw new NotAuthorizedException("Cannot create book, wrong author");
}
}
Author author = authorService.findAuthor(publishBook.getAuthor());
if (author == null) {
throw new NotAuthorizedException("The list author is not an author");
}
Book book = entityManager.merge(new Book(publishBook.getIsbn(),
publishBook.getAuthor()));
return new BookId(book.getIsbn(), book.getAuthor());
}
}
为了上述所有工作,还需要使用LoginConfig批注在JAX-RS应用程序类上启用JWT安全性。将该类转换为CDI bean也很重要,例如通过添加ApplicationScoped注释,因为JAX-RS类不会自动启用CDI。
这是它在代码中的样子:
@LoginConfig(authMethod = "MP-JWT", realmName = "admin-realm")
@ApplicationScoped
@ApplicationPath("/")
public class BookServiceConfig extends javax.ws.rs.Application {
}
如果我们认为管理作者是一个单独的有限语境,我们则希望这个服务是谨慎的。因此,我们将以与书籍服务相同的方式将其作为单独的REST服务实施。因此,我们希望书籍服务通过连接到新的作者REST服务来检查作者是否存在。以下是连接到外部作者服务的完整代码:
@ApplicationScoped
public class AuthorService {
@Inject
@RestClient
AuthorConnector authorConnector;
// inject a REST proxy for a URL given by a generated config property
private ConcurrentMap < String, Author > authorCache = new ConcurrentHashMap < > ();
@Retry
// Retry indicates that this should trigger retry the method call several times in case the remote server call results in an exception
@CircuitBreaker
// CircuiBreaker wraps the call in a circuit breaker which opens after several failures and closes again after some time
@Fallback(fallbackMethod = "getCachedAuthor")
// Fallback indicates that we should fall back to the local cache
// if the method fails even after several retries
// or the circuit is open
public Author findAuthor(String id) {
// call to an external Author service
Author author = authorConnector.get(id);
// Ideally we want to read from the remote server.
// However, we can build
// a cache as a fallback when the server is down
authorCache.put(id, author);
return author;
}
public Author getCachedAuthor(String id) {
return authorCache.get(id);
}
}
重注释,断路器,自动中断等触发拦截器,在基本动作失败的情况下会实施相应的容错模式。它们用于单独的方法或类中,以将其应用于所有方法。Fallback注释指定如果拦截器无法从故障中恢复,应调用哪个方法。此方法可以提供替代结果或通知有关错误。
容错性注解也完全支持可配置性。注释的属性可以通过我们之前使用的相同配置机制来覆盖。当为方法启用任何拦截器时,它将从类和字段名称生成的配置名称中读取配置。例如,要指定方法findAuthor的重试次数,我们可以使用名称ws.ament.microprofile.gettingstarted.AuthorService / findAuthor / Retry / maxRetries来指定配置属性。
这也意味着您可以在代码中使用没有任何属性的注释,并在稍后为每个环境配置不同的值。
在代码中,我们还看到由MicroProfile容器提供的REST客户端代理。该URL由生成的配置名称的外部配置指定,类似于容错注释。剩下的只是在代理上调用一个方法来完成远程调用的所有工作并返回一个Author实例。
所以你得有它!几个休息控制器,服务器,并且您有一个使用Eclipse MicroProfile构建的微服务来管理书籍。
最后一件事是找出你的应用程序中发生了什么。MicroProfile容器中的度量和健康检查功能提供了很多开箱即用的信息,它可以通过REST端点获得。
在应用程序的生命周期中收集的各种度量标准可以通过基于/ metrics基本路径的HTTP上的REST,以JSON或Prometheus格式自动公开。它提供了有关JVM,线程,加载的类和操作系统的常用指标。其他自定义指标可以由实现提供。应用程序还可以使用方法拦截器或生产者方法非常轻松地收集度量标准。
例如,如果服务在本地主机和端口8080上运行,则可以简单地使用HTTP头Accept = application / json 访问http:// localhost:8080 / metrics,您将得到如下所示的内容:
{
"base": {
"classloader.totalLoadedClass.count": 16987,
"cpu.systemLoadAverage": 1.22,
"thread.count": 141,
"classloader.currentLoadedClass.count": 16986,
"jvm.uptime": 52955,
"memory.committedNonHeap": 131727360,
"gc.PS MarkSweep.count": 3,
"memory.committedHeap": 503316480,
"thread.max.count": 143,
"gc.PS Scavenge.count": 20,
"cpu.availableProcessors": 8,
"thread.daemon.count": 123,
"classloader.totalUnloadedClass.count": 2,
"memory.usedNonHeap": 117340624,
"memory.maxHeap": 503316480,
"memory.usedHeap": 139449848,
"gc.PS MarkSweep.time": 428,
"memory.maxNonHeap": -1,
"gc.PS Scavenge.time": 220
}
}
您还可以访问http:// localhost:8080 / health,以确定服务运行良好还是存在一些错误。这是一个简单的是/否检查,如果一切正常,则提供HTTP 200状态码。这适用于可以自动检测并重新启动服务的系统,例如Kubernetes。
MicroProfile 1.3还有一些组件,例如Open API和Open Tracing。我们不会在这里介绍它们,您可以在microprofile.io上找到它们来了解API和文档。您可以在Payara MicroProfile文档中找到更多关于Microprofile API的文档,包括由Payara Micro添加的其他增强功能。
您也可以在GitHub上下载本文中使用的完整示例代码。