与使用传统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有一个简单的学习曲线。
在本系列文章中,我们将使用几个微服务创建一个应用程序:
你将完成以下工作:
下图说明了你将要构建的应用程序:
创建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.CompileStatic import io.micronaut.http.annotation.Controller import 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() } }
在上面的代码中,有几个地方值得一提:
上述控制器使用了一个接口和一个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.CompileStatic import groovy.transform.TupleConstructor @CompileStatic @TupleConstructor class Book { String isbn String name }
Micronaut在 编译时 把一个实现了BooksRepository接口的bean连接起来。
对于这个应用,我们创建了一个单例,我们是使用javax.inject.Singleton注解定义的。
books/src/main/groovy/example/micronaut/BooksRepositoryImpl.groovy package example.micronaut import groovy.transform.CompileStatic import javax.inject.Singleton @CompileStatic @Singleton class BooksRepositoryImpl implements BooksRepository { @Override List<Book> findAll() { [ new Book("1491950358", "Building Microservices"), new Book("1680502395", "Release It!"), ] } }
功能测试的价值最大,因为它们测试了整个应用程序。但是,对于其他框架,很少使用功能测试和集成测试。大多数情况下,因为它们涉及到整个应用程序的启动,所以速度很慢。
然而,在Micronaut中编写功能测试是一件乐事。因为它们很快,非常快。
上述控制器的功能测试如下:
books/src/test/groovy/example/micronaut/BooksControllerSpec.groovy package example.micronaut import io.micronaut.context.ApplicationContext import io.micronaut.core.type.Argument import io.micronaut.http.HttpRequest import io.micronaut.http.client.RxHttpClient import io.micronaut.runtime.server.EmbeddedServer import spock.lang.AutoCleanup import spock.lang.Shared import spock.lang.Specification class 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<Book> books = client.toBlocking().retrieve(request, Argument.of(List, Book)) then: books books.size() == 2 } }
在上述测试中,有几个地方值得一提:
运行下面的命令,创建另外一个名为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.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<Int> { return when (isbn) { "1491950358" -> HttpResponse.ok(2) "1680502395" -> HttpResponse.ok(3) else -> HttpResponse.notFound() } } }
创建一个Java网关应用,该应用会消费books和inventory这两个微服务。
mn create-app example.micronaut.gateway
如果不指定lang标识,就会默认选用Java。
在gateway微服务中,创建一个 声明式HTTP客户端 和books微服务通信。
首先创建一个接口:
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.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微服务通信。
首先创建一个接口:
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.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<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.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<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 { @Override public 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的集成很简单。
首先向books、inventory和gateway三个微服务中的每一个添加服务发现客户端依赖项:
gateway/build.gradle runtime "io.micronaut:discovery-client" books/build.gradle runtime "io.micronaut:discovery-client" inventory/build.gradle runtime "io.micronaut:discovery-client"
我们需要对每个应用的配置做一些修改,以便应用启动时注册到Consul。
gateway/src/main/resources/application.yml micronaut: application: name: gateway server: port: 8080 consul: client: registration: enabled: true defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}" books/src/main/resources/application.yml micronaut: application: name: books server: port: 8082 consul: client: registration: enabled: true defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}" inventory/src/main/resources/application.yml micronaut: application: name: inventory server: port: 8081 consul: 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.yml consul: client: registration: enabled: false books/src/test/resources/application-test.yml consul: client: registration: enabled: false inventory/src/test/resources/application-test.yml consul: client: registration: enabled: false
开始使用Consul的最简单方式是通过Docker。现在,运行一个Docker实例。
docker run -p 8500:8500 consul
使用Gradle创建一个 多项目构建 。在根目录下创建一个settings.gradle文件。
settings.gradle include '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。
欢迎感兴趣的读者和我一起编写即将到来的第二部分。同时,请在下面的评论区自由提问。
Sergio del Amo Caballero 是一名专门从事以Grails/Micronaut为后端的移动手机应用程序(iOS、Android)开发的开发人员。自2015年以来,Sergio del Amo围绕Groovy生态系统和微服务撰写简讯“ Groovy Calamari ”。Groovy、Grails、Micronaut, Gradle、…
查看英文原文: Micronaut Tutorial: How to Build Microservices with this JVM-based Framework