2个月前 (08-02)  微服务 |   抢沙发  35 
文章评分 0 次,平均分 0.0

与使用传统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.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公开一个路由/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.CompileStatic
import groovy.transform.TupleConstructor

@CompileStatic
@TupleConstructor
class Book {
    String isbn
    String name
}

Micronaut在编译时连接一个实现BooksRepository接口的bean。

对于这个应用程序,我们创建了一个singleton,我们用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编写功能测试是一件乐事。因为他们很快;真的很快。

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

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对象变得很容易。

第二个微服务-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
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

默认情况下,如果不指定语言,则选择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; 
                        })
                    );

    }
}

在为控制器创建功能测试之前,我们需要在测试环境中为(BooksFetcherInventoryFetcher)创建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很简单。

首先,将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: 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中注册时,每个服务都使用属性micronaut.application.name作为服务id。这就是为什么我们在前面的@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

每个微服务在配置的端口808080818082中启动。

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的一切。

原文地址:https://www.infoq.com/articles/micronaut-tutorial-microservices-jvm/

 

除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/2188.html

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册