From 206b53b7ea0551701e122263277cdf6d9eb45eab Mon Sep 17 00:00:00 2001 From: Rowell Belen Date: Mon, 27 Mar 2017 08:36:41 -0600 Subject: [PATCH 1/4] Vert.x Handlers --- build.gradle | 4 +- loadTest.groovy | 10 +++++ .../com/serverless/LambdaHandler.groovy | 20 ++++++++-- .../com/serverless/handler/CreateUser.groovy | 30 ++++++++------- .../handler/FindUsersByLastName.groovy | 17 +++++---- .../com/serverless/handler/GetUsers.groovy | 15 ++++---- .../com/serverless/handler/Handler.groovy | 9 ----- .../serverless/handler/MessageHandler.groovy | 10 +++++ .../com/serverless/lambda/Request.groovy | 18 +++++---- .../com/serverless/lambda/Response.groovy | 3 +- .../serverless/persistence/entity/User.groovy | 2 +- .../service/DispatcherService.groovy | 35 ----------------- .../groovy/com/serverless/trait/ToJson.groovy | 11 ++++++ .../com/serverless/vertx/RequestCodec.groovy | 38 +++++++++++++++++++ .../com/serverless/vertx/ResponseCodec.groovy | 38 +++++++++++++++++++ .../com/serverless/vertx/VertxConfig.groovy | 31 +++++++++++++++ .../com/serverless/LambdaHandlerTest.groovy | 33 ++++++++++++++++ 17 files changed, 236 insertions(+), 88 deletions(-) create mode 100644 loadTest.groovy delete mode 100644 src/main/groovy/com/serverless/handler/Handler.groovy create mode 100644 src/main/groovy/com/serverless/handler/MessageHandler.groovy delete mode 100644 src/main/groovy/com/serverless/service/DispatcherService.groovy create mode 100644 src/main/groovy/com/serverless/trait/ToJson.groovy create mode 100644 src/main/groovy/com/serverless/vertx/RequestCodec.groovy create mode 100644 src/main/groovy/com/serverless/vertx/ResponseCodec.groovy create mode 100644 src/main/groovy/com/serverless/vertx/VertxConfig.groovy create mode 100644 src/test/java/com/serverless/LambdaHandlerTest.groovy diff --git a/build.gradle b/build.gradle index 55a7605..52e3a25 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,9 @@ dependencies { 'org.codehaus.groovy:groovy-all:2.4.7', 'com.amazonaws:aws-lambda-java-core:1.1.0', 'com.amazonaws:aws-lambda-java-log4j:1.0.0', - 'com.h2database:h2' + 'com.h2database:h2', + 'io.vertx:vertx-core:3.4.1', + 'io.vertx:vertx-lang-groovy:3.4.1' ) // Use JUnit test framework diff --git a/loadTest.groovy b/loadTest.groovy new file mode 100644 index 0000000..bd131fc --- /dev/null +++ b/loadTest.groovy @@ -0,0 +1,10 @@ +@Grab(group='org.codehaus.gpars', module='gpars', version='1.2.1') +import static groovyx.gpars.GParsPool.withPool + +withPool(50){ + (1..2000).eachParallel { i -> + def beginTime = System.currentTimeMillis() + def result = 'https://dncpcllkd0.execute-api.us-east-1.amazonaws.com/dev/users'.toURL().text + println "${i}: ${System.currentTimeMillis() - beginTime}" + } +} \ No newline at end of file diff --git a/src/main/groovy/com/serverless/LambdaHandler.groovy b/src/main/groovy/com/serverless/LambdaHandler.groovy index 934d90a..06476ec 100644 --- a/src/main/groovy/com/serverless/LambdaHandler.groovy +++ b/src/main/groovy/com/serverless/LambdaHandler.groovy @@ -2,16 +2,19 @@ package com.serverless import com.amazonaws.services.lambda.runtime.Context import com.amazonaws.services.lambda.runtime.RequestHandler -import com.serverless.service.DispatcherService import com.serverless.lambda.Request import com.serverless.lambda.Response import groovy.transform.Memoized import groovy.util.logging.Log4j +import io.vertx.core.eventbus.EventBus import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.context.ApplicationContext import org.springframework.context.annotation.ComponentScan +import java.util.concurrent.CompletableFuture +import java.util.concurrent.TimeUnit + @SpringBootApplication @ComponentScan(basePackages = "com.serverless") @Log4j @@ -23,13 +26,22 @@ class LambdaHandler implements RequestHandler { @Memoized ApplicationContext getApplicationContext(String[] args = []) { + System.setProperty('vertx.disableFileCPResolving', 'true') return SpringApplication.run(LambdaHandler.class, args) } @Override Response handleRequest(Map input, Context context) { - final Request request = new Request(input, context) - DispatcherService dispatcher = getApplicationContext().getBean(DispatcherService.class) - return dispatcher.dispatch(request) + final Request request = new Request(context.awsRequestId, input) + def eventBus = applicationContext.getBean(EventBus.class) + CompletableFuture future = new CompletableFuture<>() + eventBus.send(request.methodResource(), request, { ar -> + if (ar.succeeded()) { + assert ar.result().body() instanceof Response + log.info "Received reply: ${ar.result().body()}" + future.complete(ar.result().body()) + } + }) + future.get(5, TimeUnit.SECONDS) } } diff --git a/src/main/groovy/com/serverless/handler/CreateUser.groovy b/src/main/groovy/com/serverless/handler/CreateUser.groovy index eb4f254..8e634e2 100644 --- a/src/main/groovy/com/serverless/handler/CreateUser.groovy +++ b/src/main/groovy/com/serverless/handler/CreateUser.groovy @@ -5,8 +5,8 @@ import com.serverless.lambda.Response import com.serverless.persistence.entity.User import com.serverless.persistence.repository.UserRepository import groovy.json.JsonOutput -import groovy.transform.CompileStatic import groovy.util.logging.Log4j +import io.vertx.core.eventbus.Message import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component @@ -14,33 +14,35 @@ import javax.annotation.PostConstruct @Component @Log4j -@CompileStatic -class CreateUser implements Handler { +class CreateUser implements MessageHandler { @Autowired private UserRepository userRepository + @PostConstruct + void init() { + // Sample Data + userRepository.save(new User('Michael', 'Jordan')) + userRepository.save(new User('Scottie', 'Pippen')) + userRepository.save(new User('Phil', 'Jackson')) + } + @Override - boolean route(Request request) { - request.resourcePath() == '/users/create' && request.httpMethod() == 'POST' + String getRouteChannel() { + return 'POST:/users/create' } @Override - Response respond(final Request request) { + void handle(final Message message) { + final Request request = message.body() def firstName = request.queryString('firstName') def lastName = request.queryString('lastName') def user = new User(firstName, lastName) userRepository.save(user) - Response.builder() + def response = Response.builder() .statusCode(201) .body(JsonOutput.prettyPrint(JsonOutput.toJson(user))) .build() - } - - @PostConstruct - void init() { - userRepository.save(new User('Michael', 'Jordan')) - userRepository.save(new User('Scottie', 'Pippen')) - userRepository.save(new User('Phil', 'Jackson')) + message.reply(response) } } diff --git a/src/main/groovy/com/serverless/handler/FindUsersByLastName.groovy b/src/main/groovy/com/serverless/handler/FindUsersByLastName.groovy index 7b030fa..15b4567 100644 --- a/src/main/groovy/com/serverless/handler/FindUsersByLastName.groovy +++ b/src/main/groovy/com/serverless/handler/FindUsersByLastName.groovy @@ -1,34 +1,35 @@ package com.serverless.handler - import com.serverless.lambda.Request import com.serverless.lambda.Response import com.serverless.persistence.repository.UserRepository import groovy.json.JsonOutput -import groovy.transform.CompileStatic import groovy.util.logging.Log4j + +import io.vertx.core.eventbus.Message import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component @Component @Log4j -@CompileStatic -class FindUsersByLastName implements Handler { +class FindUsersByLastName implements MessageHandler { @Autowired private UserRepository userRepository @Override - boolean route(final Request request) { - request.resourcePath() == '/users/{lastName}' && request.httpMethod() == 'GET' + String getRouteChannel() { + return 'GET:/users/{lastName}' } @Override - Response respond(final Request request) { + void handle(final Message message) { + def request = message.body() def customers = userRepository.findByLastName(request.pathParameter('lastName')) - Response.builder() + def response = Response.builder() .statusCode(200) .body(JsonOutput.prettyPrint(JsonOutput.toJson(customers))) .build() + message.reply(response) } } diff --git a/src/main/groovy/com/serverless/handler/GetUsers.groovy b/src/main/groovy/com/serverless/handler/GetUsers.groovy index 5ca07a0..8b71168 100644 --- a/src/main/groovy/com/serverless/handler/GetUsers.groovy +++ b/src/main/groovy/com/serverless/handler/GetUsers.groovy @@ -1,34 +1,33 @@ package com.serverless.handler - import com.serverless.lambda.Request import com.serverless.lambda.Response import com.serverless.persistence.repository.UserRepository import groovy.json.JsonOutput -import groovy.transform.CompileStatic import groovy.util.logging.Log4j +import io.vertx.core.eventbus.Message import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component @Component @Log4j -@CompileStatic -class GetUsers implements Handler { +class GetUsers implements MessageHandler { @Autowired private UserRepository userRepository @Override - boolean route(final Request request) { - request.resourcePath() == '/users' && request.httpMethod() == 'GET' + String getRouteChannel() { + return 'GET:/users' } @Override - Response respond(final Request request) { + void handle(final Message message) { def allCustomers = userRepository.findAll() - Response.builder() + def response = Response.builder() .statusCode(200) .body(JsonOutput.prettyPrint(JsonOutput.toJson(allCustomers))) .build() + message.reply(response) } } diff --git a/src/main/groovy/com/serverless/handler/Handler.groovy b/src/main/groovy/com/serverless/handler/Handler.groovy deleted file mode 100644 index 55d465d..0000000 --- a/src/main/groovy/com/serverless/handler/Handler.groovy +++ /dev/null @@ -1,9 +0,0 @@ -package com.serverless.handler - -import com.serverless.lambda.Request -import com.serverless.lambda.Response - -interface Handler { - boolean route(Request request) - Response respond(Request request) -} \ No newline at end of file diff --git a/src/main/groovy/com/serverless/handler/MessageHandler.groovy b/src/main/groovy/com/serverless/handler/MessageHandler.groovy new file mode 100644 index 0000000..b97d00d --- /dev/null +++ b/src/main/groovy/com/serverless/handler/MessageHandler.groovy @@ -0,0 +1,10 @@ +package com.serverless.handler + +import com.serverless.lambda.Request +import io.vertx.core.Handler +import io.vertx.core.eventbus.Message + + +interface MessageHandler extends Handler> { + String getRouteChannel() +} \ No newline at end of file diff --git a/src/main/groovy/com/serverless/lambda/Request.groovy b/src/main/groovy/com/serverless/lambda/Request.groovy index 9fad90c..26ea2b9 100644 --- a/src/main/groovy/com/serverless/lambda/Request.groovy +++ b/src/main/groovy/com/serverless/lambda/Request.groovy @@ -1,20 +1,20 @@ package com.serverless.lambda -import com.amazonaws.services.lambda.runtime.Context +import com.serverless.trait.ToJson import groovy.transform.ToString @ToString(includePackage = false) -class Request { - private final Map input - private final Context context +class Request implements ToJson { + final Map input + final String requestId - Request(final Map input, final Context context) { + Request(final String requestId, final Map input) { this.input = input - this.context = context + this.requestId = requestId } String requestId() { - context?.awsRequestId + requestId } String resourcePath() { @@ -32,4 +32,8 @@ class Request { String pathParameter(String name) { input?.pathParameters?."${name}"?.trim() } + + String methodResource() { + "${httpMethod()}:${resourcePath()}" + } } diff --git a/src/main/groovy/com/serverless/lambda/Response.groovy b/src/main/groovy/com/serverless/lambda/Response.groovy index ba26ea3..1a0c466 100644 --- a/src/main/groovy/com/serverless/lambda/Response.groovy +++ b/src/main/groovy/com/serverless/lambda/Response.groovy @@ -1,5 +1,6 @@ package com.serverless.lambda +import com.serverless.trait.ToJson import groovy.transform.CompileStatic import groovy.transform.ToString import groovy.transform.builder.Builder @@ -7,7 +8,7 @@ import groovy.transform.builder.Builder @Builder @ToString(includePackage = false) @CompileStatic -class Response { +class Response implements ToJson { int statusCode String body private Map headers = [:] diff --git a/src/main/groovy/com/serverless/persistence/entity/User.groovy b/src/main/groovy/com/serverless/persistence/entity/User.groovy index adc190e..27994f8 100644 --- a/src/main/groovy/com/serverless/persistence/entity/User.groovy +++ b/src/main/groovy/com/serverless/persistence/entity/User.groovy @@ -19,7 +19,7 @@ class User { String firstName String lastName - User(){} + User() {} User(String firstName, String lastName) { this.firstName = firstName diff --git a/src/main/groovy/com/serverless/service/DispatcherService.groovy b/src/main/groovy/com/serverless/service/DispatcherService.groovy deleted file mode 100644 index 5d7db60..0000000 --- a/src/main/groovy/com/serverless/service/DispatcherService.groovy +++ /dev/null @@ -1,35 +0,0 @@ -package com.serverless.service - - -import com.serverless.handler.Handler -import com.serverless.lambda.Request -import com.serverless.lambda.Response -import groovy.transform.CompileStatic -import groovy.util.logging.Log4j -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.stereotype.Component - -@Component -@CompileStatic -@Log4j -class DispatcherService { - - @Autowired - List handlers - - Response dispatch(Request request) { - log.info("request: ${request}") - try { - def handlers = handlers?.findAll { it.route(request) } - if (handlers?.size() != 1) { - throw new RuntimeException('Unable to resolve unique handler for route/request') - } - final Response response = handlers.first().respond(request) - response.addHeader('X-Request-Id', request.requestId()) - log.info("response: ${response}") - return response - } catch (e) { - return Response.builder().statusCode(500).body(e.getMessage()).build() - } - } -} diff --git a/src/main/groovy/com/serverless/trait/ToJson.groovy b/src/main/groovy/com/serverless/trait/ToJson.groovy new file mode 100644 index 0000000..d507b1f --- /dev/null +++ b/src/main/groovy/com/serverless/trait/ToJson.groovy @@ -0,0 +1,11 @@ +package com.serverless.trait + +import groovy.json.JsonOutput +import groovy.transform.CompileStatic + +@CompileStatic +trait ToJson { + String asJson() { + return JsonOutput.prettyPrint(JsonOutput.toJson(this)) + } +} \ No newline at end of file diff --git a/src/main/groovy/com/serverless/vertx/RequestCodec.groovy b/src/main/groovy/com/serverless/vertx/RequestCodec.groovy new file mode 100644 index 0000000..4184178 --- /dev/null +++ b/src/main/groovy/com/serverless/vertx/RequestCodec.groovy @@ -0,0 +1,38 @@ +package com.serverless.vertx + +import com.serverless.lambda.Request +import groovy.json.JsonSlurper +import io.vertx.core.buffer.Buffer +import io.vertx.core.eventbus.MessageCodec +import org.springframework.stereotype.Component + +@Component +class RequestCodec implements MessageCodec { + + @Override + void encodeToWire(Buffer buffer, Request request) { + buffer.appendString(request.asJson()) + } + + @Override + Request decodeFromWire(int pos, Buffer buffer) { + String jsonString = buffer.getString(0, buffer.length()) + Map json = JsonSlurper.newInstance().parseText(jsonString) + return new Request(json.requestId, json.input) + } + + @Override + Request transform(Request request) { + request + } + + @Override + String name() { + this.getClass().getSimpleName() + } + + @Override + byte systemCodecID() { + -1 + } +} diff --git a/src/main/groovy/com/serverless/vertx/ResponseCodec.groovy b/src/main/groovy/com/serverless/vertx/ResponseCodec.groovy new file mode 100644 index 0000000..2f64aa2 --- /dev/null +++ b/src/main/groovy/com/serverless/vertx/ResponseCodec.groovy @@ -0,0 +1,38 @@ +package com.serverless.vertx + +import com.serverless.lambda.Response +import groovy.json.JsonSlurper +import io.vertx.core.buffer.Buffer +import io.vertx.core.eventbus.MessageCodec +import org.springframework.stereotype.Component + +@Component +class ResponseCodec implements MessageCodec { + + @Override + void encodeToWire(Buffer buffer, Response response) { + buffer.appendString(response.asJson()) + } + + @Override + Response decodeFromWire(int pos, Buffer buffer) { + String jsonString = buffer.getString(0, buffer.length()) + Map json = JsonSlurper.newInstance().parseText(jsonString) + Response.builder().statusCode(json?.statusCode).body(json.body).headers(json.headers).build() + } + + @Override + Response transform(Response response) { + response + } + + @Override + String name() { + this.getClass().getSimpleName() + } + + @Override + byte systemCodecID() { + return -1 + } +} diff --git a/src/main/groovy/com/serverless/vertx/VertxConfig.groovy b/src/main/groovy/com/serverless/vertx/VertxConfig.groovy new file mode 100644 index 0000000..f0bb5b1 --- /dev/null +++ b/src/main/groovy/com/serverless/vertx/VertxConfig.groovy @@ -0,0 +1,31 @@ +package com.serverless.vertx + +import com.serverless.handler.MessageHandler +import com.serverless.lambda.Request +import com.serverless.lambda.Response +import io.vertx.core.Vertx +import io.vertx.core.eventbus.EventBus +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class VertxConfig { + + @Bean + EventBus eventBus(final List handlers, + final RequestCodec requestCodec, + final ResponseCodec responseCodec) { + def eventBus = Vertx.vertx().eventBus() + + // Register Codecs + eventBus.registerDefaultCodec(Request.class, requestCodec) + eventBus.registerDefaultCodec(Response.class, responseCodec) + + // Register Handlers + handlers?.each { + eventBus.consumer(it.getRouteChannel(), it) + } + + eventBus + } +} diff --git a/src/test/java/com/serverless/LambdaHandlerTest.groovy b/src/test/java/com/serverless/LambdaHandlerTest.groovy new file mode 100644 index 0000000..950a763 --- /dev/null +++ b/src/test/java/com/serverless/LambdaHandlerTest.groovy @@ -0,0 +1,33 @@ +package com.serverless + +import com.amazonaws.services.lambda.runtime.Context +import groovy.util.logging.Log4j +import org.junit.Test +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.context.annotation.ComponentScan + +@SpringBootApplication +@ComponentScan(basePackages = "com.serverless") +@Log4j +class LambdaHandlerTest { + @Test + void handleRequest() throws Exception { + + LambdaHandler mainHandler = new LambdaHandler() + + def input + def context = {} as Context + context.metaClass.getAwsRequestId = { UUID.randomUUID().toString() } + + + input = [httpMethod: 'GET', resource: '/users/{lastName}', pathParameters: [lastName: 'Jackson']] + mainHandler.handleRequest(input, context) + + input = [httpMethod : 'POST', resource: '/users/create', + queryStringParameters: [firstName: 'Rowell', lastName: 'Belen']] + mainHandler.handleRequest(input, context) + + input = [httpMethod: 'GET', resource: '/users'] + mainHandler.handleRequest(input, context) + } +} \ No newline at end of file From 1398dddf264756750d9ef67d042f0e6ac7d36997 Mon Sep 17 00:00:00 2001 From: Rowell Belen Date: Mon, 27 Mar 2017 21:58:16 -0600 Subject: [PATCH 2/4] Remove load script --- loadTest.groovy | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 loadTest.groovy diff --git a/loadTest.groovy b/loadTest.groovy deleted file mode 100644 index bd131fc..0000000 --- a/loadTest.groovy +++ /dev/null @@ -1,10 +0,0 @@ -@Grab(group='org.codehaus.gpars', module='gpars', version='1.2.1') -import static groovyx.gpars.GParsPool.withPool - -withPool(50){ - (1..2000).eachParallel { i -> - def beginTime = System.currentTimeMillis() - def result = 'https://dncpcllkd0.execute-api.us-east-1.amazonaws.com/dev/users'.toURL().text - println "${i}: ${System.currentTimeMillis() - beginTime}" - } -} \ No newline at end of file From 3844ada40c89ddb36a9ba8a307e0c8d33545fbc4 Mon Sep 17 00:00:00 2001 From: Rowell Belen Date: Wed, 29 Mar 2017 00:06:22 -0600 Subject: [PATCH 3/4] Vert.x Standalone --- build.gradle | 10 +--- serverless.yml | 2 +- .../com/serverless/LambdaHandler.groovy | 47 ------------------ .../groovy/com/serverless/VertxHandler.groovy | 48 +++++++++++++++++++ .../com/serverless/handler/CreateUser.groovy | 48 ------------------- .../handler/FindUsersByLastName.groovy | 35 -------------- .../com/serverless/handler/GetUsers.groovy | 33 ------------- .../serverless/handler/MessageHandler.groovy | 10 ---- .../com/serverless/lambda/Request.groovy | 39 --------------- .../com/serverless/lambda/Response.groovy | 19 -------- .../serverless/persistence/entity/User.groovy | 28 ----------- .../repository/UserRepository.groovy | 11 ----- .../groovy/com/serverless/trait/ToJson.groovy | 11 ----- .../com/serverless/vertx/RequestCodec.groovy | 38 --------------- .../com/serverless/vertx/ResponseCodec.groovy | 38 --------------- .../com/serverless/vertx/VertxConfig.groovy | 31 ------------ .../com/serverless/LambdaHandlerTest.groovy | 33 ------------- 17 files changed, 50 insertions(+), 431 deletions(-) delete mode 100644 src/main/groovy/com/serverless/LambdaHandler.groovy create mode 100644 src/main/groovy/com/serverless/VertxHandler.groovy delete mode 100644 src/main/groovy/com/serverless/handler/CreateUser.groovy delete mode 100644 src/main/groovy/com/serverless/handler/FindUsersByLastName.groovy delete mode 100644 src/main/groovy/com/serverless/handler/GetUsers.groovy delete mode 100644 src/main/groovy/com/serverless/handler/MessageHandler.groovy delete mode 100644 src/main/groovy/com/serverless/lambda/Request.groovy delete mode 100644 src/main/groovy/com/serverless/lambda/Response.groovy delete mode 100644 src/main/groovy/com/serverless/persistence/entity/User.groovy delete mode 100644 src/main/groovy/com/serverless/persistence/repository/UserRepository.groovy delete mode 100644 src/main/groovy/com/serverless/trait/ToJson.groovy delete mode 100644 src/main/groovy/com/serverless/vertx/RequestCodec.groovy delete mode 100644 src/main/groovy/com/serverless/vertx/ResponseCodec.groovy delete mode 100644 src/main/groovy/com/serverless/vertx/VertxConfig.groovy delete mode 100644 src/test/java/com/serverless/LambdaHandlerTest.groovy diff --git a/build.gradle b/build.gradle index 52e3a25..774a742 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,3 @@ - -plugins { - id 'org.springframework.boot' version '1.5.2.RELEASE' -} - apply plugin: 'groovy' repositories { @@ -11,13 +6,10 @@ repositories { } dependencies { - compile ( - 'org.springframework.boot:spring-boot-starter', - 'org.springframework.boot:spring-boot-starter-data-jpa', + compile( 'org.codehaus.groovy:groovy-all:2.4.7', 'com.amazonaws:aws-lambda-java-core:1.1.0', 'com.amazonaws:aws-lambda-java-log4j:1.0.0', - 'com.h2database:h2', 'io.vertx:vertx-core:3.4.1', 'io.vertx:vertx-lang-groovy:3.4.1' ) diff --git a/serverless.yml b/serverless.yml index c63e928..ff900fb 100644 --- a/serverless.yml +++ b/serverless.yml @@ -11,7 +11,7 @@ package: functions: users: - handler: com.serverless.LambdaHandler + handler: com.serverless.VertxHandler events: - http: path: users diff --git a/src/main/groovy/com/serverless/LambdaHandler.groovy b/src/main/groovy/com/serverless/LambdaHandler.groovy deleted file mode 100644 index 06476ec..0000000 --- a/src/main/groovy/com/serverless/LambdaHandler.groovy +++ /dev/null @@ -1,47 +0,0 @@ -package com.serverless - -import com.amazonaws.services.lambda.runtime.Context -import com.amazonaws.services.lambda.runtime.RequestHandler -import com.serverless.lambda.Request -import com.serverless.lambda.Response -import groovy.transform.Memoized -import groovy.util.logging.Log4j -import io.vertx.core.eventbus.EventBus -import org.springframework.boot.SpringApplication -import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.context.ApplicationContext -import org.springframework.context.annotation.ComponentScan - -import java.util.concurrent.CompletableFuture -import java.util.concurrent.TimeUnit - -@SpringBootApplication -@ComponentScan(basePackages = "com.serverless") -@Log4j -class LambdaHandler implements RequestHandler { - - static void main(String[] args) throws Exception { - LambdaHandler.newInstance().getApplicationContext(args) - } - - @Memoized - ApplicationContext getApplicationContext(String[] args = []) { - System.setProperty('vertx.disableFileCPResolving', 'true') - return SpringApplication.run(LambdaHandler.class, args) - } - - @Override - Response handleRequest(Map input, Context context) { - final Request request = new Request(context.awsRequestId, input) - def eventBus = applicationContext.getBean(EventBus.class) - CompletableFuture future = new CompletableFuture<>() - eventBus.send(request.methodResource(), request, { ar -> - if (ar.succeeded()) { - assert ar.result().body() instanceof Response - log.info "Received reply: ${ar.result().body()}" - future.complete(ar.result().body()) - } - }) - future.get(5, TimeUnit.SECONDS) - } -} diff --git a/src/main/groovy/com/serverless/VertxHandler.groovy b/src/main/groovy/com/serverless/VertxHandler.groovy new file mode 100644 index 0000000..1c78ddb --- /dev/null +++ b/src/main/groovy/com/serverless/VertxHandler.groovy @@ -0,0 +1,48 @@ +package com.serverless + +import com.amazonaws.services.lambda.runtime.Context +import com.amazonaws.services.lambda.runtime.RequestHandler +import groovy.transform.Memoized +import groovy.util.logging.Log4j +import io.vertx.core.Vertx + +import java.util.concurrent.CompletableFuture +import java.util.concurrent.TimeUnit + +@Log4j +class VertxHandler implements RequestHandler { + + @Override + Map handleRequest(Map input, Context context) { + CompletableFuture future = new CompletableFuture<>() + def address = "${input.httpMethod}:${input.resource}" + vertx().eventBus().send(address, input, { ar -> + if (ar.succeeded()) { + log.info "Received reply: ${ar.result().body()}" + future.complete(ar.result().body()) + } + }) + future.get(5, TimeUnit.SECONDS) + } + + @Memoized + Vertx vertx() { + System.setProperty('vertx.disableFileCPResolving', 'true') + def vertx = Vertx.vertx() + def eventBus = vertx.eventBus() + + eventBus.consumer('GET:/users') { message -> + message.reply([statusCode: 200, body: 'GET:/users']) + } + + eventBus.consumer('GET:/users/{lastName}') { message -> + message.reply([statusCode: 200, body: 'GET:/users/{lastName}']) + } + + eventBus.consumer('POST:/users/create') { message -> + message.reply([statusCode: 201, body: 'POST:/users/create']) + } + + return vertx + } +} diff --git a/src/main/groovy/com/serverless/handler/CreateUser.groovy b/src/main/groovy/com/serverless/handler/CreateUser.groovy deleted file mode 100644 index 8e634e2..0000000 --- a/src/main/groovy/com/serverless/handler/CreateUser.groovy +++ /dev/null @@ -1,48 +0,0 @@ -package com.serverless.handler - -import com.serverless.lambda.Request -import com.serverless.lambda.Response -import com.serverless.persistence.entity.User -import com.serverless.persistence.repository.UserRepository -import groovy.json.JsonOutput -import groovy.util.logging.Log4j -import io.vertx.core.eventbus.Message -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.stereotype.Component - -import javax.annotation.PostConstruct - -@Component -@Log4j -class CreateUser implements MessageHandler { - - @Autowired - private UserRepository userRepository - - @PostConstruct - void init() { - // Sample Data - userRepository.save(new User('Michael', 'Jordan')) - userRepository.save(new User('Scottie', 'Pippen')) - userRepository.save(new User('Phil', 'Jackson')) - } - - @Override - String getRouteChannel() { - return 'POST:/users/create' - } - - @Override - void handle(final Message message) { - final Request request = message.body() - def firstName = request.queryString('firstName') - def lastName = request.queryString('lastName') - def user = new User(firstName, lastName) - userRepository.save(user) - def response = Response.builder() - .statusCode(201) - .body(JsonOutput.prettyPrint(JsonOutput.toJson(user))) - .build() - message.reply(response) - } -} diff --git a/src/main/groovy/com/serverless/handler/FindUsersByLastName.groovy b/src/main/groovy/com/serverless/handler/FindUsersByLastName.groovy deleted file mode 100644 index 15b4567..0000000 --- a/src/main/groovy/com/serverless/handler/FindUsersByLastName.groovy +++ /dev/null @@ -1,35 +0,0 @@ -package com.serverless.handler - -import com.serverless.lambda.Request -import com.serverless.lambda.Response -import com.serverless.persistence.repository.UserRepository -import groovy.json.JsonOutput -import groovy.util.logging.Log4j - -import io.vertx.core.eventbus.Message -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.stereotype.Component - -@Component -@Log4j -class FindUsersByLastName implements MessageHandler { - - @Autowired - private UserRepository userRepository - - @Override - String getRouteChannel() { - return 'GET:/users/{lastName}' - } - - @Override - void handle(final Message message) { - def request = message.body() - def customers = userRepository.findByLastName(request.pathParameter('lastName')) - def response = Response.builder() - .statusCode(200) - .body(JsonOutput.prettyPrint(JsonOutput.toJson(customers))) - .build() - message.reply(response) - } -} diff --git a/src/main/groovy/com/serverless/handler/GetUsers.groovy b/src/main/groovy/com/serverless/handler/GetUsers.groovy deleted file mode 100644 index 8b71168..0000000 --- a/src/main/groovy/com/serverless/handler/GetUsers.groovy +++ /dev/null @@ -1,33 +0,0 @@ -package com.serverless.handler - -import com.serverless.lambda.Request -import com.serverless.lambda.Response -import com.serverless.persistence.repository.UserRepository -import groovy.json.JsonOutput -import groovy.util.logging.Log4j -import io.vertx.core.eventbus.Message -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.stereotype.Component - -@Component -@Log4j -class GetUsers implements MessageHandler { - - @Autowired - private UserRepository userRepository - - @Override - String getRouteChannel() { - return 'GET:/users' - } - - @Override - void handle(final Message message) { - def allCustomers = userRepository.findAll() - def response = Response.builder() - .statusCode(200) - .body(JsonOutput.prettyPrint(JsonOutput.toJson(allCustomers))) - .build() - message.reply(response) - } -} diff --git a/src/main/groovy/com/serverless/handler/MessageHandler.groovy b/src/main/groovy/com/serverless/handler/MessageHandler.groovy deleted file mode 100644 index b97d00d..0000000 --- a/src/main/groovy/com/serverless/handler/MessageHandler.groovy +++ /dev/null @@ -1,10 +0,0 @@ -package com.serverless.handler - -import com.serverless.lambda.Request -import io.vertx.core.Handler -import io.vertx.core.eventbus.Message - - -interface MessageHandler extends Handler> { - String getRouteChannel() -} \ No newline at end of file diff --git a/src/main/groovy/com/serverless/lambda/Request.groovy b/src/main/groovy/com/serverless/lambda/Request.groovy deleted file mode 100644 index 26ea2b9..0000000 --- a/src/main/groovy/com/serverless/lambda/Request.groovy +++ /dev/null @@ -1,39 +0,0 @@ -package com.serverless.lambda - -import com.serverless.trait.ToJson -import groovy.transform.ToString - -@ToString(includePackage = false) -class Request implements ToJson { - final Map input - final String requestId - - Request(final String requestId, final Map input) { - this.input = input - this.requestId = requestId - } - - String requestId() { - requestId - } - - String resourcePath() { - input?.resource ?: 'unknown' - } - - String httpMethod() { - input?.httpMethod ?: 'unknown' - } - - String queryString(String name) { - input?.queryStringParameters?."${name}"?.trim() - } - - String pathParameter(String name) { - input?.pathParameters?."${name}"?.trim() - } - - String methodResource() { - "${httpMethod()}:${resourcePath()}" - } -} diff --git a/src/main/groovy/com/serverless/lambda/Response.groovy b/src/main/groovy/com/serverless/lambda/Response.groovy deleted file mode 100644 index 1a0c466..0000000 --- a/src/main/groovy/com/serverless/lambda/Response.groovy +++ /dev/null @@ -1,19 +0,0 @@ -package com.serverless.lambda - -import com.serverless.trait.ToJson -import groovy.transform.CompileStatic -import groovy.transform.ToString -import groovy.transform.builder.Builder - -@Builder -@ToString(includePackage = false) -@CompileStatic -class Response implements ToJson { - int statusCode - String body - private Map headers = [:] - - void addHeader(String key, String value) { - headers.put(key, value) - } -} diff --git a/src/main/groovy/com/serverless/persistence/entity/User.groovy b/src/main/groovy/com/serverless/persistence/entity/User.groovy deleted file mode 100644 index 27994f8..0000000 --- a/src/main/groovy/com/serverless/persistence/entity/User.groovy +++ /dev/null @@ -1,28 +0,0 @@ -package com.serverless.persistence.entity - -import groovy.transform.CompileStatic -import groovy.transform.ToString - -import javax.persistence.Entity -import javax.persistence.GeneratedValue -import javax.persistence.GenerationType -import javax.persistence.Id - -@Entity -@ToString -@CompileStatic -class User { - - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - Long id - String firstName - String lastName - - User() {} - - User(String firstName, String lastName) { - this.firstName = firstName - this.lastName = lastName - } -} \ No newline at end of file diff --git a/src/main/groovy/com/serverless/persistence/repository/UserRepository.groovy b/src/main/groovy/com/serverless/persistence/repository/UserRepository.groovy deleted file mode 100644 index 9ee1c78..0000000 --- a/src/main/groovy/com/serverless/persistence/repository/UserRepository.groovy +++ /dev/null @@ -1,11 +0,0 @@ -package com.serverless.persistence.repository - -import com.serverless.persistence.entity.User -import groovy.transform.CompileStatic -import org.springframework.data.repository.CrudRepository - -@CompileStatic -interface UserRepository extends CrudRepository { - - List findByLastName(String lastName) -} \ No newline at end of file diff --git a/src/main/groovy/com/serverless/trait/ToJson.groovy b/src/main/groovy/com/serverless/trait/ToJson.groovy deleted file mode 100644 index d507b1f..0000000 --- a/src/main/groovy/com/serverless/trait/ToJson.groovy +++ /dev/null @@ -1,11 +0,0 @@ -package com.serverless.trait - -import groovy.json.JsonOutput -import groovy.transform.CompileStatic - -@CompileStatic -trait ToJson { - String asJson() { - return JsonOutput.prettyPrint(JsonOutput.toJson(this)) - } -} \ No newline at end of file diff --git a/src/main/groovy/com/serverless/vertx/RequestCodec.groovy b/src/main/groovy/com/serverless/vertx/RequestCodec.groovy deleted file mode 100644 index 4184178..0000000 --- a/src/main/groovy/com/serverless/vertx/RequestCodec.groovy +++ /dev/null @@ -1,38 +0,0 @@ -package com.serverless.vertx - -import com.serverless.lambda.Request -import groovy.json.JsonSlurper -import io.vertx.core.buffer.Buffer -import io.vertx.core.eventbus.MessageCodec -import org.springframework.stereotype.Component - -@Component -class RequestCodec implements MessageCodec { - - @Override - void encodeToWire(Buffer buffer, Request request) { - buffer.appendString(request.asJson()) - } - - @Override - Request decodeFromWire(int pos, Buffer buffer) { - String jsonString = buffer.getString(0, buffer.length()) - Map json = JsonSlurper.newInstance().parseText(jsonString) - return new Request(json.requestId, json.input) - } - - @Override - Request transform(Request request) { - request - } - - @Override - String name() { - this.getClass().getSimpleName() - } - - @Override - byte systemCodecID() { - -1 - } -} diff --git a/src/main/groovy/com/serverless/vertx/ResponseCodec.groovy b/src/main/groovy/com/serverless/vertx/ResponseCodec.groovy deleted file mode 100644 index 2f64aa2..0000000 --- a/src/main/groovy/com/serverless/vertx/ResponseCodec.groovy +++ /dev/null @@ -1,38 +0,0 @@ -package com.serverless.vertx - -import com.serverless.lambda.Response -import groovy.json.JsonSlurper -import io.vertx.core.buffer.Buffer -import io.vertx.core.eventbus.MessageCodec -import org.springframework.stereotype.Component - -@Component -class ResponseCodec implements MessageCodec { - - @Override - void encodeToWire(Buffer buffer, Response response) { - buffer.appendString(response.asJson()) - } - - @Override - Response decodeFromWire(int pos, Buffer buffer) { - String jsonString = buffer.getString(0, buffer.length()) - Map json = JsonSlurper.newInstance().parseText(jsonString) - Response.builder().statusCode(json?.statusCode).body(json.body).headers(json.headers).build() - } - - @Override - Response transform(Response response) { - response - } - - @Override - String name() { - this.getClass().getSimpleName() - } - - @Override - byte systemCodecID() { - return -1 - } -} diff --git a/src/main/groovy/com/serverless/vertx/VertxConfig.groovy b/src/main/groovy/com/serverless/vertx/VertxConfig.groovy deleted file mode 100644 index f0bb5b1..0000000 --- a/src/main/groovy/com/serverless/vertx/VertxConfig.groovy +++ /dev/null @@ -1,31 +0,0 @@ -package com.serverless.vertx - -import com.serverless.handler.MessageHandler -import com.serverless.lambda.Request -import com.serverless.lambda.Response -import io.vertx.core.Vertx -import io.vertx.core.eventbus.EventBus -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration - -@Configuration -class VertxConfig { - - @Bean - EventBus eventBus(final List handlers, - final RequestCodec requestCodec, - final ResponseCodec responseCodec) { - def eventBus = Vertx.vertx().eventBus() - - // Register Codecs - eventBus.registerDefaultCodec(Request.class, requestCodec) - eventBus.registerDefaultCodec(Response.class, responseCodec) - - // Register Handlers - handlers?.each { - eventBus.consumer(it.getRouteChannel(), it) - } - - eventBus - } -} diff --git a/src/test/java/com/serverless/LambdaHandlerTest.groovy b/src/test/java/com/serverless/LambdaHandlerTest.groovy deleted file mode 100644 index 950a763..0000000 --- a/src/test/java/com/serverless/LambdaHandlerTest.groovy +++ /dev/null @@ -1,33 +0,0 @@ -package com.serverless - -import com.amazonaws.services.lambda.runtime.Context -import groovy.util.logging.Log4j -import org.junit.Test -import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.context.annotation.ComponentScan - -@SpringBootApplication -@ComponentScan(basePackages = "com.serverless") -@Log4j -class LambdaHandlerTest { - @Test - void handleRequest() throws Exception { - - LambdaHandler mainHandler = new LambdaHandler() - - def input - def context = {} as Context - context.metaClass.getAwsRequestId = { UUID.randomUUID().toString() } - - - input = [httpMethod: 'GET', resource: '/users/{lastName}', pathParameters: [lastName: 'Jackson']] - mainHandler.handleRequest(input, context) - - input = [httpMethod : 'POST', resource: '/users/create', - queryStringParameters: [firstName: 'Rowell', lastName: 'Belen']] - mainHandler.handleRequest(input, context) - - input = [httpMethod: 'GET', resource: '/users'] - mainHandler.handleRequest(input, context) - } -} \ No newline at end of file From d3396f875acac565666eb447c2d5309999c0cf90 Mon Sep 17 00:00:00 2001 From: Rowell Belen Date: Wed, 29 Mar 2017 21:35:05 -0600 Subject: [PATCH 4/4] UserService Verticle --- .../groovy/com/serverless/VertxHandler.groovy | 57 +++++++++++-------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/src/main/groovy/com/serverless/VertxHandler.groovy b/src/main/groovy/com/serverless/VertxHandler.groovy index 1c78ddb..b2cb895 100644 --- a/src/main/groovy/com/serverless/VertxHandler.groovy +++ b/src/main/groovy/com/serverless/VertxHandler.groovy @@ -2,47 +2,56 @@ package com.serverless import com.amazonaws.services.lambda.runtime.Context import com.amazonaws.services.lambda.runtime.RequestHandler -import groovy.transform.Memoized -import groovy.util.logging.Log4j +import io.vertx.core.AbstractVerticle import io.vertx.core.Vertx import java.util.concurrent.CompletableFuture import java.util.concurrent.TimeUnit -@Log4j class VertxHandler implements RequestHandler { + // Initialize Vertx instance and deploy UserService Verticle + final Vertx vertxInstance = { + System.setProperty('vertx.disableFileCPResolving', 'true') + final vertx = Vertx.vertx() + vertx.deployVerticle(UserService.newInstance()) + return vertx + }() + @Override Map handleRequest(Map input, Context context) { - CompletableFuture future = new CompletableFuture<>() - def address = "${input.httpMethod}:${input.resource}" - vertx().eventBus().send(address, input, { ar -> - if (ar.succeeded()) { - log.info "Received reply: ${ar.result().body()}" - future.complete(ar.result().body()) + final future = new CompletableFuture() + + // Send message to event bus using httpmethod:resource as dynamic channel + final eventBusAddress = "${input.httpMethod}:${input.resource}" + vertxInstance.eventBus().send(eventBusAddress, input, { asyncResult -> + if (asyncResult.succeeded()) { + future.complete(asyncResult.result().body()) + } else { + future.completeExceptionally(asyncResult.cause()) } }) + future.get(5, TimeUnit.SECONDS) } - @Memoized - Vertx vertx() { - System.setProperty('vertx.disableFileCPResolving', 'true') - def vertx = Vertx.vertx() - def eventBus = vertx.eventBus() + class UserService extends AbstractVerticle { + @Override + void start() throws Exception { - eventBus.consumer('GET:/users') { message -> - message.reply([statusCode: 200, body: 'GET:/users']) - } + final eventBus = vertx.eventBus() - eventBus.consumer('GET:/users/{lastName}') { message -> - message.reply([statusCode: 200, body: 'GET:/users/{lastName}']) - } + eventBus.consumer('GET:/users') { message -> + message.reply([statusCode: 200, body: 'Received GET:/users']) + } - eventBus.consumer('POST:/users/create') { message -> - message.reply([statusCode: 201, body: 'POST:/users/create']) - } + eventBus.consumer('GET:/users/{lastName}') { message -> + message.reply([statusCode: 200, body: 'Received GET:/users/{lastName}']) + } - return vertx + eventBus.consumer('POST:/users/create') { message -> + message.reply([statusCode: 201, body: 'Received POST:/users/create']) + } + } } }