caballero java_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客户端实现它们之间的通信。

下图说明了你将要构建的应用程序:

8741-1539964751215.png

微服务#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.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 list() {

booksRepository.findAll()

}

}

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

控制器暴露一个route/api/books端点,可以使用GET请求调用;

注解@Get和@Controller的值是一个RFC-6570 URI模板;

通过构造函数注入,Micronaut提供了一个协作类:BooksRepository;

Micronaut控制器默认消费和生成JSON。

上述控制器使用了一个接口和一个POGO:

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

package example.micronaut

interface BooksRepository {

List 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 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 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.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 {

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

package example.micronaut;

import io.reactivex.Flowable;

public interface BooksFetcher {

Flowable 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 fetchBooks();

}

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

此外,Micronaut支持 应用程序环境

的概念。在上述代码清单中,你可以看到,使用 @Requires

注解很容易禁止某些bean在特定环境中加载。

而且,就像你在前面的代码示例中看到的那样,非阻塞类型在Micronaut中是一等公民。BooksClient::fetchBooks()方法返回Flowable,其中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 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 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 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 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 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 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,你会看到:

af14dd9c8affbfa320eed4ccb6e1b5e4.png

每个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。

欢迎感兴趣的读者和我一起编写即将到来的第二部分。同时,请在下面的评论区自由提问。

关于作者

b4dc097e71c646fda3d2a388cc1c6f3b.png

Sergio del Amo

Caballero

是一名专门从事以Grails/Micronaut为后端的移动手机应用程序(iOS、Android)开发的开发人员。自2015年以来,Sergio del Amo围绕Groovy生态系统和微服务撰写简讯“ Groovy Calamari

”。Groovy、Grails、Micronaut, Gradle、…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值