Micronaut Functions deployed in AWS Lambda
Learn how to write a function with Micronaut and deploy it to AWS Lambda.
Authors: Sergio del Amo
Micronaut Version: 1.1.0.M1
1 Getting Started
Let’s describe the microservices you are going to build through the tutorial.
-
vies-vat-validator
- A Function which you will build with Micronaut and deploy to AWS Lambda. -
invoice
- A microservice which exposes an endpoint to check the Value Added Tax (VAT) which should apply to an invoice. It consumes thevies-vat-validator
function to ensure the VAT number is valid.
The next diagram illustrates the flow:
1.1 What you will need
To complete this guide, you will need the following:
-
Some time on your hands
-
A decent text editor or IDE
-
JDK 1.8 or greater installed with
JAVA_HOME
configured appropriately
You will need also an AWS Account.
1.2 Solution
We recommend you to follow the instructions in the next sections and create the app step by step. However, you can go right to the completed example.
-
Download and unzip the source
or
-
Clone the Git repository:
git clone https://github.com/micronaut-guides/micronaut-function-aws-lambda-groovy.git
Then, cd
into the complete
folder which you will find in the root project of the downloaded/cloned project.
2 Writing the Function
Create the function:
mn create-function example.micronaut.vies-vat-validator --lang groovy
2.1 POJOs
Create a POJO to encapsulate the validation request:
{
"memberStateCode": "es",
"vatNumber": "B86412491"
}
package example.micronaut
import groovy.transform.CompileStatic
@CompileStatic
class VatValidationRequest implements Serializable {
String memberStateCode
String vatNumber
}
And another POJO to encapsulate the expected response. Note: we add a Boolean valid
property.
{
"memberStateCode": "es",
"vatNumber": "B86412491"
"valid": true
}
package example.micronaut
import groovy.transform.CompileStatic
@CompileStatic
class VatValidation extends VatValidationRequest implements Serializable {
Boolean valid
}
2.2 SOAP VAT Validation
VIES (VAT Information Exchange System) is an electronic mean of validating VAT-identification numbers of economic operators registered in the European Union for cross border transactions on goods or services.
For example, if you create an e-commerce Web application in the European union you would need to check the validity of the purchaser’s VAT Number in order to create a proper invoice.
To automate the validation checks, the EU does not offer a REST endpoint but a SOAP service. Its WSDL file can be obtained here.
SOAP (originally Simple Object Access Protocol) is a protocol specification for exchanging structured information in the implementation of web services in computer networks. Its purpose is to induce extensibility, neutrality and independence
Create VatService
to do a SOAP request to validate the VAT Number.
To consume the SOAP service we could have use a SOAP library. However, the use case is so simple we are going to use the Micronaut HTTP Client and build the SOAP envelope manually.
Add http-client
dependency to build.gradle
compile "io.micronaut:micronaut-http-client"
Create a VatService
which consumes the SOAP Service:
package example.micronaut
import groovy.transform.CompileStatic
import io.micronaut.http.HttpRequest
import io.micronaut.http.client.RxHttpClient
import io.micronaut.http.client.annotation.Client
import io.reactivex.Flowable
import io.reactivex.Single
import javax.inject.Singleton
@CompileStatic
@Singleton (1)
class VatService {
private static final String SERVER = "http://ec.europa.eu"
private static final String PATH = "/taxation_customs/vies/services/checkVatService"
private static final String VALID_XML_OPEN_TAG = "<valid>"
private static final String VALID_XML_CLOSE_TAG = "</valid>"
protected final RxHttpClient client
VatService(@Client("http://ec.europa.eu") RxHttpClient client) { (2)
this.client = client
}
Single<Boolean> validateVat(String memberStateCode, String vatNumber) {
String soapEnvelope = soapEnvelope(memberStateCode, vatNumber)
HttpRequest request = HttpRequest.POST("${SERVER}${PATH}".toString(), soapEnvelope) (3)
.contentType("application/soap+xml")
Flowable<String> response = client.retrieve(request, String.class)
response.firstOrError().map(this.&parseResponseToBoolean) as Single<Boolean>
}
private Boolean parseResponseToBoolean(String response) {
if (!response.contains(VALID_XML_OPEN_TAG) || !response.contains(VALID_XML_CLOSE_TAG)) {
return false
}
int beginIndex = response.indexOf(VALID_XML_OPEN_TAG) + VALID_XML_OPEN_TAG.length()
String validResponse = response.substring(beginIndex, response.indexOf(VALID_XML_CLOSE_TAG))
Boolean.valueOf(validResponse)
}
private String soapEnvelope(String memberStateCode, String vatNumber) {
"""\
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">
<soapenv:Header/>
<soapenv:Body xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">
<urn:checkVat xmlns:urn=\"urn:ec.europa.eu:taxud:vies:services:checkVat:types\">
<urn:countryCode>${memberStateCode}</urn:countryCode>
<urn:vatNumber>${vatNumber}</urn:vatNumber>
</urn:checkVat>
</soapenv:Body>
</soapenv:Envelope>"""
}
}
1 | Use javax.inject.Singleton to designate a class a a singleton |
2 | Constructor Injection of Micronaut RxClient |
3 | Define a POST Request. |
2.3 Function
Edit ViesVatValidatorFunction
package example.micronaut
import groovy.transform.Field
import javax.inject.Inject
@Field @Inject VatService vatService (1)
VatValidation viesVatValidator(VatValidationRequest request) { (2)
final String memberStateCode = request.getMemberStateCode()
final String vatNumber = request.getVatNumber()
vatService.validateVat(memberStateCode, vatNumber)
.map { Boolean valid -> new VatValidation(memberStateCode: memberStateCode, vatNumber: vatNumber, valid: valid) }
.blockingGet() (3)
}
1 | In order to make use of dependency injection in your Groovy function, use the groovy.transform.Field annotation transform in addition to the @Inject annotation. |
2 | We define a method with a POJO as input or output. That it is the easiest way to have a parameter with an incoming JSON Payload and JSON Payload response. |
3 | We cannot return a non-blocking type due to AWS Lambda Handler Input/Output supported Types. |
Modify logback.xml
to set DEBUG
level for package example.micronaut
.
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
</root>
<logger name="example.micronaut" level="DEBUG"/>
</configuration>
2.4 Function Tests
build.gradle
already contains these dependencies:
testRuntime "io.micronaut:micronaut-http-server-netty"
testRuntime "io.micronaut:micronaut-function-web"
Because of that, testing your functions is really easy.
First modify ViesVatValidatorClient
interface to match to the method signature forced by Function<VatValidationRequest, VatValidation>
.
package example.micronaut
import io.micronaut.function.client.FunctionClient
import io.micronaut.http.annotation.Body
import io.reactivex.Single
import javax.inject.Named
@FunctionClient (1)
interface ViesVatValidatorClient {
@Named("vies-vat-validator") (2)
Single<VatValidation> apply(@Body VatValidationRequest request) (3)
}
1 | The FunctionClient annotation allows applying introduction advise to an interface such that methods defined by the interface become invokers of remote functions configured by the application. |
2 | For a method name viesVatValidator method the function URI is vies-vat-validator . |
3 | Functions that only return a value are mapped to HTTP GET requests, whilst functions that accept an input require an HTTP POST. Thus, use @Body to supply the POJO. |
Now you can start up the Micronaut application and access your function via the client interface in your test.
Modify ViesVatValidatorFunctionSpec.groovy
:
package example.micronaut
import io.micronaut.context.ApplicationContext
import io.micronaut.runtime.server.EmbeddedServer
import spock.lang.Specification
import spock.lang.Unroll
class ViesVatValidatorFunctionSpec extends Specification {
@Unroll("#code #vatNumber is #description")
void testViesVatValidatorFunction(String code, String vatNumber, boolean expected, String description) throws Exception {
given:
EmbeddedServer server = ApplicationContext.run(EmbeddedServer.class)
when:
ViesVatValidatorClient client = server.getApplicationContext().getBean(ViesVatValidatorClient.class)
then:
noExceptionThrown()
when:
VatValidationRequest req = new VatValidationRequest(memberStateCode: code, vatNumber: vatNumber)
then:
expected == client.apply(req).blockingGet().valid
cleanup:
server.stop()
server.close()
where:
code | vatNumber | expected
"es" | "B99286353" | true
"es" | "B19280031" | true
"es" | "XXXXXXXXX" | false
description = expected ? 'is valid' : 'is invalid'
}
}
3 Test with SAM CLI
AWS SAM CLI is a CLI tool for local development and testing of Serverless applications.
You can skip this section. If you want to complete this section you will need SAM and Docker installed. |
To test your Micronaut function with SAM you will first need to install SAM.
Then create a file event.json
. We will use it as the stimulus for the SAM cli invocation.
{
"memberStateCode": "es",
"vatNumber": "B86412491"
}
Next create a AWS SAM template file:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 25
Resources:
ViesVatValidatorFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: build/libs/complete/vies-vat-validator-0.1-all.jar
Handler: example.micronaut.ViesVatValidatorFunction::viesVatValidator
Runtime: java8
With that in place, you can invoke your function locally:
o$ sam local invoke "ViesVatValidatorFunction" -e event.json
2018-09-25 09:45:40 Invoking example.micronaut.ViesVatValidatorFunction::viesVatValidator (java8)
2018-09-25 09:45:40 Found credentials in shared credentials file: ~/.aws/credentials
2018-09-25 09:45:40 Decompressing /Users/sdelamo/Developer/micronaut-guides/micronaut-function-aws-lambda-groovy/complete/vies-vat-validator/build/libs/complete/vies-vat-validator-0.1-all.jar
Fetching lambci/lambda:java8 Docker container image........................
2018-09-25 09:45:47 Mounting /private/var/folders/pb/dx1ms2_92_g54v9jvzv7k4hc0000gn/T/tmp1JpBgZ as /var/task:ro inside runtime container
START RequestId: 02f9123b-d64e-4257-ab90-334cec2f9cb7 Version: $LATEST
END RequestId: 02f9123b-d64e-4257-ab90-334cec2f9cb7
REPORT RequestId: 02f9123b-d64e-4257-ab90-334cec2f9cb7 Duration: 1305.64 ms Billed Duration: 1400 ms Memory Size: 128 MB Max Memory Used: 37 MB
{"memberStateCode":"es","vatNumber":"B86412491","valid":true}
4 Deploy to AWS Lambda
Deploy your Micronaut function to AWS Lambda.
First, create a Function. I select Paris as the region.

Select Java 8 runtime. Name: vies-vat-validator
and create a new role form template(s)
. I give the role name
lambda_basic_execution
.

Configure tests events:

Create a payload with valid data:

Generate a jar file:
`vies-vat-validator $ ./gradlew shadowJar`
The file is just 12MB ;-)
$ du -h build/libs/complete/vies-vat-validator-0.1-all.jar
12M build/libs/complete/vies-vat-validator-0.1-all.jar
Upload the JAR as illustrated here:
Enter as Handler
value example.micronaut.ViesVatValidatorFunction::viesVatValidator
.
I have allocated just 256Mb and a timeout of 25s.

5 Writing the App
Now, it is time to consume the AWS Lambda function from another micronaut Microservice:
Create the microservice:
mn create-app example.micronaut.invoice --lang groovy
The previous command creates an application with a default package of example.micronaut
in a folder invoice`
5.1 POJOs
We expect to get an incoming JSON payload such as:
{
"vatNumber": "B86412491",
"countryCode": "es",
"lines": [
{
"count": 2,
"productId": "1491950358",
"price": 19.99
},
{
"count": 1,
"productId": "1680502395",
"price": 15
},
]
}
We create two POJOs to map it.
package example.micronaut
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import javax.validation.constraints.NotBlank
import javax.validation.constraints.NotEmpty
import javax.validation.constraints.NotNull
@EqualsAndHashCode
@CompileStatic
class Invoice {
@NotNull
@NotBlank
String vatNumber
@NotNull
@NotBlank
String countryCode
@NotEmpty
List<InvoiceLine> lines
}
package example.micronaut
import groovy.transform.EqualsAndHashCode
import javax.validation.constraints.NotBlank
import javax.validation.constraints.NotNull
import javax.validation.constraints.Positive
@EqualsAndHashCode
class InvoiceLine {
@NotNull
@NotBlank
String productId
@Positive
Integer count
@NotNull
@Positive
BigDecimal price
BigDecimal vatPrice(BigDecimal vatPercentage) {
getPrice().multiply(BigDecimal.valueOf(getCount()).multiply(vatPercentage))
}
}
5.2 Call the Function
Create an interface to abstract the collaboration with the function:
package example.micronaut
import io.micronaut.http.annotation.Body
import io.reactivex.Single
interface VatValidator {
Single<VatValidation> validateVat(@Body VatValidationRequest req) (1)
}
1 | Functions that only return a value are mapped to HTTP GET requests, whilst functions that accept an input require an HTTP POST. Thus, use @Body to supply the POJO. |
Create two POJOs which are used by the previous interface:
package example.micronaut
import groovy.transform.CompileStatic
@CompileStatic
class VatValidationRequest implements Serializable {
String memberStateCode
String vatNumber
}
package example.micronaut
import groovy.transform.CompileStatic
@CompileStatic
class VatValidation extends VatValidationRequest implements Serializable {
Boolean valid
}
Modify build.gradle
to add function-client
and com.amazonaws:aws-java-sdk-lambda
dependencies:
compile "io.micronaut:micronaut-function-client"
runtime 'com.amazonaws:aws-java-sdk-lambda:1.11.414'
Also modify src/main/resources/application.yml
and the define a function with the same name vies-vat-validator
as the one we deployed to AWS Lambda:
aws:
lambda:
functions:
vat:
functionName: vies-vat-validator
qualifer: vat
region: eu-west-3 # Paris Region
Add the next configuration properties which we will use shortly:
vat:
percentage: 0.21
scale: 2
retry:
attempts: 5
delay: 5s
Create VatClient
to invoke our function:
package example.micronaut
import io.micronaut.context.annotation.Requires
import io.micronaut.context.env.Environment
import io.micronaut.function.client.FunctionClient
import io.micronaut.http.annotation.Body
import io.micronaut.retry.annotation.Retryable
import io.reactivex.Single
import javax.inject.Named
@FunctionClient (1)
@Requires(notEnv = Environment.TEST) (2)
interface VatClient extends VatValidator {
@Override
@Named("vies-vat-validator") (3)
@Retryable(attempts = '${vat.retry.attempts:3}', delay = '${vat.retry.delay:1s}') (4)
Single<VatValidation> validateVat(@Body VatValidationRequest req) (5)
}
1 | The FunctionClient annotation allows applying introduction advise to an interface such that methods defined by the interface become invokers of remote functions configured by the application. |
2 | You can remove a bean from the test classpath easily. |
3 | Use the function name vies-vat-validator |
4 | AWS Lambda functions may take time to warm up. Thus, failure is something you have to plan for and it is pretty common to want to attempt to retry an operation if it fails. With Micronaut Retry you can try again easily. |
5 | Functions that only return a value are mapped to HTTP GET requests, whilst functions that accept an input require an HTTP POST. Thus, use @Body to supply the POJO. |
5.3 Controller
Micronaut’s validation is built on with the standard framework – JSR 380, also known as Bean Validation 2.0.
Hibernate Validator is a reference implementation of the validation API.
Add the next snippet to build.gradle
compile "io.micronaut.configuration:micronaut-hibernate-validator"
package example.micronaut
import groovy.transform.CompileStatic
import io.micronaut.context.annotation.Value
import io.micronaut.http.annotation.Body
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Post
import io.micronaut.validation.Validated
import io.reactivex.Single
import javax.validation.Valid
@CompileStatic
@Validated (1)
@Controller("/invoice") (2)
class InvoiceController {
private final BigDecimal vatPercentage
private final VatValidator vatValidator
private final int scale
InvoiceController(@Value('${vat.percentage}') BigDecimal vatPercentage, (3)
@Value('${vat.scale}') int scale,
VatValidator vatValidator) { (4)
this.vatPercentage = vatPercentage
this.scale = scale
this.vatValidator = vatValidator
}
@Post("/vat") (5)
Single<Taxes> calculateVat(@Valid @Body Invoice invoice) { (6)
vatValidator.validateVat(new VatValidationRequest(memberStateCode: invoice.countryCode, vatNumber: invoice.vatNumber)) (7)
.map { VatValidation vatValidation ->
BigDecimal percentage = vatValidation.valid ? vatPercentage : new BigDecimal("0")
new Taxes(vat: invoice.lines.stream()
.map { InvoiceLine line -> line.vatPrice(percentage) }
.reduce { BigDecimal a, BigDecimal b -> a.add(b) }
.get()
.setScale(scale, BigDecimal.ROUND_HALF_EVEN))
}
}
}
1 | Add @Validated annotation at the class level to any class that requires validation. |
2 | The class is defined as a controller with the @Controller annotation mapped to the path /invoice . |
3 | Constructor injection of a configuration property. |
4 | Constructor injection of a bean. |
5 | The @Post annotation is used to map the calculateVat method to all requests to /invoice/vat that use an HTTP POST. |
6 | Add @Valid to any method parameter which requires validation. Use a POGO supplied as a JSON payload in the request to populate the invoice. |
7 | This invokes the function in AWS Lambda. |
5.4 Tests
We don’t want the real AWS Lambda function executed in our tests.
Thus, create a bean to provide an implementation of VatValidator
in the test phase:
package example.micronaut
import groovy.transform.CompileStatic
import io.micronaut.context.annotation.Requires
import io.micronaut.context.env.Environment
import io.reactivex.Single
import javax.inject.Singleton
@CompileStatic
@Requires(env = Environment.TEST)
@Singleton
class VatValidatorMock implements VatValidator {
@Override
Single<VatValidation> validateVat(VatValidationRequest request) {
List<String> validVatNumbers = Collections.singletonList("B84965375")
Single.just(new VatValidation(memberStateCode: request.memberStateCode,
vatNumber: request.vatNumber,
valid: validVatNumbers.contains(request.vatNumber)))
}
}
Create a JUnit test which verifies the logic:
package example.micronaut
import io.micronaut.context.ApplicationContext
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 InvoiceControllerSpec extends Specification {
@Shared
@AutoCleanup
EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer) (1)
@Shared
@AutoCleanup
RxHttpClient rxHttpClient = embeddedServer.applicationContext.createBean(RxHttpClient, embeddedServer.getURL()) (2)
void "test BooksController"() {
when:
VatValidator bean = embeddedServer.getApplicationContext().getBean(VatValidator.class)
then:
noExceptionThrown()
bean instanceof VatValidatorMock (3)
when:
Invoice invoice = new Invoice(vatNumber: "B84965375", countryCode: "es", lines: [
new InvoiceLine(productId: "1491950358", count: 2, price: new BigDecimal(19.99)),
new InvoiceLine(productId: "1680502395", count: 1, price: new BigDecimal(15)),
])
HttpRequest request = HttpRequest.POST("/invoice/vat", invoice)
Taxes rsp = rxHttpClient.toBlocking().retrieve(request, Taxes.class)
BigDecimal expected = new BigDecimal("11.55")
then:
expected == rsp.vat
when:
invoice.setVatNumber("B99999999")
rsp = rxHttpClient.toBlocking().retrieve(request, Taxes.class)
expected = new BigDecimal("0.00")
then:
expected == rsp.vat
}
}
1 | To run the application from a unit test you can use the EmbeddedServer interface. |
2 | Register a RxHttpClient bean in the application context and point it to the embedded server URL. The EmbeddedServer interface provides the URL of the server under test which runs on a random port. |
3 | Verify the bean for interface VatValidator is VatValidatorMock . |
If you are curious about how the 11.55 is calculated. Given a 21% VAT.
Price |
Count |
Line VAT |
19,99 |
2 |
8,3958 |
15 |
1 |
3,15 |
Total VAT |
11,55 |
6 Running the app
The easiest way to configure credentials for invoking the function is to define a ~/.aws/credentials` file`.
[default]
aws_access_key_id=XXXXXXXXXXX
aws_secret_access_key=XXXXX
To run the application use the ./gradlew run
command which will start the application on port 8080.
execute:
curl -X "POST" "http://localhost:8080/invoice/vat" \
-H 'Content-Type: application/json' \
-d $'{
"countryCode": "es",
"vatNumber": "B86412491",
"lines": [
{
"count": 2,
"price": 19.99,
"productId": "1491950358"
},
{
"count": 1,
"price": 15,
"productId": "1680502395"
}
]
}'
and you will get:
{"vat":11.55}
7 AWS API Gateway
Amazon API Gateway is a fully managed service that makes it easy for developers to create, publish, maintain, monitor, and secure APIs at any scale.
A common use case of AWS API Gateway is to create HTTP endpoints which expose functions deploy to AWS Lambda.
Let’s create an endpoint which exposes the Micronaut function we deployed to AWS Lambda.
Create a new AWS Gateway API

Create a POST method, with integration type Lambda Function
and enter the Lambda function name you used.

Create a Model for the incoming request to the method.

Edit the POST Method request and configure the Model as the content type.

You will be able to test the API method and its integration with the AWS Lambda function.

Deploy the API to create an endpoint which we can invoke with an HTTP request:


You will get an invoke URL to use:

7.1 Consume API Gateway URL
Although we are not using a discovery server like Consul or Eureka, you can manually configure service discovery in application.yml:
micronaut:
http:
services:
viesvatvalidator: (1)
urls:
- "https://8ah5p89xof.execute-api.eu-west-3.amazonaws.com"
stage: "beta"
application:
name: invoice
server:
port: 8080
1 | Configure service name, we will use name in @Client annotation. |
Create a Micronaut declarative HTTP Client to consume that endpoint:
package example.micronaut
import io.micronaut.context.annotation.Primary
import io.micronaut.context.annotation.Requires
import io.micronaut.context.env.Environment
import io.micronaut.http.annotation.Body
import io.micronaut.http.annotation.Post
import io.micronaut.http.client.annotation.Client
import io.micronaut.retry.annotation.Retryable
import io.reactivex.Single
@Primary (1)
@Client("viesvatvalidator") (2)
@Requires(notEnv = Environment.TEST) (3)
interface AwsApiGatewayVatClient extends VatValidator {
@Override
@Retryable(attempts = '${vat.retry.attempts:3}', delay = '${vat.retry.delay:1s}')
@Post('/${micronaut.http.services.viesvatvalidator.stage}') (4)
Single<VatValidation> validateVat(@Body VatValidationRequest req)
}
1 | Given a common interface called VatValidator
that is implemented by multiple classes (AwsApiGatewayVatClient , VatClient ) you can boost which bean is injection with @Primary. |
2 | Use the service id you defined in micronaut.http.services configuration. |
3 | You can remove a bean from the test classpath easily. |
4 | Define this as a POST request and interpolate a configuration property to define the path. |
8 Next Steps
Read more about Serverless Functions inside Micronaut.