写点什么

使用 Micronaut 框架构建一个微服务网络.

用户头像
Java 白
关注
发布于: 刚刚

与使用传统 JVM 框架构建的应用程序不同,Micronaut 具有 100%的编译时间、无反射、依赖注入和 AOP。因此,Micronaut 应用程序体积小,内存占用小。使用 Micronaut,您可以开发一个大的整体或一个可以部署到 AWS Lambda 的小功能。您不受框架的约束。


Micronaut 还将云技术集成到框架中,并将服务发现、分布式跟踪、断路器等微服务模式烘焙到框架中。


Micronaut 于 2018 年 5 月以开源形式发布,并计划于 2018 年底发布其 1.0.0 版本。您可以在今天尝试 Micronaut,因为里程碑和发布候选版本都可用。


Micronaut 开发团队就是 Grails 框架背后的同一个团队。Grails 最近迎来了它的十周年纪念日,它继续帮助开发人员使用许多生产力提升工具来设计 web 应用程序。Grails3 构建在 Spring Boot 之上。您很快就会发现,Micronaut 为来自 Grails 和 Spring Boot 这两个框架的开发人员提供了一个简单的学习曲线。


在本系列文章中,我们将创建一个由多个微服务组成的应用程序:


图书微服务;用 Groovy 编写。库存微服务;用 Kotlin 写的。网关微服务;用 Java 编写。你会:


编写端点并使用 compiletime DI。编写功能测试。将这些 micronaut 应用程序配置为向 Consul 注册。它们之间通过 Micronaut 声明性 HTTP 客户端进行通信。下一个图表说明了应用程序,您将构建:


使用 Micronaut 框架构建一个微服务网络微服务-一个非常棒的微服务创建 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.groovy


package example.micronaut


import 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<Book> list() { booksRepository.findAll()}
复制代码


}在前面的代码中有几点值得一提。


Controller 公开一个路由 /api/books ,可以通过 GET 请求调用它。@Get 和 @Controller 注释的值是 RFC-6570 URI 模板。通过构造函数注入,Micronaut 提供一个 collaborator : BooksRepository 。默认情况下,Micronaut 控制器使用并生成 JSON。前一个 Controller 使用接口和 POGO:


books/src/main/groovy/example/micronaut/BooksRepository.groovy


package example.micronaut


interface BooksRepository {List<Book> findAll()}


books/src/main/groovy/example/micronaut/Book.groovy


package example.micronaut


import groovy.transform.CompileStaticimport groovy.transform.TupleConstructor


@CompileStatic@TupleConstructorclass Book {String isbnString name}Micronaut 在编译时连接一个实现 BooksRepository 接口的 bean。


对于这个应用程序,我们创建了一个 singleton,我们用 javax.inject.singleton 注释定义它。


books/src/main/groovy/example/micronaut/BooksRepositoryImpl.groovy


package example.micronaut


import groovy.transform.CompileStaticimport javax.inject.Singleton


@CompileStatic@Singletonclass BooksRepositoryImpl implements BooksRepository {


@OverrideList<Book> findAll() {    [        new Book("1491950358", "Building Microservices"),        new Book("1680502395", "Release It!"),    ]}
复制代码


}功能测试增加了最大的价值,因为它们测试整个应用程序。然而,对于其他框架,很少使用功能测试和集成测试。主要是因为它们涉及整个应用程序的启动,所以速度很慢。


然而,用 Micronaut 编写功能测试是一件乐事。因为他们很快;真的很快。


下表列出了先前 controller 的功能测试:


books/src/test/groovy/example/micronaut/BooksControllerSpec.groovy


package example.micronaut


import 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.Specification


class BooksControllerSpec extends Specification {


@Shared@AutoCleanupEmbeddedServer 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<Book> books = client.toBlocking().retrieve(request, Argument.of(List, Book))
then: books books.size() == 2}
复制代码


}上一次测试中有几点值得一提:


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


mn create-app example.micronaut.inventory --lang kotlin 这项新的微服务控制着每本书的库存。


创建 Kotlin 数据类以封装域:


inventory/src/main/kotlin/example/micronaut/Book.kt


package example.micronaut


data class Book(val isbn: String, val stock: Int)创建一个控制器,用于返回书籍的库存。


inventory/src/main/kotlin/example/micronaut/BookController.kt


package example.micronaut


import io.micronaut.http.HttpResponseimport io.micronaut.http.MediaTypeimport io.micronaut.http.annotation.Controllerimport io.micronaut.http.annotation.Getimport io.micronaut.http.annotation.Producesimport io.micronaut.security.annotation.Secured


@Controller("/api")class BooksController {


@Produces(MediaType.TEXT_PLAIN) @Get("/inventory/{isbn}") fun inventory(isbn: String): HttpResponse<Int> {    return when (isbn) {         "1491950358" -> HttpResponse.ok(2)         "1680502395" -> HttpResponse.ok(3)         else -> HttpResponse.notFound()    }}
复制代码


}第三个微服务-Java 微服务创建一个使用书籍和库存微服务的 Java 网关应用程序。


mn create-app example.micronaut.gateway 默认情况下,如果不指定语言,则选择 Java。


在网关微服务内部,创建一个声明性 HTTP 客户端以与图书微服务通信。


首先创建一个接口:


gateway/src/main/java/example/micronaut/BooksFetcher.java


package example.micronaut;


import io.reactivex.Flowable;


public interface BooksFetcher {Flowable<Book> fetchBooks();}然后创建一个声明性 HTTP 客户端;用 @Client 注释的接口。


gateway/src/main/java/example/micronaut/BooksClient.java


package 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.annotation.Client;import io.reactivex.Flowable;


@Client("books")


@Requires(notEnv = Environment.TEST)


public interface BooksClient extends BooksFetcher {


@Override @Get("/api/books") Flowable<Book> fetchBooks();
复制代码


}Micronaut 声明性 HTTP 客户端方法将在编译时为您实现,从而大大简化 HTTP 客户端的创建。


此外,Micronaut 支持应用程序环境的概念。在前面的代码清单中,您可以看到为带有 @Requires 注释的特定环境禁用某些 bean 的加载是多么容易。


此外,从前面的代码示例中可以看出,非阻塞类型是 Micronaut 中的一级公民。 BooksClient::fetchBooks() 方法返回 Flowable<Book> ,其中 Book 是 Java POJO:


gateway/src/main/java/example/micronaut/Book.java


package 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 microservice 通信。


首先创建一个接口:


gateway/src/main/java/example/micronaut/InventoryFetcher.java


package example.micronaut;


import io.reactivex.Maybe;


public interface InventoryFetcher {Maybe<Integer> inventory(String isbn);}然后,HTTP 声明性客户端:


gateway/src/main/java/example/micronaut/InventoryClient.java


package 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.Maybe;


@Client("inventory")@Requires(notEnv = Environment.TEST)public interface InventoryClient extends InventoryFetcher {@Override@Get("/api/inventory/{isbn}")Maybe<Integer> inventory(String isbn);}现在,创建一个控制器,它注入两个 bean 并创建一个反应响应。


gateway/src/main/java/example/micronaut/BooksController.java


package example.micronaut;


import io.micronaut.http.annotation.Controller;import io.micronaut.http.annotation.Get;import io.micronaut.security.annotation.Secured;import io.reactivex.Flowable;import java.util.List;


@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<Book> 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.java


package 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 {@Overridepublic Flowable<Book> 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.java


package 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<Integer> 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.java


package 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<Book> books = client.toBlocking().retrieve(request, Argument.of(List.class, Book.class)); assertNotNull(books); assertEquals(1, books.size()); }
复制代码


}发现服务我们将配置我们的 micronaut 微服务,以便向 Consul 服务发现注册。


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


集成 Micronaut 和 Consul 很简单。


首先,将 discovery 客户端依赖项添加到每个 microservice books、inventory 和 gateway 中:


gateway/build.gradle


runtime "io.micronaut:micronaut-discovery-client"


books/build.gradle


runtime "io.micronaut:micronaut-discovery-client"


inventory/build.gradle


runtime "io.micronaut:micronaut-discovery-client"我们需要对每个应用程序进行一些配置更改,以便当应用程序启动时,它可以向 Consul 注册。


gateway/src/main/resources/application.yml


micronaut:application:name: gatewayserver:port: 8080consul:client:registration:enabled: truedefaultZone: "{CONSUL_PORT:8500}"


books/src/main/resources/application.ymlmicronaut:application:name: booksserver:port: 8082consul:client:registration:enabled: truedefaultZone: "{CONSUL_PORT:8500}"


inventory/src/main/resources/application.ymlmicronaut:application:name: inventoryserver:port: 8081consul:client:registration:enabled: truedefaultZone: "{CONSUL_PORT:8500}"在 Consul 中注册时,每个服务都使用属性 micronaut.application.name 作为服务 id。这就是为什么我们在前面的 @Client 注释中使用这些确切的名称。


前面的代码清单说明了 Micronaut 的另一个功能,即配置文件中带有默认值的环境变量插值。见:


defaultZone: "{CONSUL_PORT:8500}"此外,在 Micronaut 中,您可以拥有环境特定配置。我们将在每个环境中创建一个名为 application-test.yml 的文件,以便在测试阶段协商注册。


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


books/src/test/resources/application-test.ymlconsul:client:registration: enabled: false


inventory/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 用户界面。打开 http://localhost:8500/ui 在浏览器中,您将看到:


使用 Micronaut 框架构建一个微服务网络 $ 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 的一切。


如果您觉得文章对您有帮助,可以点赞评论转发支持一下~蟹蟹!

用户头像

Java 白

关注

还未添加个人签名 2021.06.21 加入

Java使我秃头

评论

发布
暂无评论
使用Micronaut框架构建一个微服务网络.