Reactive microservices
with Micronaut
Álvaro Sánchez-Mariscal
@alvaro_sanchez
About me
— Developer since 2001.
— Founded Salenda in 2007.
— Grails user since v0.4.
— Working @ OCI since 2015.
— Father since 2017!
!
@alvaro_sanchez
DISCLAIMER
!
Work in progress
Introducing
 Introducing Micronaut
The productivity of Grails with the performance
of a compile-time, non-blocking framework.
— Designed from the ground up with microservices and the
cloud in mind.
— Ultra-lightweight and Reactive (based on Netty)
— Integrated AOP and Compile-Time DI for Java and Groovy.
— HTTP Client & Server.
@alvaro_sanchez
Reactive microservices
— Any Reactive Streams implementation:
— RxJava 2.x.
— Reactor 3.x.
— Akka.
— Plain old java.util.concurrent.CompletableFuture.
@alvaro_sanchez
Micronaut is small and fast!
— JAR files: 8Mb (Java) / 12Mb (Groovy).
— Spring+Groovy: 36MB / Grails: 27Mb.
— Heap size: 7Mb (Java) / 19Mb (Groovy).
— Spring+Groovy: 33Mb / Grails: 49Mb.
— Startup time: ~1 second.
— Spring / Grails: ~3-4 seconds.
@alvaro_sanchez
Features
Inversion of Control
Dependency Injection
Inversion of Control /
Dependency Injection
— Inspired from Spring.
— Compile-time: no reflection, no runtime proxies.
— JSR-330 (javax.inject) or Spring-like annotations.
— Implemented via annotation processors (Java) and
AST transformations (Groovy) that use ASM to
generate bytecode.
@alvaro_sanchez
interface Engine {
String start()
}
@Singleton
class V8Engine implements Engine {
String start() {
"Starting V8"
}
}
@Singleton
class Vehicle {
final Engine engine
@Inject Vehicle(Engine engine) {
this.engine = engine
}
String start() {
engine.start() // "Starting V8"
}
}
@alvaro_sanchez
Implicit injection via constructor
@Singleton
class WeatherService {}
@Controller('/weather')
class WeatherController {
final WeatherService weatherService
WeatherController(WeatherService weatherService) {
this.weatherService = weatherService
}
}
@alvaro_sanchez
Injectable types
— An Optional of a bean. If the bean doesn’t exist
empty() is injected. Alternatively, use @Nullable.
— An Iterable or subtype of Iterable (eg List, Collection
etc).
— A lazy Stream of beans.
— A native array of beans of a given type (Engine[]).
— A javax.inject.Provider if a circular dependency
requires it.
@alvaro_sanchez
Bean qualifiers
@Singleton
class V6Engine implements Engine { ... }
@Singleton
class V8Engine implements Engine { ... }
//Vehicle constructor
@Inject Vehicle(@Named('v8') Engine engine) {
this.engine = engine
}
@alvaro_sanchez
Bean qualifiers
@Qualifier
@Retention(RUNTIME)
@interface V8 {
}
//Vehicle constructor
@Inject Vehicle(@V8 Engine engine) {
this.engine = engine
}
@alvaro_sanchez
Refreshable beans
@Refreshable
class WeatherService {
String forecast
@PostConstruct
void init() {
forecast = "Scattered Clouds ${new Date().format('dd/MMM/yy HH:ss.SSS')}"
}
String latestForecast() {
return forecast
}
}
@alvaro_sanchez
Refreshable beans
@Controller('/weather')
class WeatherController {
@Inject
WeatherService weatherService
...
}
— Refreshable via:
— The /refresh endpoint.
— applicationContext.publishEvent(new RefreshEvent())
@alvaro_sanchez
Refreshing upon config
changes
@Bean(preDestroy = "close")
@Refreshable('mongodb')
MongoClient mongoClient(ReactiveMongoConfiguration rmc) {
return MongoClients.create(rmc.buildSettings())
}
@alvaro_sanchez
 Bean factories
@Singleton
class CrankShaft {}
@Factory
class EngineFactory {
@Bean
@Singleton
Engine v8Engine(CrankShaft crankShaft) {
new V8Engine(crankShaft)
}
}
@alvaro_sanchez
 Conditional beans
@Singleton
@Requires(beans = DataSource)
@Requires(property = "datasource.url")
class JdbcBookService implements BookService {
DataSource dataSource
public JdbcBookService(DataSource dataSource) {
this.dataSource = dataSource
}
}
@alvaro_sanchez
 Conditional beans
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PACKAGE, ElementType.TYPE})
@Requires(beans = DataSource)
@Requires(property = "datasource.url")
@interface RequiresJdbc {
}
@RequiresJdbc
class JdbcBookService implements BookService {
...
}
@alvaro_sanchez
 Supported conditions
— @Requires(classes=javax.servlet.Servlet)
— @Requires(beans=javax.sql.DataSource)
— @Requires(env='TRAVIS')
— @Requires(configuration='foo.bar')
— @Requires(sdk=Sdk.JAVA, value="1.8")
— @Requires(property='consul.enabled', value="true",
defaultValue="false")
@alvaro_sanchez
 Bean configurations
— Grouping of multiple bean definitions within a
package:
//file: src/main/groovy/com/example/json/package-info.groovy
@Configuration
@Requires(classes = com.fasterxml.jackson.databind.ObjectMapper)
package com.example.json
— All the beans from the package can be required with
@Requires(configuration='com.example.json')
@alvaro_sanchez
Life-cycle methods and events
— javax.annotation.PostConstruct is supported.
— For events:
@Singleton
class EngineInitializer implements BeanInitializedEventListener<EngineFactory> {
@Override
EngineFactory onInitialized(BeanInitializingEvent<EngineFactory> event) {
EngineFactory engineFactory = event.bean
engineFactory.rodLength = 6.6
return event.bean
}
}
@alvaro_sanchez
Application configuration
PropertySources
— Command line arguments.
— Properties from SPRING_APPLICATION_JSON (for Spring compatibility).
— Properties from MICRONAUT_APPLICATION_JSON.
— Java System Properties.
— OS environment variables.
— Enviroment-specific properties from application-{environment}.
{extension}.
— (Either .properties, .json, .yml or .groovy formats supported)
— Application-specific properties from application.{extension}.
@alvaro_sanchez
 Injecting configuration values
@Singleton
class EngineImpl implements Engine {
@Value('${my.engine.cylinders:6}')
int cylinders
}
@alvaro_sanchez
 @EachProperty
@EachProperty("myapp.datasources")
public class DataSourceConfiguration {
final String name
final URI url
}
//src/main/resources/application.properties
myapp.datasources.one = "jdbc:mysql://localhost/one"
myapp.datasources.two = "jdbc:mysql://localhost/two"
@alvaro_sanchez
 @EachBean
@Factory
class DataSourceFactory {
@EachBean(DataSourceConfiguration)
DataSource dataSource(DataSourceConfiguration cfg) {
URI url = cfg.url
return new DataSource(url)
}
}
@alvaro_sanchez
 Type-safe @ConfigurationProperties
@ConfigurationProperties('my.engine')
class EngineConfig {
@NotBlank
String manufacturer = "Ford"
@Min(1L)
int cylinders
CrankShaft crankShaft = new CrankShaft()
@ConfigurationProperties('crank-shaft')
static class CrankShaft {
Optional<Double> rodLength = Optional.empty()
}
}
@alvaro_sanchez
 Type-safe @ConfigurationProperties
#src/main/resources/application.yml
my:
engine:
manufacturer: Subaru
cylinders: 4
crank-shaft:
rodLength: 4
@alvaro_sanchez
 Type-safe @ConfigurationProperties
— Simple injection:
@Singleton
class EngineImpl implements Engine {
final EngineConfig config
EngineImpl(EngineConfig config) {
this.config = config
}
}
@alvaro_sanchez
AOP
Micronaut's AOP
— Around Advice - decorates a method or class.
— Trace logging.
— Hystrix support.
— Cache abstraction.
— Circuit breaker pattern with retry support.
— Validation.
— Introduction Advice - introduces new behaviour to a class.
— HTTP client.
@alvaro_sanchez
Cache abstraction
@Singleton
@CacheConfig("pets")
class PetService {
@Cacheable
Pet findById(Long id) { ... }
@CachePut
void save(Pet pet) { ... }
@CacheInvalidate
void delete(Pet pet) { ... }
}
@alvaro_sanchez
HTTP Server
Hello World
//src/main/groovy/example/Application.groovy
package example
import io.micronaut.runtime.Micronaut
Micronaut.run(getClass())
@alvaro_sanchez
Hello World
//src/main/groovy/example/HelloController.groovy
@Controller("/")
class HelloController {
@Get("/hello/{name}")
String hello(String name) {
return "Hello $name!"
}
}
@alvaro_sanchez
Hello World
class HelloControllerSpec extends Specification {
@Shared @AutoCleanup EmbeddedServer embeddedServer =
ApplicationContext.run(EmbeddedServer)
@Shared @AutoCleanup HttpClient client =
HttpClient.create(embeddedServer.URL)
void "test hello world response"() {
expect:
client.toBlocking()
.retrieve(HttpRequest.GET('/hello/Greach')) == "Hello Greach!"
}
}
@alvaro_sanchez
JUnit with Java test
public class HelloWorldTest {
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 testHelloWorkd() throws Exception {
String body = client.toBlocking().retrieve("/hello/Greach");
assertEquals(body, "Hello Greach!");
}
}
@alvaro_sanchez
Declarative routing
@Get("/hello")
String hello() {
return "Hello World"
}
@Get
String hello() {
return "Hello World"
}
@alvaro_sanchez
Programmatic routing
@Singleton
class MyRoutes extends GroovyRouteBuilder {
MyRoutes(ApplicationContext beanContext) { super(beanContext) }
@Inject
void bookResources(BookController bookController, AuthorController authorController) {
GET(bookController) {
POST("/hello{/message}", bookController.&hello)
}
GET(bookController, ID) {
GET(authorController)
}
}
}
@alvaro_sanchez
Request binding
— Body: String hello(@Body String body)
— Cookies: String hello(@CookieValue String myCookie)
— Headers: String hello(@Header String contentType)
— Parameters: String hello(@Parameter String myParam)
— Special cases:
— String hello(@Header Optional<String> contentType)
— String hello(@Parameter ZonedDateTime date)
@alvaro_sanchez
Direct request/response
manipulation
@Get("/hello")
HttpResponse<String> hello(HttpRequest<?> request) {
String name = request.getParameters()
.getFirst("name")
.orElse("Nobody");
return HttpResponse.ok("Hello " + name + "!!")
.header("X-My-Header", "Foo");
}
@alvaro_sanchez
Reactive request processing
@Post(consumes = MediaType.TEXT_PLAIN)
Single<MutableHttpResponse<String>> echoFlow(@Body Flowable<String> text) {
return text.collect(StringBuffer::new, StringBuffer::append)
.map(buffer ->
HttpResponse.ok(buffer.toString())
);
}
@alvaro_sanchez
Reactive responses
— Any type that implements the
org.reactivestreams.Publisher:
Flowable<String> hello() {}
— A Java's CompletableFuture instance:
CompletableFuture<String> hello() {}
— An io.micronaut.http.HttpResponse and optional
response body:
HttpResponse<Flowable<String>> hello() {}
@alvaro_sanchez
Non-reactive responses
— Any implementation of CharSequence:
String hello()
— Any simple POJO type
Book show()
@alvaro_sanchez
Threading model
The response type determines the thread pool used to
execute the request:
— Non-blocking requests will be executed in the Netty
event loop thread pool.
— Blocking requests will be executed on the I/O thread
pool.
@alvaro_sanchez
Reactive JSON parsing with
Jackson
@Post
public Single<HttpResponse<Person>> save(@Body Single<Person> person) {
return person.map(p -> {
//do something blocking, and then:
return HttpResponse.created(p);
}
);
}
@alvaro_sanchez
Non-reactive JSON parsing
If your method does not do any blocking I/O:
@Post
public HttpResponse<Person> save(@Body Person person) {
//do something, and then:
return HttpResponse.created(person);
}
@alvaro_sanchez
HTTP Client
 Basics
//src/main/groovy/example/HelloController.groovy
@Controller("/")
class HelloController {
@Get("/hello/{name}") String hello(String name) { return "Hello $name!" }
}
//src/test/groovy/example/HelloClient.groovy
@Client('/')
interface HelloClient {
@Get("/hello/{name}") String hello(String name)
}
@alvaro_sanchez
 Testing it
class HelloControllerSpec extends Specification {
@Shared @AutoCleanup EmbeddedServer embeddedServer =
ApplicationContext.run(EmbeddedServer)
void "test hello world"() {
given:
HelloClient client = embeddedServer.applicationContext.getBean(HelloClient)
expect:
client.hello("Fred") == "Hello Fred!"
}
}
@alvaro_sanchez
 Features
— Service discovery aware: Consul and Eureka support.
— Load balancing: round robin, Netflix's Ribbon.
— Reactive: based on the return types.
— Fault tolerant: retry, fallback, circuit breaker.
@alvaro_sanchez
Fallback support
@Validated
interface PetOperations<T extends Pet> {
@Get("/") Single<List<T>> list()
}
@Client(id = "pets", path = "/v1/pets")
interface PetClient extends PetOperations<Pet> {
@Get("/") Single<List<Pet>> findAll()
}
@Fallback
class PetClientFallback implements PetOperations<Pet> {
Single<List<Pet>> list() { Single.just([] as List<Pet>) }
}
@alvaro_sanchez
Retry / circuit breaker support
@Client("/dodgy-api")
@Retryable(attempts = '5', delay = '5ms')
interface ApiClient { ... }
@Singleton
@CircuitBreaker(attempts = '5', delay = '5ms', reset = '300ms')
class MyService { ... }
@alvaro_sanchez
Retryable beans
@Singleton
class Neo4jDriverBuilder {
@Retryable(ServiceUnavailableException)
Driver buildDriver() {
//build driver
}
}
@alvaro_sanchez
Serverless
Simple functions as Groovy scripts
@Field @Inject Twitter twitterClient
@CompileStatic
UpdateResult updateStatus(Message status) {
Status s = twitterClient.updateStatus(status.text)
URL url= new URL("https://twitter.com/$s.user.screenName/status/${s.id}")
return new UpdateResult(url, s.createdAt.time)
}
@alvaro_sanchez
Function beans
import java.util.function.Function
@FunctionBean('book')
class BookFunction implements Function<Book, Book> {
@Override
Book apply(Book book) {
book.title = book.title.toUpperCase()
return book
}
}
@alvaro_sanchez
Function clients
@FunctionClient
interface TweetClient {
Single<Result> updateStatus(String text)
static class Result {
URL url
}
}
@alvaro_sanchez
Testing: directly
void "run function directly"() {
expect:
new HelloWorldFunction()
.hello(new Person(name: "Fred"))
.text == "Hello Fred!"
}
@alvaro_sanchez
Testing: REST
void "run function as REST service"() {
given:
EmbeddedServer server = ApplicationContext.run(EmbeddedServer)
HelloClient client = server.getApplicationContext().getBean(HelloClient)
when:
Message message = client.hello("Fred").blockingGet()
then:
message.text == "Hello Fred!"
}
@alvaro_sanchez
 Testing: AWS Lambda
void "run execute function as lambda"() {
given:
ApplicationContext applicationContext = ApplicationContext.run(
'aws.lambda.functions.hello.functionName':'hello-world',
'aws.lambda.region':'us-east-1'
)
HelloClient client = applicationContext.getBean(HelloClient)
when:
Message message = client.hello("Fred").blockingGet()
then:
message.text == "Hello Fred!"
}
@alvaro_sanchez
Micronaut
Petstore
Petstore architecture
@alvaro_sanchez
Q & A
Álvaro Sánchez-Mariscal
@alvaro_sanchez

Reactive microservices with Micronaut - Greach 2018

  • 1.
    Reactive microservices with Micronaut ÁlvaroSánchez-Mariscal @alvaro_sanchez
  • 2.
    About me — Developersince 2001. — Founded Salenda in 2007. — Grails user since v0.4. — Working @ OCI since 2015. — Father since 2017! ! @alvaro_sanchez
  • 3.
  • 4.
  • 5.
     Introducing Micronaut The productivityof Grails with the performance of a compile-time, non-blocking framework. — Designed from the ground up with microservices and the cloud in mind. — Ultra-lightweight and Reactive (based on Netty) — Integrated AOP and Compile-Time DI for Java and Groovy. — HTTP Client & Server. @alvaro_sanchez
  • 6.
    Reactive microservices — AnyReactive Streams implementation: — RxJava 2.x. — Reactor 3.x. — Akka. — Plain old java.util.concurrent.CompletableFuture. @alvaro_sanchez
  • 7.
    Micronaut is smalland fast! — JAR files: 8Mb (Java) / 12Mb (Groovy). — Spring+Groovy: 36MB / Grails: 27Mb. — Heap size: 7Mb (Java) / 19Mb (Groovy). — Spring+Groovy: 33Mb / Grails: 49Mb. — Startup time: ~1 second. — Spring / Grails: ~3-4 seconds. @alvaro_sanchez
  • 8.
  • 9.
  • 10.
    Inversion of Control/ Dependency Injection — Inspired from Spring. — Compile-time: no reflection, no runtime proxies. — JSR-330 (javax.inject) or Spring-like annotations. — Implemented via annotation processors (Java) and AST transformations (Groovy) that use ASM to generate bytecode. @alvaro_sanchez
  • 11.
    interface Engine { Stringstart() } @Singleton class V8Engine implements Engine { String start() { "Starting V8" } } @Singleton class Vehicle { final Engine engine @Inject Vehicle(Engine engine) { this.engine = engine } String start() { engine.start() // "Starting V8" } } @alvaro_sanchez
  • 12.
    Implicit injection viaconstructor @Singleton class WeatherService {} @Controller('/weather') class WeatherController { final WeatherService weatherService WeatherController(WeatherService weatherService) { this.weatherService = weatherService } } @alvaro_sanchez
  • 13.
    Injectable types — AnOptional of a bean. If the bean doesn’t exist empty() is injected. Alternatively, use @Nullable. — An Iterable or subtype of Iterable (eg List, Collection etc). — A lazy Stream of beans. — A native array of beans of a given type (Engine[]). — A javax.inject.Provider if a circular dependency requires it. @alvaro_sanchez
  • 14.
    Bean qualifiers @Singleton class V6Engineimplements Engine { ... } @Singleton class V8Engine implements Engine { ... } //Vehicle constructor @Inject Vehicle(@Named('v8') Engine engine) { this.engine = engine } @alvaro_sanchez
  • 15.
    Bean qualifiers @Qualifier @Retention(RUNTIME) @interface V8{ } //Vehicle constructor @Inject Vehicle(@V8 Engine engine) { this.engine = engine } @alvaro_sanchez
  • 16.
    Refreshable beans @Refreshable class WeatherService{ String forecast @PostConstruct void init() { forecast = "Scattered Clouds ${new Date().format('dd/MMM/yy HH:ss.SSS')}" } String latestForecast() { return forecast } } @alvaro_sanchez
  • 17.
    Refreshable beans @Controller('/weather') class WeatherController{ @Inject WeatherService weatherService ... } — Refreshable via: — The /refresh endpoint. — applicationContext.publishEvent(new RefreshEvent()) @alvaro_sanchez
  • 18.
    Refreshing upon config changes @Bean(preDestroy= "close") @Refreshable('mongodb') MongoClient mongoClient(ReactiveMongoConfiguration rmc) { return MongoClients.create(rmc.buildSettings()) } @alvaro_sanchez
  • 19.
     Bean factories @Singleton class CrankShaft{} @Factory class EngineFactory { @Bean @Singleton Engine v8Engine(CrankShaft crankShaft) { new V8Engine(crankShaft) } } @alvaro_sanchez
  • 20.
     Conditional beans @Singleton @Requires(beans =DataSource) @Requires(property = "datasource.url") class JdbcBookService implements BookService { DataSource dataSource public JdbcBookService(DataSource dataSource) { this.dataSource = dataSource } } @alvaro_sanchez
  • 21.
     Conditional beans @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PACKAGE, ElementType.TYPE}) @Requires(beans= DataSource) @Requires(property = "datasource.url") @interface RequiresJdbc { } @RequiresJdbc class JdbcBookService implements BookService { ... } @alvaro_sanchez
  • 22.
     Supported conditions — @Requires(classes=javax.servlet.Servlet) —@Requires(beans=javax.sql.DataSource) — @Requires(env='TRAVIS') — @Requires(configuration='foo.bar') — @Requires(sdk=Sdk.JAVA, value="1.8") — @Requires(property='consul.enabled', value="true", defaultValue="false") @alvaro_sanchez
  • 23.
     Bean configurations — Groupingof multiple bean definitions within a package: //file: src/main/groovy/com/example/json/package-info.groovy @Configuration @Requires(classes = com.fasterxml.jackson.databind.ObjectMapper) package com.example.json — All the beans from the package can be required with @Requires(configuration='com.example.json') @alvaro_sanchez
  • 24.
    Life-cycle methods andevents — javax.annotation.PostConstruct is supported. — For events: @Singleton class EngineInitializer implements BeanInitializedEventListener<EngineFactory> { @Override EngineFactory onInitialized(BeanInitializingEvent<EngineFactory> event) { EngineFactory engineFactory = event.bean engineFactory.rodLength = 6.6 return event.bean } } @alvaro_sanchez
  • 25.
  • 26.
    PropertySources — Command linearguments. — Properties from SPRING_APPLICATION_JSON (for Spring compatibility). — Properties from MICRONAUT_APPLICATION_JSON. — Java System Properties. — OS environment variables. — Enviroment-specific properties from application-{environment}. {extension}. — (Either .properties, .json, .yml or .groovy formats supported) — Application-specific properties from application.{extension}. @alvaro_sanchez
  • 27.
     Injecting configuration values @Singleton classEngineImpl implements Engine { @Value('${my.engine.cylinders:6}') int cylinders } @alvaro_sanchez
  • 28.
     @EachProperty @EachProperty("myapp.datasources") public class DataSourceConfiguration{ final String name final URI url } //src/main/resources/application.properties myapp.datasources.one = "jdbc:mysql://localhost/one" myapp.datasources.two = "jdbc:mysql://localhost/two" @alvaro_sanchez
  • 29.
     @EachBean @Factory class DataSourceFactory { @EachBean(DataSourceConfiguration) DataSourcedataSource(DataSourceConfiguration cfg) { URI url = cfg.url return new DataSource(url) } } @alvaro_sanchez
  • 30.
     Type-safe @ConfigurationProperties @ConfigurationProperties('my.engine') class EngineConfig{ @NotBlank String manufacturer = "Ford" @Min(1L) int cylinders CrankShaft crankShaft = new CrankShaft() @ConfigurationProperties('crank-shaft') static class CrankShaft { Optional<Double> rodLength = Optional.empty() } } @alvaro_sanchez
  • 31.
  • 32.
     Type-safe @ConfigurationProperties — Simpleinjection: @Singleton class EngineImpl implements Engine { final EngineConfig config EngineImpl(EngineConfig config) { this.config = config } } @alvaro_sanchez
  • 33.
  • 34.
    Micronaut's AOP — AroundAdvice - decorates a method or class. — Trace logging. — Hystrix support. — Cache abstraction. — Circuit breaker pattern with retry support. — Validation. — Introduction Advice - introduces new behaviour to a class. — HTTP client. @alvaro_sanchez
  • 35.
    Cache abstraction @Singleton @CacheConfig("pets") class PetService{ @Cacheable Pet findById(Long id) { ... } @CachePut void save(Pet pet) { ... } @CacheInvalidate void delete(Pet pet) { ... } } @alvaro_sanchez
  • 36.
  • 37.
    Hello World //src/main/groovy/example/Application.groovy package example importio.micronaut.runtime.Micronaut Micronaut.run(getClass()) @alvaro_sanchez
  • 38.
    Hello World //src/main/groovy/example/HelloController.groovy @Controller("/") class HelloController{ @Get("/hello/{name}") String hello(String name) { return "Hello $name!" } } @alvaro_sanchez
  • 39.
    Hello World class HelloControllerSpecextends Specification { @Shared @AutoCleanup EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer) @Shared @AutoCleanup HttpClient client = HttpClient.create(embeddedServer.URL) void "test hello world response"() { expect: client.toBlocking() .retrieve(HttpRequest.GET('/hello/Greach')) == "Hello Greach!" } } @alvaro_sanchez
  • 40.
    JUnit with Javatest public class HelloWorldTest { 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 testHelloWorkd() throws Exception { String body = client.toBlocking().retrieve("/hello/Greach"); assertEquals(body, "Hello Greach!"); } } @alvaro_sanchez
  • 41.
    Declarative routing @Get("/hello") String hello(){ return "Hello World" } @Get String hello() { return "Hello World" } @alvaro_sanchez
  • 42.
    Programmatic routing @Singleton class MyRoutesextends GroovyRouteBuilder { MyRoutes(ApplicationContext beanContext) { super(beanContext) } @Inject void bookResources(BookController bookController, AuthorController authorController) { GET(bookController) { POST("/hello{/message}", bookController.&hello) } GET(bookController, ID) { GET(authorController) } } } @alvaro_sanchez
  • 43.
    Request binding — Body:String hello(@Body String body) — Cookies: String hello(@CookieValue String myCookie) — Headers: String hello(@Header String contentType) — Parameters: String hello(@Parameter String myParam) — Special cases: — String hello(@Header Optional<String> contentType) — String hello(@Parameter ZonedDateTime date) @alvaro_sanchez
  • 44.
    Direct request/response manipulation @Get("/hello") HttpResponse<String> hello(HttpRequest<?>request) { String name = request.getParameters() .getFirst("name") .orElse("Nobody"); return HttpResponse.ok("Hello " + name + "!!") .header("X-My-Header", "Foo"); } @alvaro_sanchez
  • 45.
    Reactive request processing @Post(consumes= MediaType.TEXT_PLAIN) Single<MutableHttpResponse<String>> echoFlow(@Body Flowable<String> text) { return text.collect(StringBuffer::new, StringBuffer::append) .map(buffer -> HttpResponse.ok(buffer.toString()) ); } @alvaro_sanchez
  • 46.
    Reactive responses — Anytype that implements the org.reactivestreams.Publisher: Flowable<String> hello() {} — A Java's CompletableFuture instance: CompletableFuture<String> hello() {} — An io.micronaut.http.HttpResponse and optional response body: HttpResponse<Flowable<String>> hello() {} @alvaro_sanchez
  • 47.
    Non-reactive responses — Anyimplementation of CharSequence: String hello() — Any simple POJO type Book show() @alvaro_sanchez
  • 48.
    Threading model The responsetype determines the thread pool used to execute the request: — Non-blocking requests will be executed in the Netty event loop thread pool. — Blocking requests will be executed on the I/O thread pool. @alvaro_sanchez
  • 49.
    Reactive JSON parsingwith Jackson @Post public Single<HttpResponse<Person>> save(@Body Single<Person> person) { return person.map(p -> { //do something blocking, and then: return HttpResponse.created(p); } ); } @alvaro_sanchez
  • 50.
    Non-reactive JSON parsing Ifyour method does not do any blocking I/O: @Post public HttpResponse<Person> save(@Body Person person) { //do something, and then: return HttpResponse.created(person); } @alvaro_sanchez
  • 51.
  • 52.
     Basics //src/main/groovy/example/HelloController.groovy @Controller("/") class HelloController { @Get("/hello/{name}")String hello(String name) { return "Hello $name!" } } //src/test/groovy/example/HelloClient.groovy @Client('/') interface HelloClient { @Get("/hello/{name}") String hello(String name) } @alvaro_sanchez
  • 53.
     Testing it class HelloControllerSpecextends Specification { @Shared @AutoCleanup EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer) void "test hello world"() { given: HelloClient client = embeddedServer.applicationContext.getBean(HelloClient) expect: client.hello("Fred") == "Hello Fred!" } } @alvaro_sanchez
  • 54.
     Features — Service discoveryaware: Consul and Eureka support. — Load balancing: round robin, Netflix's Ribbon. — Reactive: based on the return types. — Fault tolerant: retry, fallback, circuit breaker. @alvaro_sanchez
  • 55.
    Fallback support @Validated interface PetOperations<Textends Pet> { @Get("/") Single<List<T>> list() } @Client(id = "pets", path = "/v1/pets") interface PetClient extends PetOperations<Pet> { @Get("/") Single<List<Pet>> findAll() } @Fallback class PetClientFallback implements PetOperations<Pet> { Single<List<Pet>> list() { Single.just([] as List<Pet>) } } @alvaro_sanchez
  • 56.
    Retry / circuitbreaker support @Client("/dodgy-api") @Retryable(attempts = '5', delay = '5ms') interface ApiClient { ... } @Singleton @CircuitBreaker(attempts = '5', delay = '5ms', reset = '300ms') class MyService { ... } @alvaro_sanchez
  • 57.
    Retryable beans @Singleton class Neo4jDriverBuilder{ @Retryable(ServiceUnavailableException) Driver buildDriver() { //build driver } } @alvaro_sanchez
  • 58.
  • 59.
    Simple functions asGroovy scripts @Field @Inject Twitter twitterClient @CompileStatic UpdateResult updateStatus(Message status) { Status s = twitterClient.updateStatus(status.text) URL url= new URL("https://twitter.com/$s.user.screenName/status/${s.id}") return new UpdateResult(url, s.createdAt.time) } @alvaro_sanchez
  • 60.
    Function beans import java.util.function.Function @FunctionBean('book') classBookFunction implements Function<Book, Book> { @Override Book apply(Book book) { book.title = book.title.toUpperCase() return book } } @alvaro_sanchez
  • 61.
    Function clients @FunctionClient interface TweetClient{ Single<Result> updateStatus(String text) static class Result { URL url } } @alvaro_sanchez
  • 62.
    Testing: directly void "runfunction directly"() { expect: new HelloWorldFunction() .hello(new Person(name: "Fred")) .text == "Hello Fred!" } @alvaro_sanchez
  • 63.
    Testing: REST void "runfunction as REST service"() { given: EmbeddedServer server = ApplicationContext.run(EmbeddedServer) HelloClient client = server.getApplicationContext().getBean(HelloClient) when: Message message = client.hello("Fred").blockingGet() then: message.text == "Hello Fred!" } @alvaro_sanchez
  • 64.
     Testing: AWS Lambda void"run execute function as lambda"() { given: ApplicationContext applicationContext = ApplicationContext.run( 'aws.lambda.functions.hello.functionName':'hello-world', 'aws.lambda.region':'us-east-1' ) HelloClient client = applicationContext.getBean(HelloClient) when: Message message = client.hello("Fred").blockingGet() then: message.text == "Hello Fred!" } @alvaro_sanchez
  • 65.
  • 66.
  • 67.
    Q & A ÁlvaroSánchez-Mariscal @alvaro_sanchez