千家信息网

Micronaut 教程:如何使用基于 JVM 的框架构建微服务?

发表于:2024-11-22 作者:千家信息网编辑
千家信息网最后更新 2024年11月22日,本文要点:Micronaut 是一种基于 jvm 的现代化全栈框架,用于构建模块化且易于测试的微服务应用程序。Micronaut 提供完全的编译时、反射无关的依赖注入和 AOP。该框架的开发团队和 G
千家信息网最后更新 2024年11月22日Micronaut 教程:如何使用基于 JVM 的框架构建微服务?

本文要点:

Micronaut 是一种基于 jvm 的现代化全栈框架,用于构建模块化且易于测试的微服务应用程序。Micronaut 提供完全的编译时、反射无关的依赖注入和 AOP。该框架的开发团队和 Grails 框架的开发团队是同一个。Micronaut 框架集成了云技术,服务发现、分布式跟踪、断路器等微服务模式也内置到了框架中。在本教程中,你将使用不同的语言创建三个微服务:Java、Kotlin 和 Groovy。你还将了解使用 Micronaut HTTP 客户端消费其他微服务是多么容易,以及如何创建快速执行的功能测试。

与使用传统 JVM 框架构建的应用程序不同, Micronaut 提供 100% 的编译时、反射无关的依赖注入和 AOP。因此,Micronaut 应用程序很小,内存占用也很低。使用 Micronaut,你可以开发一个很大的单体应用或一个可以部署到 AWS Lambda 的小函数。框架不会限制你。

Micronaut 框架还集成了云技术,服务发现、分布式跟踪、断路器等微服务模式也内置到了框架中。

Micronaut 在 2018 年 5 月作为开源软件发布,计划在 2018 年底之前发布 1.0.0 版本。现在你可以试用 Micronaut,因为里程碑版本和发行候选版本已经可用。

Micronaut 框架的开发团队和 Grails 框架的开发团队是同一个。Grails 最近迎来了它的 10 周年纪念,它继续用许多生产力促进器帮助开发人员来编写 Web 应用程序。Grails 3 构建在 Spring Boot 之上。你很快就会发现,对于使用 Grails 和 Spring Boot 这两个框架的开发人员来说,Micronaut 有一个简单的学习曲线。

教程简介

在本系列文章中,我们将使用几个微服务创建一个应用程序:

  • 一个 books 微服务,使用 Groovy 编写;
  • 一个 inventory 微服务,使用 Kotlin 编写;
  • 一个 gateway 微服务,使用 Java 编写。
    你将完成以下工作:
  • 编写端点,使用编译时依赖注入;
  • 编写功能测试;
  • 配置那些 Micronaut 应用程序,注册到 Consul;
  • 使用 Micronaut 声明式 HTTP 客户端实现它们之间的通信。
    下图说明了你将要构建的应用程序:

微服务#1 Groovy 微服务

创建 Micronaut 应用的最简单方法是使用其命令行接口( Micronaut CLI ),使用 SDKMan 可以轻松安装。
Micronaut 应用程序可以使用 Java、Kotlin 和 Groovy 编写。首先,让我们创建一个 Groovy Micronaut 应用:

mn create-app example.micronaut.books --lang groovy .

上面的命令创建一个名为 books 的应用,默认包为 example.micronaut。

Micronaut 是测试框架无关的。它根据你使用的语言选择一个默认测试框架。在默认情况下,Java 使用 JUnit。如果你选择了 Groovy,在默认情况下,将使用 Spock。你可以搭配使用不同的语言和测试框架。例如,用 Spock 测试一个 Java Micronaut 应用程序。

而且,Micronaut 是构建工具无关的。你可以使用 Maven 或 Gradle 。默认使用 Gradle。

生成的应用中包含一个基于 Netty 的非阻塞 HTTP 服务器。

创建一个控制器暴露你的第一个 Micronaut 端点:

books/src/main/groovy/example/micronaut/BooksController.groovypackage example.micronautimport groovy.transform.CompileStaticimport io.micronaut.http.annotation.Controllerimport io.micronaut.http.annotation.Get@CompileStatic@Controller("/api")class BooksController { private final BooksRepository booksRepository BooksController(BooksRepository booksRepository) { this.booksRepository = booksRepository } @Get("/books") List list() { booksRepository.findAll() }}

在上面的代码中,有几个地方值得一提:

  • 控制器暴露一个 route/api/books 端点,可以使用 GET 请求调用;
  • 注解 @Get 和 @Controller 的值是一个 RFC-6570 URI 模板;
  • 通过构造函数注入,Micronaut 提供了一个协作类:BooksRepository;
  • Micronaut 控制器默认消费和生成 JSON。
    上述控制器使用了一个接口和一个 POGO:
books/src/main/groovy/example/micronaut/BooksRepository.groovypackage example.micronautinterface BooksRepository { List findAll()}books/src/main/groovy/example/micronaut/Book.groovypackage example.micronautimport groovy.transform.CompileStaticimport groovy.transform.TupleConstructor@CompileStatic@TupleConstructorclass Book { String isbn String name}

Micronaut 在编译时把一个实现了 BooksRepository 接口的 bean 连接起来。

对于这个应用,我们创建了一个单例,我们是使用 javax.inject.Singleton 注解定义的。

books/src/main/groovy/example/micronaut/BooksRepositoryImpl.groovypackage example.micronautimport groovy.transform.CompileStaticimport javax.inject.Singleton@CompileStatic@Singletonclass BooksRepositoryImpl implements BooksRepository { @Override List findAll() { [ new Book("1491950358", "Building Microservices"), new Book("1680502395", "Release It!"), ] }}

功能测试的价值最大,因为它们测试了整个应用程序。但是,对于其他框架,很少使用功能测试和集成测试。大多数情况下,因为它们涉及到整个应用程序的启动,所以速度很慢。

然而,在 Micronaut 中编写功能测试是一件乐事。因为它们很快,非常快。

上述控制器的功能测试如下:

books/src/test/groovy/example/micronaut/BooksControllerSpec.groovypackage example.micronautimport io.micronaut.context.ApplicationContextimport io.micronaut.core.type.Argumentimport io.micronaut.http.HttpRequestimport io.micronaut.http.client.RxHttpClientimport io.micronaut.runtime.server.EmbeddedServerimport spock.lang.AutoCleanupimport spock.lang.Sharedimport spock.lang.Specificationclass BooksControllerSpec extends Specification { @Shared @AutoCleanup EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer) @Shared @AutoCleanup RxHttpClient client = embeddedServer.applicationContext.createBean(RxHttpClient, embeddedServer.getURL()) void "test books retrieve"() {  when: HttpRequest request = HttpRequest.GET('/api/books') List books = client.toBlocking().retrieve(request, Argument.of(List, Book)) then: books books.size() == 2 }}

在上述测试中,有几个地方值得一提:

  • 借助 EmbeddedServer 接口,很容易从单元测试运行应用程序;
  • 很容易创建一个 HTTP 客户端 bean 来消费嵌入式服务器;
  • Micronaut Http 客户端很容易把 JSON 解析成 Java 对象。

    微服务#2 Kotlin 微服务

    运行下面的命令,创建另外一个名为 inventory 的微服务。这次,我们使用 Kotlin 语言。

> mn create-app example.micronaut.inventory --lang kotlin
这个新的微服务控制着每本书的库存。
创建一个 Kotlin数据类,封装属性域:

inventory/src/main/kotlin/example/micronaut/Book.ktpackage example.micronautdata class Book(val isbn: String, val stock: Int)

创建一个控制器,返回一本书的库存。

inventory/src/main/kotlin/example/micronaut/BookController.ktpackage example.micronautimport io.micronaut.http.HttpResponse import io.micronaut.http.MediaType import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get import io.micronaut.http.annotation.Produces@Controller("/api") class BooksController { @Produces(MediaType.TEXT_PLAIN)  @Get("/inventory/{isbn}")  fun inventory(isbn: String): HttpResponse { return when (isbn) {  "1491950358" -> HttpResponse.ok(2)  "1680502395" -> HttpResponse.ok(3)  else -> HttpResponse.notFound() } }}

微服务#3 Java 微服务

创建一个 Java 网关应用,该应用会消费 books 和 inventory 这两个微服务。

mn create-app example.micronaut.gateway

如果不指定 lang 标识,就会默认选用 Java。

在 gateway 微服务中,创建一个声明式HTTP 客户端和books 微服务通信。

首先创建一个接口:

gateway/src/main/java/example/micronaut/BooksFetcher.javapackage example.micronaut;import io.reactivex.Flowable;public interface BooksFetcher {  Flowable fetchBooks(); }

然后,创建一个声明式 HTTP 客户端,这是一个使用了 @Client 注解的接口。

gateway/src/main/java/example/micronaut/BooksClient.javapackage example.micronaut;import io.micronaut.context.annotation.Requires; import io.micronaut.context.env.Environment; import io.micronaut.http.annotation.Get; import io.micronaut.http.client.Client; import io.reactivex.Flowable;@Client("books") @Requires(notEnv = Environment.TEST) public interface BooksClient extends BooksFetcher { @Override @Get("/api/books") Flowable fetchBooks();}

Micronaut 声明式 HTTP 客户端方法将在编译时实现,极大地简化了 HTTP 客户端的创建。

此外,Micronaut 支持应用程序环境的概念。在上述代码清单中,你可以看到,使用 @Requires 注解很容易禁止某些 bean 在特定环境中加载。

而且,就像你在前面的代码示例中看到的那样,非阻塞类型在 Micronaut 中是一等公民。BooksClient::fetchBooks() 方法返回 Flowable,其中 Book 是一个 Java POJO:

gateway/src/main/java/example/micronaut/Book.javapackage example.micronaut;public class Book { private String isbn;  private String name;  private Integer stock; public Book() {} public Book(String isbn, String name) {  this.isbn = isbn;  this.name = name;  } public String getIsbn() { return isbn; } public void setIsbn(String isbn) { this.isbn = isbn; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getStock() { return stock; } public void setStock(Integer stock) { this.stock = stock; }}

创建另外一个声明式 HTTP 客户端,与 inventory 微服务通信。

首先创建一个接口:

gateway/src/main/java/example/micronaut/InventoryFetcher.javapackage example.micronaut;import io.reactivex.Maybe;public interface InventoryFetcher {  Maybe inventory(String isbn); }

然后,一个 HTTP 声明式客户端:

gateway/src/main/java/example/micronaut/InventoryClient.javapackage example.micronaut;import io.micronaut.context.annotation.Requires; import io.micronaut.context.env.Environment; import io.micronaut.http.annotation.Get; import io.micronaut.http.client.Client; import io.reactivex.Flowable;import io.reactivex.Maybe; import io.reactivex.Single;@Client("inventory") @Requires(notEnv = Environment.TEST)public interface InventoryClient extends InventoryFetcher { @Override  @Get("/api/inventory/{isbn}")  Maybe inventory(String isbn);}

现在,创建一个控制器,注入两个 bean,创建一个反应式应答。

gateway/src/main/java/example/micronaut/BooksController.javapackage example.micronaut;import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import io.reactivex.Flowable;@Controller("/api") public class BooksController { private final BooksFetcher booksFetcher;  private final InventoryFetcher inventoryFetcher; public BooksController(BooksFetcher booksFetcher, InventoryFetcher inventoryFetcher) { this.booksFetcher = booksFetcher; this.inventoryFetcher = inventoryFetcher;  } @Get("/books") Flowable findAll() {  return booksFetcher.fetchBooks() .flatMapMaybe(b -> inventoryFetcher.inventory(b.getIsbn()) .filter(stock -> stock > 0) .map(stock -> {  b.setStock(stock);  return b;  }) ); }}

在为控制器创建功能测试之前,我们需要在测试环境中为(BooksFetcher 和 InventoryFetcher)创建 bean 实现。

创建符合 BooksFetcher 接口的 bean,只适用于测试环境;参见 @Requires 注解。

gateway/src/test/java/example/micronaut/MockBooksClient.javapackage example.micronaut;import io.micronaut.context.annotation.Requires; import io.micronaut.context.env.Environment; import io.reactivex.Flowable;import javax.inject.Singleton;@Singleton @Requires(env = Environment.TEST) public class MockBooksClient implements BooksFetcher { @Override public Flowable fetchBooks() {  return Flowable.just(new Book("1491950358", "Building Microservices"), new Book("1680502395", "Release It!"), new Book("0321601912", "Continuous Delivery:")); } }

创建符合 InventoryFetcher 接口的 bean,只适用于测试环境;

gateway/src/test/java/example/micronaut/MockInventoryClient.javapackage example.micronaut;import io.micronaut.context.annotation.Requires; import io.micronaut.context.env.Environment; import io.reactivex.Maybe;import javax.inject.Singleton;@Singleton @Requires(env = Environment.TEST) public class MockInventoryClient implements InventoryFetcher { @Override  public Maybe inventory(String isbn) {  if (isbn.equals("1491950358")) {  return Maybe.just(2);  }  if (isbn.equals("1680502395")) {  return Maybe.just(0);  }  return Maybe.empty(); } }

创建功能测试。在 Groovy 微服务中,我们编写了一个 Spock 测试,这次,我们编写 JUnit 测试。

gateway/src/test/java/example/micronaut/BooksControllerTest.javapackage example.micronaut;import io.micronaut.context.ApplicationContext;import io.micronaut.core.type.Argument;import io.micronaut.http.HttpRequest;import io.micronaut.http.client.HttpClient;import io.micronaut.runtime.server.EmbeddedServer;import org.junit.AfterClass;import org.junit.BeforeClass;import org.junit.Test;import static org.junit.Assert.assertEquals;import static org.junit.Assert.assertNotNull;import java.util.List;public class BooksControllerTest { private static EmbeddedServer server;  private static HttpClient client; @BeforeClass  public static void setupServer() { server = ApplicationContext.run(EmbeddedServer.class);  client = server .getApplicationContext() .createBean(HttpClient.class, server.getURL()); } @AfterClass  public static void stopServer() { if (server != null) {  server.stop(); } if (client != null) {  client.stop(); } } @Test  public void retrieveBooks() {  HttpRequest request = HttpRequest.GET("/api/books");  List books = client.toBlocking().retrieve(request, Argument.of(List.class, Book.class));  assertNotNull(books);  assertEquals(1, books.size()); } }

服务发现

我们将配置我们的 Micronaut 微服务,注册到 Consul 服务发现。

Consul 是一个分布式服务网格,用于跨任何运行时平台和公有或私有云连接、防护和配置服务。

Micronaut 与 Consul 的集成很简单。

首先向 books、inventory 和 gateway 三个微服务中的每一个添加服务发现客户端依赖项:

gateway/build.gradleruntime "io.micronaut:discovery-client"books/build.gradleruntime "io.micronaut:discovery-client"inventory/build.gradleruntime "io.micronaut:discovery-client"

我们需要对每个应用的配置做一些修改,以便应用启动时注册到 Consul。

gateway/src/main/resources/application.ymlmicronaut: application: name: gateway  server: port: 8080consul: client: registration:  enabled: true defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"books/src/main/resources/application.ymlmicronaut: application: name: books server: port: 8082consul: client: registration:  enabled: true defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"inventory/src/main/resources/application.ymlmicronaut: application: name: inventory server: port: 8081consul: client: registration:  enabled: true defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"

每个服务在 Consul 中注册时都使用属性 microaut.application .name 作为服务 id。这就是为什么我们在前面的 @Client 注解中使用那些明确的名称。

前面的代码清单展示了 Micronaut 的另一个特性,配置文件中有带默认值的环境变量插值,如下所示:

defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"

另外,在 Micronaut 中可以有特定于环境的配置文件。我们将在每个环境中创建一个名为 application-test.yml 的文件,用于测试阶段的 Consul 注册。

gateway/src/test/resources/application-test.ymlconsul: client: registration: enabled: falsebooks/src/test/resources/application-test.ymlconsul: client: registration: enabled: falseinventory/src/test/resources/application-test.ymlconsul: client: registration: enabled: false

**运行应用
开始使用 Consul 的最简单方式是通过 Docker。现在,运行一个 Docker 实例。

docker run -p 8500:8500 consul

使用 Gradle 创建一个多项目构建。在根目录下创建一个settings.gradle 文件。

settings.gradleinclude 'books' include 'inventory' include 'gateway'

现在,你可以并行运行每个应用了。Gradle 为此提供了一个方便的标识(-parallel):

./gradlew -parallel run

每个微服务都在配置好的端口上启动:8080、8081 和 8082。

Consul 提供了一个 HTML UI。在浏览器中打开 http://localhost:8500/ui,你会看到:

每个 Micronaut 微服务都已注册到 Consul。

你可以使用下面的 curl 命令调用网关微服务:

$ curl http://localhost:8080/api/books [{"isbn":"1680502395","name":"Release It!","stock":3}, {"isbn":"1491950358","name":"Building Microservices","stock":2}]

恭喜你已经创建好了第一个 Micronaut 微服务网络!

小结

在本教程中,你用不同的语言创建了三个微服务:Java、Kotlin 和 Groovy。你还了解了使用 Micronaut HTTP 客户端消费其他微服务是多么容易,以及如何创建快速执行的功能测试。

此外,你创建的一切都可以利用完全反射无关的依赖注入和 AOP。

服务 应用 测试 框架 应用程序 程序 客户 客户端 功能 功能测试 接口 控制 控制器 环境 开发 配置 注解 运行 语言 消费 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 a8立式服务器机箱 有关环保的数据库文件 网络安全法第四十四规定 海康威视服务器怎么打开 新形势下网络安全建设探讨 网络安全非标机箱供应商 大学生科技节互联网金融大赛 shell怎么跨服务器拉取文件 魂师对决苹果端服务器有哪些 企业服务器管理员工资待遇 cno数据库是什么意思啊 大数据条件下传统关系数据库 新时代集团网络安全 违反网络安全法行政案件 西安网络安全公司排行榜 软件开发外企上海 河北最快的dns服务器云空间 新华保险软件开发岗面试 文件服务器和文档管理系统 软件开发运营范围 数据库分析为什么最难 包头有哪些软件开发公司 汇智凌云的软件开发者 《网络安全》知识答题d 衡阳县天气预报软件开发 我的世界服务器ip地址在哪找 机柜服务器电源插头 数据库查询效率慢 数据库服务器指的是什么 服务器创建快捷方式
0