您好,登錄后才能下訂單哦!
本文要點:
Micronaut 是一種基于 jvm 的現(xiàn)代化全??蚣埽糜跇?gòu)建模塊化且易于測試的微服務(wù)應(yīng)用程序。Micronaut 提供完全的編譯時、反射無關(guān)的依賴注入和 AOP。該框架的開發(fā)團(tuán)隊和 Grails 框架的開發(fā)團(tuán)隊是同一個。Micronaut 框架集成了云技術(shù),服務(wù)發(fā)現(xiàn)、分布式跟蹤、斷路器等微服務(wù)模式也內(nèi)置到了框架中。在本教程中,你將使用不同的語言創(chuàng)建三個微服務(wù):Java、Kotlin 和 Groovy。你還將了解使用 Micronaut HTTP 客戶端消費其他微服務(wù)是多么容易,以及如何創(chuàng)建快速執(zhí)行的功能測試。
與使用傳統(tǒng) JVM 框架構(gòu)建的應(yīng)用程序不同, Micronaut 提供 100% 的編譯時、反射無關(guān)的依賴注入和 AOP。因此,Micronaut 應(yīng)用程序很小,內(nèi)存占用也很低。使用 Micronaut,你可以開發(fā)一個很大的單體應(yīng)用或一個可以部署到 AWS Lambda 的小函數(shù)。框架不會限制你。
Micronaut 框架還集成了云技術(shù),服務(wù)發(fā)現(xiàn)、分布式跟蹤、斷路器等微服務(wù)模式也內(nèi)置到了框架中。
Micronaut 在 2018 年 5 月作為開源軟件發(fā)布,計劃在 2018 年底之前發(fā)布 1.0.0 版本?,F(xiàn)在你可以試用 Micronaut,因為里程碑版本和發(fā)行候選版本已經(jīng)可用。
Micronaut 框架的開發(fā)團(tuán)隊和 Grails 框架的開發(fā)團(tuán)隊是同一個。Grails 最近迎來了它的 10 周年紀(jì)念,它繼續(xù)用許多生產(chǎn)力促進(jìn)器幫助開發(fā)人員來編寫 Web 應(yīng)用程序。Grails 3 構(gòu)建在 Spring Boot 之上。你很快就會發(fā)現(xiàn),對于使用 Grails 和 Spring Boot 這兩個框架的開發(fā)人員來說,Micronaut 有一個簡單的學(xué)習(xí)曲線。
在本系列文章中,我們將使用幾個微服務(wù)創(chuàng)建一個應(yīng)用程序:
創(chuàng)建 Micronaut 應(yīng)用的最簡單方法是使用其命令行接口( Micronaut CLI ),使用 SDKMan 可以輕松安裝。
Micronaut 應(yīng)用程序可以使用 Java、Kotlin 和 Groovy 編寫。首先,讓我們創(chuàng)建一個 Groovy Micronaut 應(yīng)用:
mn create-app example.micronaut.books --lang groovy .
上面的命令創(chuàng)建一個名為 books 的應(yīng)用,默認(rèn)包為 example.micronaut。
Micronaut 是測試框架無關(guān)的。它根據(jù)你使用的語言選擇一個默認(rèn)測試框架。在默認(rèn)情況下,Java 使用 JUnit。如果你選擇了 Groovy,在默認(rèn)情況下,將使用 Spock。你可以搭配使用不同的語言和測試框架。例如,用 Spock 測試一個 Java Micronaut 應(yīng)用程序。
而且,Micronaut 是構(gòu)建工具無關(guān)的。你可以使用 Maven 或 Gradle 。默認(rèn)使用 Gradle。
生成的應(yīng)用中包含一個基于 Netty 的非阻塞 HTTP 服務(wù)器。
創(chuàng)建一個控制器暴露你的第一個 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()
}
}
在上面的代碼中,有幾個地方值得一提:
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 在編譯時把一個實現(xiàn)了 BooksRepository 接口的 bean 連接起來。
對于這個應(yīng)用,我們創(chuàng)建了一個單例,我們是使用 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!"),
]
}
}
功能測試的價值最大,因為它們測試了整個應(yīng)用程序。但是,對于其他框架,很少使用功能測試和集成測試。大多數(shù)情況下,因為它們涉及到整個應(yīng)用程序的啟動,所以速度很慢。
然而,在 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
}
}
在上述測試中,有幾個地方值得一提:
運行下面的命令,創(chuàng)建另外一個名為 inventory 的微服務(wù)。這次,我們使用 Kotlin 語言。
> mn create-app example.micronaut.inventory --lang kotlin
這個新的微服務(wù)控制著每本書的庫存。
創(chuàng)建一個 Kotlin數(shù)據(jù)類,封裝屬性域:
inventory/src/main/kotlin/example/micronaut/Book.kt
package example.micronaut
data class Book(val isbn: String, val stock: Int)
創(chuàng)建一個控制器,返回一本書的庫存。
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()
}
}
}
創(chuàng)建一個 Java 網(wǎng)關(guān)應(yīng)用,該應(yīng)用會消費 books 和 inventory 這兩個微服務(wù)。
mn create-app example.micronaut.gateway
如果不指定 lang 標(biāo)識,就會默認(rèn)選用 Java。
在 gateway 微服務(wù)中,創(chuàng)建一個聲明式HTTP 客戶端和books 微服務(wù)通信。
首先創(chuàng)建一個接口:
gateway/src/main/java/example/micronaut/BooksFetcher.java
package example.micronaut;
import io.reactivex.Flowable;
public interface BooksFetcher {
Flowable<Book> fetchBooks();
}
然后,創(chuàng)建一個聲明式 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 客戶端方法將在編譯時實現(xiàn),極大地簡化了 HTTP 客戶端的創(chuàng)建。
此外,Micronaut 支持應(yīng)用程序環(huán)境的概念。在上述代碼清單中,你可以看到,使用 @Requires 注解很容易禁止某些 bean 在特定環(huán)境中加載。
而且,就像你在前面的代碼示例中看到的那樣,非阻塞類型在 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; }
}
創(chuàng)建另外一個聲明式 HTTP 客戶端,與 inventory 微服務(wù)通信。
首先創(chuàng)建一個接口:
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);
}
現(xiàn)在,創(chuàng)建一個控制器,注入兩個 bean,創(chuàng)建一個反應(yīng)式應(yīng)答。
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;
})
);
}
}
在為控制器創(chuàng)建功能測試之前,我們需要在測試環(huán)境中為(BooksFetcher 和 InventoryFetcher)創(chuàng)建 bean 實現(xiàn)。
創(chuàng)建符合 BooksFetcher 接口的 bean,只適用于測試環(huán)境;參見 @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:"));
}
}
創(chuàng)建符合 InventoryFetcher 接口的 bean,只適用于測試環(huán)境;
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();
}
}
創(chuàng)建功能測試。在 Groovy 微服務(wù)中,我們編寫了一個 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 微服務(wù),注冊到 Consul 服務(wù)發(fā)現(xiàn)。
Consul 是一個分布式服務(wù)網(wǎng)格,用于跨任何運行時平臺和公有或私有云連接、防護(hù)和配置服務(wù)。
Micronaut 與 Consul 的集成很簡單。
首先向 books、inventory 和 gateway 三個微服務(wù)中的每一個添加服務(wù)發(fā)現(xiàn)客戶端依賴項:
gateway/build.gradle
runtime "io.micronaut:discovery-client"
books/build.gradle
runtime "io.micronaut:discovery-client"
inventory/build.gradle
runtime "io.micronaut:discovery-client"
我們需要對每個應(yīng)用的配置做一些修改,以便應(yīng)用啟動時注冊到 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}"
每個服務(wù)在 Consul 中注冊時都使用屬性 microaut.application .name 作為服務(wù) id。這就是為什么我們在前面的 @Client 注解中使用那些明確的名稱。
前面的代碼清單展示了 Micronaut 的另一個特性,配置文件中有帶默認(rèn)值的環(huán)境變量插值,如下所示:
defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"
另外,在 Micronaut 中可以有特定于環(huán)境的配置文件。我們將在每個環(huán)境中創(chuàng)建一個名為 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
**運行應(yīng)用
開始使用 Consul 的最簡單方式是通過 Docker。現(xiàn)在,運行一個 Docker 實例。
docker run -p 8500:8500 consul
使用 Gradle 創(chuàng)建一個多項目構(gòu)建。在根目錄下創(chuàng)建一個settings.gradle 文件。
settings.gradle
include 'books'
include 'inventory'
include 'gateway'
現(xiàn)在,你可以并行運行每個應(yīng)用了。Gradle 為此提供了一個方便的標(biāo)識(-parallel):
./gradlew -parallel run
每個微服務(wù)都在配置好的端口上啟動:8080、8081 和 8082。
Consul 提供了一個 HTML UI。在瀏覽器中打開 http://localhost:8500/ui,你會看到:
每個 Micronaut 微服務(wù)都已注冊到 Consul。
你可以使用下面的 curl 命令調(diào)用網(wǎng)關(guān)微服務(wù):
$ curl http://localhost:8080/api/books [{"isbn":"1680502395","name":"Release It!","stock":3}, {"isbn":"1491950358","name":"Building Microservices","stock":2}]
恭喜你已經(jīng)創(chuàng)建好了第一個 Micronaut 微服務(wù)網(wǎng)絡(luò)!
在本教程中,你用不同的語言創(chuàng)建了三個微服務(wù):Java、Kotlin 和 Groovy。你還了解了使用 Micronaut HTTP 客戶端消費其他微服務(wù)是多么容易,以及如何創(chuàng)建快速執(zhí)行的功能測試。
此外,你創(chuàng)建的一切都可以利用完全反射無關(guān)的依賴注入和 AOP。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。