点击围观!腾讯 TAPD 助力金融行业研发提效、敏捷转型最佳实践! 了解详情
写点什么

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

  • 2018-10-19
  • 本文字数:0 字

    阅读完需:约 1 分钟

本文要点

  • 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.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()
    }
}

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

  • 控制器暴露一个 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<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 客户端 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<Int> {
        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<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

2018-10-19 15:595963
用户头像

发布了 1008 篇内容, 共 356.7 次阅读, 收获喜欢 332 次。

关注

评论 1 条评论

发布
暂无评论
发现更多内容

【21-1】21 连更第一篇

耳东@Erdong

6月日更

Kubernetes 的自动伸缩你用对了吗?

张晓辉

Kubernetes k8s最佳实践

证券互动问答平台关键词监控提醒

木头

互动平台 证券监控 股市消息 监控提醒

☕️【Java技术之旅】站在Linux操作系统角度去看Thread(线程)

洛神灬殇

线程 Thread 6月日更 内核线程

TcaplusDB小知识之TcaplusDB限制条件

数据人er

数据库 nosql tencentdb TcaplusDB

那个陪我打王者的兄弟进了阿里

艾小仙

这些书都学完,绝对是编程界的大佬

看山

Java 程序员 6月日更

【Flutter 专题】109 图解自定义 ACERadio 单选框

阿策小和尚

Flutter 小菜 0 基础学习 Flutter Android 小菜鸟 6月日更

密码学系列之:生日攻击

程序那些事

加密解密 密码学 程序那些事

内嵌双向链表的设计与实现

实力程序员

模块六作业

c

架构实战营

Java 并发编程——线程池开篇

Antway

6月日更

一文教会你认识Vuex状态机

华为云开发者联盟

Vue 应用 vuex 事件 父子组件

缓存穿透、缓存雪崩、缓存击穿问题与优化方案

Skysper

当人工智能遇上视频直播——基于Agora Web SDK实现目标识别

dajyaretakuya

深度学习 音视频 WebRTC 声网 TensorFlow.js

读深入ES6记[二]

蛋先生DX

ES6 6月日更

聊聊追求测试技术导致过度测试

陈磊@Criss

如何进行可视化大屏视觉设计?

博文视点Broadview

【布道API】浅谈API设计风格

devpoint

Rest API 6月日更

Python——字符串转换与处理

在即

6月日更

Packer 自动化镜像 Windows 安装过程

HoneyMoose

Locust完成gRPC协议的性能测试

陈磊@Criss

【Vue2.x 源码学习】第八篇 - 数组的深层劫持

Brave

源码 vue2 6月日更

【LeetCode】从上到下打印二叉树 Java题解

Albert

算法 LeetCode 6月日更

你愿意被管理么?

escray

学习 极客时间 朱赟的技术管理课 6月日更

Mybatis 二级缓存简单示例

Java mybatis

SpringBootApplication注解

梦倚栏杆

递归全排列问题(两种方法 Java实现)

若尘

数据结构 递归 6月日更

大道看“虚实”,DOM的虚实并进

法医

Vue 大前端 6月日更

Webpack 系列:如何编写loader

范文杰

webpack 6月日更

高性能 JavaScriptの七 -- 编程实践小技巧

空城机

JavaScript 大前端 6月日更

Micronaut教程:如何使用基于JVM的框架构建微服务_Java_Sergio del Amo Caballero_InfoQ精选文章