156 lines
9.7 KiB
Plaintext
156 lines
9.7 KiB
Plaintext
*{project-version}*
|
||
|
||
|
||
The https://aws.amazon.com/[AWS] adapter takes a Spring Cloud Function app and converts it to a form that can run in AWS Lambda.
|
||
|
||
[[introduction]]
|
||
== Introduction
|
||
|
||
The details of how to get stared with AWS Lambda is out of scope of this document, so the expectation is that user has some familiarity with
|
||
AWS and AWS Lambda and wants to learn what additional value spring provides.
|
||
|
||
|
||
=== Getting Started
|
||
|
||
One of the goals of Spring Cloud Function framework is to provide necessary infrastructure elements to enable a _simple function application_
|
||
to interact in a certain way in a particular environment.
|
||
A simple function application (in context or Spring) is an application that contains beans of type Supplier, Function or Consumer.
|
||
So, with AWS it means that a simple function bean should somehow be recognised and executed in AWS Lambda environment.
|
||
|
||
Let’s look at the example:
|
||
|
||
[source, java]
|
||
----
|
||
@SpringBootApplication
|
||
public class FunctionConfiguration {
|
||
|
||
public static void main(String[] args) {
|
||
SpringApplication.run(FunctionConfiguration.class, args);
|
||
}
|
||
|
||
@Bean
|
||
public Function<String, String> uppercase() {
|
||
return value -> value.toUpperCase();
|
||
}
|
||
}
|
||
----
|
||
|
||
It shows a complete Spring Boot application with a function bean defined in it. What’s interesting is that on the surface this is just
|
||
another boot app, but in the context of AWS Adapter it is also a perfectly valid AWS Lambda application. No other code or configuration
|
||
is required. All you need to do is package it and deploy it, so let’s look how we can do that.
|
||
|
||
To make things simpler we’ve provided a sample project ready to be built and deployed and you can access it
|
||
https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-samples/function-sample-aws[here].
|
||
|
||
You simply execute `./mvnw clean package` to generate JAR file. All the necessary maven plugins have already been setup to generate
|
||
appropriate AWS deployable JAR file. (You can read more details about JAR layout in <<Notes on JAR Layout>>).
|
||
|
||
Then you have to upload the JAR file (via AWS dashboard or AWS CLI) to AWS.
|
||
|
||
When ask about _handler_ you specify `org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest` which is a generic request handler.
|
||
|
||
image::AWS-deploy.png[width=800,scaledwidth="75%",align="center"]
|
||
|
||
That is all. Save and execute the function with some sample data which for this function is expected to be a
|
||
String which function will uppercase and return back.
|
||
|
||
While `org.springframework.cloud.function.adapter.aws.FunctionInvoker` is a general purpose AWS's `RequestHandler` implementation aimed at completely
|
||
isolating you from the specifics of AWS Lambda API, for some cases you may want to specify which specific AWS's `RequestHandler` you want
|
||
to use. The next section will explain you how you can accomplish just that.
|
||
|
||
|
||
[[aws-request-handlers]]
|
||
== AWS Request Handlers
|
||
|
||
The adapter has a couple of generic request handlers that you can use. The most generic is (and the one we used in the Getting Started section)
|
||
is `org.springframework.cloud.function.adapter.aws.FunctionInvoker` which is the implementation of AWS's `RequestStreamHandler`.
|
||
User doesn't need to do anything other then specify it as 'handler' on AWS dashboard when deploying function.
|
||
It will handle most of the case including Kinesis, streaming etc. .
|
||
|
||
|
||
If your app has more than one `@Bean` of type `Function` etc. then you can choose the one to use by configuring `spring.cloud.function.definition`
|
||
property or environment variable. The functions are extracted from the Spring Cloud `FunctionCatalog`. In the event you don't specify `spring.cloud.function.definition`
|
||
the framework will attempt to find a default following the search order where it searches first for `Function` then `Consumer` and finally `Supplier`).
|
||
|
||
[[aws-context]]
|
||
== AWS Context
|
||
|
||
In a typical implementation of AWS Handler user has access to AWS _context_ object. With function approach you can have the same experience if you need it.
|
||
Upon each invocation the framework will add `aws-context` message header containing the AWS _context_ instance for that particular invocation. So if you need to access it
|
||
you can simply have `Message<YourPojo>` as an input parameter to your function and then access `aws-context` from message headers.
|
||
For convenience we provide AWSLambdaUtils.AWS_CONTEXT constant.
|
||
|
||
|
||
[[aws-function-routing]]
|
||
== AWS Function Routing
|
||
|
||
One of the core features of Spring Cloud Function is https://docs.spring.io/spring-cloud-function/docs/{project-version}/reference/html/spring-cloud-function.html#_function_routing_and_filtering[routing]
|
||
- an ability to have one special function to delegate to other functions based on the user provided routing instructions.
|
||
|
||
In AWS Lambda environment this feature provides one additional benefit, as it allows you to bind a single function (Routing Function)
|
||
as AWS Lambda and thus a single HTTP endpoint for API Gateway. So in the end you only manage one function and one endpoint, while benefiting
|
||
from many function that can be part of your application.
|
||
|
||
More details are available in the provided https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-aws-routing[sample],
|
||
yet few general things worth mentioning.
|
||
|
||
Routing capabilities will be enabled by default whenever there is more then one function in your application as `org.springframework.cloud.function.adapter.aws.FunctionInvoker`
|
||
can not determine which function to bind as AWS Lambda, so it defaults to `RoutingFunction`.
|
||
This means that all you need to do is provide routing instructions which you can do https://docs.spring.io/spring-cloud-function/docs/{project-version}/reference/html/spring-cloud-function.html#_function_routing_and_filtering[using several mechanisms]
|
||
(see https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-aws-routing[sample] for more details).
|
||
|
||
Also, note that since AWS does not allow dots `.` and/or hyphens`-` in the name of the environment variable, you can benefit from boot support and simply substitute
|
||
dots with underscores and hyphens with camel case. So for example `spring.cloud.function.definition` becomes `spring_cloud_function_definition`
|
||
and `spring.cloud.function.routing-expression` becomes `spring_cloud_function_routingExpression`.
|
||
|
||
|
||
[[http-and-api-gateway]]
|
||
== HTTP and API Gateway
|
||
|
||
AWS has some platform-specific data types, including batching of messages, which is much more efficient than processing each one individually. To make use of these types you can write a function that depends on those types. Or you can rely on Spring to extract the data from the AWS types and convert it to a Spring `Message`. To do this you tell AWS that the function is of a specific generic handler type (depending on the AWS service) and provide a bean of type `Function<Message<S>,Message<T>>`, where `S` and `T` are your business data types. If there is more than one bean of type `Function` you may also need to configure the Spring Boot property `function.name` to be the name of the target bean (e.g. use `FUNCTION_NAME` as an environment variable).
|
||
|
||
The supported AWS services and generic handler types are listed below:
|
||
|
||
|===
|
||
| Service | AWS Types | Generic Handler |
|
||
|
||
| API Gateway | `APIGatewayProxyRequestEvent`, `APIGatewayProxyResponseEvent` | `org.springframework.cloud.function.adapter.aws.SpringBootApiGatewayRequestHandler` |
|
||
| Kinesis | KinesisEvent | org.springframework.cloud.function.adapter.aws.SpringBootKinesisEventHandler |
|
||
|===
|
||
|
||
|
||
For example, to deploy behind an API Gateway, use `--handler org.springframework.cloud.function.adapter.aws.SpringBootApiGatewayRequestHandler` in your AWS command line (in via the UI) and define a `@Bean` of type `Function<Message<Foo>,Message<Bar>>` where `Foo` and `Bar` are POJO types (the data will be marshalled and unmarshalled by AWS using Jackson).
|
||
|
||
[[custom-runtime]]
|
||
== Custom Runtime
|
||
|
||
You can also benefit from https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html[AWS Lambda custom runtime] feature of AWS Lambda
|
||
and Spring Cloud Function provides all the necessary components to make it easy.
|
||
|
||
From the code perspective the application should look no different then any other Spring Cloud Function application.
|
||
The only thing you need to do is to provide a `bootstrap` script in the root of your zip/jar that runs the Spring Boot application.
|
||
and select "Custom Runtime" when creating a function in AWS.
|
||
Here is an example 'bootstrap' file:
|
||
```text
|
||
#!/bin/sh
|
||
|
||
cd ${LAMBDA_TASK_ROOT:-.}
|
||
|
||
java -Dspring.main.web-application-type=none -Dspring.jmx.enabled=false \
|
||
-noverify -XX:TieredStopAtLevel=1 -Xss256K -XX:MaxMetaspaceSize=128M \
|
||
-Djava.security.egd=file:/dev/./urandom \
|
||
-cp .:`echo lib/*.jar | tr ' ' :` com.example.LambdaApplication
|
||
```
|
||
The `com.example.LambdaApplication` represents your application which contains function beans.
|
||
|
||
Set the handler name in AWS to the name of your function. You can use function composition here as well (e.g., `uppecrase|reverse`).
|
||
That is pretty much all. Once you upload your zip/jar to AWS your function will run in custom runtime.
|
||
We provide a https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-samples/function-sample-aws-custom-new[sample project]
|
||
where you can also see how to configure yoru POM to properly generate the zip file.
|
||
|
||
The functional bean definition style works for custom runtimes as well, and is
|
||
faster than the `@Bean` style. A custom runtime can start up much quicker even than a functional bean implementation
|
||
of a Java lambda - it depends mostly on the number of classes you need to load at runtime.
|
||
Spring doesn't do very much here, so you can reduce the cold start time by only using primitive types in your function, for instance,
|
||
and not doing any work in custom `@PostConstruct` initializers.
|