micronaut
重要要点
- Micronaut是基于JVM的现代全栈框架,用于构建模块化,易于测试的微服务应用程序。
- Micronaut具有100%的编译时间,无反射,依赖项注入和AOP。
- 框架开发团队与Grails框架背后的团队相同。
- Micronaut将云技术集成到框架中,并将微服务模式(例如服务发现,分布式跟踪,断路器)烘焙到框架中。
- 在本教程中,您将创建三种使用不同语言的微服务:Java,Kotlin和Groovy。 您还将学习使用Micronaut HTTP Client使用其他微服务有多么容易,以及如何创建运行速度很快的功能测试。
与使用传统JVM框架构建的应用程序不同, Micronaut具有100%的编译时,无反射,依赖项注入和AOP。 因此,Micronaut应用程序很小,内存占用很少。 使用Micronaut,您可以开发可以部署到AWS Lambda的大型组件或小型功能。 您不受框架的约束。
Micronaut还将云技术集成到框架中,并将微服务模式(例如服务发现,分布式跟踪,断路器)烘焙到框架中。
Micronaut于2018年5月以开源形式发布,计划于2018年底发布其1.0.0版本。您可以立即试用Micronaut,因为它具有里程碑和候选版本。
Micronaut开发团队与Grails Framework背后的团队相同。 最近刚迎来10周年庆的Grails继续帮助开发人员使用许多提高生产力的方法来制作Web应用程序。 Grails 3构建在Spring Boot之上。 您很快就会发现,对于来自Grails和Spring Boot两种框架的开发人员来说,Micronaut都有一条容易学习的曲线。
教程概述
在本系列文章中,我们将创建一个由几个微服务组成的应用程序:
-
books
微服务; 用Groovy编写。 -
inventory
微服务; 用Kotlin写的。 -
gateway
微服务; 用Java编写。
你会:
- 编写端点并使用编译时DI。
- 编写功能测试。
- 配置这些micronaut应用程序以向Consul注册。
- 使用Micronaut声明性HTTP客户端在它们之间进行通信。
下图说明了该应用程序,您将构建:
微服务#1-Groovy微服务
创建Micronaut应用程序的最简单方法是使用其命令行界面( Micronaut CLI ),该界面可通过SDKMan轻松安装。
Micronaut应用程序可以用Java,Kotlin和Groovy编写 。 让我们首先创建一个Groovy Micronaut应用程序:
mn create-app example.micronaut.books --lang groovy
。
上一个命令使用默认包example.micronaut
创建一个名为books的应用程序。
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()
}
}
前面的代码中有几件事值得提及。
- Controller公开了可以通过GET请求调用的route / api / books。
- @Get和@Controller批注的值是RFC-6570 URI模板。
- 通过构造函数注入,Micronaut提供了一个协作者。 BooksRepository。
- 默认情况下,Micronaut控制器使用并生成JSON。
前一个控制器使用一个接口和一个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
}
}
以前的测试中有几件事值得一提:
- 使用EmbeddedServer接口从单元测试中运行应用程序很容易。
- 您可以轻松创建一个HTTP Client bean来使用嵌入式服务器。
- Micronaut Http Client使将JSON解析为Java对象变得容易。
第二个微服务。 Kotlin微服务
运行下一个命令以创建另一个名为库存的微服务。 这次,我们使用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
import 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
如果未指定lang
标记,则默认情况下会选择Java。
在gateway
微服务内部,创建一个声明性HTTP客户端以与书籍微服务通信。
首先创建一个接口:
gateway/src/main/java/example/micronaut/BooksFetcher.java
package example.micronaut;
import io.reactivex.Flowable;
public interface BooksFetcher {
Flowable<Book> fetchBooks();
}
然后创建一个声明性的HTTP客户端; 用@Client.
注释的接口@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()
方法返回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.Maybe;
@Client("inventory")
@Requires(notEnv = Environment.TEST)
public interface InventoryClient extends InventoryFetcher {
@Override
@Get("/api/inventory/{isbn}")
Maybe<Integer> inventory(String isbn);
}
现在,创建一个同时注入两个bean并创建React式响应的控制器。
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 {
@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 Service发现注册。
Consul是一个分布式服务网格,用于跨任何运行时平台和公共或私有云连接,保护和配置服务
整合Micronaut和Consul很简单。
首先添加到每本微服务books
, nventory
和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: 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中注册时,都将属性icronaut.application.name
用作服务id。 这就是为什么我们在先前的@Client
批注中使用这些确切名称的@Client
。
先前的代码清单说明了Micronaut的另一个功能,即在配置文件中带有默认值的环境变量插值。 看到:
defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
另外,在Micronaut中,您可以具有特定于环境的配置文件。 我们将在每个环境中创建一个名为application-test.yml
的文件,以在测试阶段咨询注册。
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 ,您将看到:
您可以使用下一个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的所有内容。
和我一起参加本系列的第二部分,即将推出。 同时,请随时通过下面的评论部分提出问题。
micronaut