Micronaut

Vladimír Oraný

Test Facilitator @ Agorapulse

@musketyr

 

http://micronaut.io/

 

Agenda

  • Introduction
  • Demo
    • Dependency Injection
    • HTTP Server
    • Testing
    • HTTP Client
    • Performance Comparison
    • Functions
  • Summary

The Team

Demo

New Microservice

curl -s https://get.sdkman.io | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk install micronaut 1.0.0.RC2
sdk use micronaut 1.0.0.RC2
mn --version
mn create-app --features spock,graal-native-image cloud-winterboots-product
cd cloud-winterboots-product
mn create-bean ProductService
mn create-controller Product

IDE Support

Product Service

package cloud.winterboots.product;

import java.util.Arrays;
import java.util.List;

@javax.inject.Singleton
public class ProductService {

    List<Product> getAll() {
        return Arrays.asList(new Product("Hucule"), new Product("Superboots"));
    }

}
package cloud.winterboots.product;

public class Product {

    private final String name;

    @java.beans.ConstructorProperties({ "name" })
    public Product(String name) { this.name = name; }

    public String getName() { return name; }

    // equals, hashCode, toString
}

Product Controller

package cloud.winterboots.product;

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;

import java.util.List;

@Controller("/product")
public class ProductController {

    private final ProductService productService;

    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    @Get("/")
    public List<Product> index() {
        return productService.getAll();
    }
}

Product Controller Spec

package cloud.winterboots.product

// imports

class ProductControllerSpec extends Specification {

    @Shared @AutoCleanup EmbeddedServer embeddedServer =
            ApplicationContext.run(EmbeddedServer)
    @Shared @AutoCleanup RxHttpClient client =
            embeddedServer.applicationContext.createBean(RxHttpClient, embeddedServer.getURL())


    void "test index"() {
        given:
            HttpResponse<List<Product>> response =
                    client.toBlocking().exchange(
                            HttpRequest.GET("/product"),
                            Argument.of(List, Product)
                    )

        expect:
            response.status == HttpStatus.OK
            response.body()
            response.body().size() == 2
            response.body().get(0).name == 'Hucule'
    }
}

AoT Compilation

Product Client

mn create-client Product
package cloud.winterboots.product;

import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.annotation.Client;
import io.reactivex.Flowable;

@Client("/product")
public interface ProductClient {

    @Get("/")
    Flowable<Product> index();
}

Product Controller Spec II

package cloud.winterboots.product

import io.micronaut.context.ApplicationContext
import io.micronaut.runtime.server.EmbeddedServer
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

class ProductControllerSpec extends Specification {

    @Shared @AutoCleanup EmbeddedServer embeddedServer =
            ApplicationContext.run(EmbeddedServer)

    void "test index"() {
        given:
            ProductClient client =
                    embeddedServer.applicationContext.createBean(ProductClient)
            List<Product> products =
                    client.index().toList().blockingGet()

        expect:
            products
            products.size() == 2
            products.get(0).name == 'Hucule'
    }
}

Running the Microservice

./gradlew run
# src/test/http/product.http

GET http://localhost:8080/product
Accept: application/json

Running the Microservice

GraalVM

sdk install java 1.0.0-rc7-graal 
sdk use java 1.0.0-rc7-graal 
./build-native-image.sh
./cloud-winterboots-product 

GraalVM + Docker

FROM oracle/graalvm-ce:1.0.0-rc7

EXPOSE 8080

COPY build/libs/*-all.jar cloud-winterboots-product.jar
COPY build/reflect.json reflect.json

RUN java -cp cloud-winterboots-product.jar io.micronaut.graal.reflect.GraalClassLoadingAnalyzer
RUN native-image --no-server \
             --class-path cloud-winterboots-product.jar \
			 -H:ReflectionConfigurationFiles=reflect.json \
			 -H:EnableURLProtocols=http \
			 -H:IncludeResources="logback.xml|application.yml|META-INF/services/*.*" \
			 -H:Name=cloud-winterboots-product \
			 -H:Class=cloud.winterboots.product.Application \
			 -H:+ReportUnsupportedElementsAtRuntime \
			 -H:+AllowVMInspection \
			 --rerun-class-initialization-at-runtime='sun.security.jca.JCAUtil$CachedSecureRandomHolder,javax.net.ssl.SSLContext' \
			 --delay-class-initialization-to-runtime=io.netty.handler.codec.http.HttpObjectEncoder,io.netty.handler.codec.http.websocketx.WebSocket00FrameEncoder,io.netty.handler.ssl.util.ThreadLocalInsecureRandom


CMD ./cloud-winterboots-product
./gradlew assemble
docker build --tag="cloud-winterboots-product" .
docker run -d -p 8080:8080 cloud-winterboots-product

Management

// build.gradle
dependencies {
    compile "io.micronaut:management"
    compile "io.micronaut.configuration:micrometer-core"
}
# src/main/resources/application.yml
---
endpoints:
  metrics:
    sensitive: false

Management

GET http://localhost:8080/health
Accept: application/json

###
GET http://localhost:8080/metrics
Accept: application/json

###
GET http://localhost:8080/metrics/jvm.memory.committed
Accept: application/json

###
GET http://localhost:8080/metrics/jvm.memory.used
Accept: application/json

###
GET http://localhost:8080/routes
Accept: application/json

###
GET http://localhost:8080/loggers
Accept: application/json

###
GET http://localhost:8080/beans
Accept: application/json

###
GET http://localhost:8080/info
Accept: application/json

Micronaut vs Spring Boot

cd ..
curl -L https://github.com/musketyr/lusk/releases/download/0.2.3/lusk.tar | tar -xvf -
lusk/bin/lusk cloud-winterboots-product -c 100
cd cloud-winterboots-product
./gradlew run

cd ..
curl https://start.spring.io/starter.tgz -d dependencies=web \
    -d type=gradle-project -d baseDir=boot-sample-app | tar -xzvf -
lusk/bin/lusk boot-sample-app -c 100
cd boot-sample-app
./gradlew bootRun
./gradlew cleanTest test --tests HttpSpec

Functions

cd ..
mn create-function cloud-winterboots-scraper
cd cloud-winterboots-scraper
# build.gradle
dependencies {
    compile "io.micronaut:micronaut-http-client"
}

Http Client

package cloud.winterboots.scraper;

import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.annotation.Client;

import java.util.List;

@Client("https://private-046fd-winterboots.apiary-mock.com")
public interface ProductClient {

    @Get("/product")
    public List<Product> index();

}

Functions

cd ..
mn create-function --features=spock cloud-winterboots-scraper
cd cloud-winterboots-scraper
// build.gradle
dependencies {
    compile "io.micronaut:micronaut-http-client"
}

Mocking in the Tests

@FunctionBean("cloud-winterboots-scraper")
public class CloudWinterbootsScraperFunction implements Supplier<String> {

    private final ProductApi client;

    public CloudWinterbootsScraperFunction(ProductApi client) {
        this.client = client;
    }

    @Override public String get() {
        return client.index().stream().map(Product::getName).collect(Collectors.joining(", "));
    }
}
public interface ProductApi {

    @Get("/product") List<Product> index();

}
@Client("https://private-046fd-winterboots.apiary-mock.com")
public interface ProductClient extends ProductApi { }

Mocking in the Tests

@Primary
@Requires(env = Environment.TEST)
public class MockProductApi implements ProductApi {

    @Override
    public List<Product> index() {
        return Arrays.asList(new Product("Superhucule"), new Product("Spring Boots"));
    }
}

Deployment

./gradlew deploy

Invocation

// build.gradle
task invoke(type: jp.classmethod.aws.gradle.lambda.AWSLambdaInvokeTask) {
    functionName = "cloud-winterboots-scraper"
    invocationType =  com.amazonaws.services.lambda.model.InvocationType.RequestResponse
    doLast {
        println "Lambda function result: " + new String(invokeResult.payload.array(), "UTF-8")
    }
}

Micronaut Guides

Summary

  • Dependency Injection and Inversion of Control (IoC)
  • Sensible Defaults and Auto-Configuration
  • Configuration and Configuration Sharing
  • Service Discovery
  • HTTP Routing
  • HTTP Client with client-side load-balancing
  • Fast startup time
  • Reduced memory footprint
  • Minimal use of reflection
  • Minimal use of proxies
  • Easy unit testing
  • Ready to use (GA next week at Oracle Code One)
  • 23 guides already and growing
  • APL 2.0 + commercial support from OCI

 

Thank You

Micronaut - as small as it gets

By musketyr

Micronaut - as small as it gets

  • 1,817