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.0.0.RC2

1 Getting Started

Lets 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 the vies-vat-validator function to ensure the VAT number is valid.

The next diagram illustrates the flow:

microservices and lambda

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.

or

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"
}
vies-vat-validator/src/main/groovy/example/micronaut/VatValidationRequest.groovy
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
}
vies-vat-validator/src/main/groovy/example/micronaut/VatValidation.groovy
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

vies-vat-validator/build.gradle
compile "io.micronaut:http-client"

Create a VatService which consumes the SOAP Service:

vies-vat-validator/src/main/groovy/example/micronaut/VatService.groovy
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

vies-vat-validator/src/main/groovy/example/micronaut/ViesVatValidatorFunction.groovy
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.

vies-vat-validator/src/main/resources/logback.xml
<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:

vies-vat-validator/build.gradle
testRuntime "io.micronaut:http-server-netty"
testRuntime "io.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>.

vies-vat-validator/src/test/groovy/example/micronaut/ViesVatValidatorClient.groovy
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:

vies-vat-validator/src/test/groovy/example/micronaut/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 Micronuat 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.

vies-vat-validator/event.json
{
  "memberStateCode": "es",
  "vatNumber": "B86412491"
}

Next create a AWS SAM template file:

vies-vat-validator/template.yaml
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.

step1

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

step2

Configure tests events:

step3

Create a payload with valid data:

step4

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.

step5

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.

invoice/src/main/groovy/example/micronaut/Invoice.groovy
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
}
invoice/src/main/groovy/example/micronaut/InvoiceLine.groovy
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:

invoice/src/main/groovy/example/micronaut/VatValidator.groovy
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:

invoice/src/main/groovy/example/micronaut/VatValidationRequest.groovy
package example.micronaut

import groovy.transform.CompileStatic

@CompileStatic
class VatValidationRequest implements Serializable {
    String memberStateCode
    String vatNumber
}
invoice/src/main/groovy/example/micronaut/VatValidation.groovy
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:

invoice/build.gradle
compile "io.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:

invoice/src/main/resources/application.yml
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:

invoice/src/main/resources/application.yml
vat:
    percentage: 0.21
    scale: 2
    retry:
        attempts: 5
        delay: 5s

Create VatClient to invoke our function:

invoice/src/main/groovy/example/micronaut/VatClient.groovy
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

invoice/build.gradle
compile "io.micronaut.configuration:hibernate-validator"
invoice/src/main/groovy/example/micronaut/InvoiceController.groovy
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:

invoice/src/test/groovy/example/micronaut/VatValidatorMock.groovy
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:

invoice/src/test/groovy/example/micronaut/InvoiceControllerSpec.groovy
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 a random port.

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

awsapigateway1

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

awsapigateway2

Create a Model for the incoming request to the method.

awsapigateway3

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

awsapigateway4

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

awsapigateway5

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

awsapigateway6
awsapigateway7

You will get an invoke URL to use:

awsapigateway8

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:

invoice/src/main/resources/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:

invoice/src/main/groovy/example/micronaut/AwsApiGatewayVatClient.groovy
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.