Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions samples/spring-cloud-function-demo/README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Spring Cloud Function AWS Demo

This project demonstrates how to use Spring Cloud Function with AWS Lambda, showcasing function routing, and deployment to AWS Lambda using AWS SAM.

## Overview

Spring Cloud Function is a framework that promotes the implementation of business logic via functions. It abstracts away the underlying runtime platform, allowing the same code to run as a web endpoint, a stream processor, or a task. This project demonstrates:

- Function definition and routing
- Custom message routing
- Deployment to AWS Lambda
- Integration with AWS services (API Gateway, SQS)

## Project Structure

- `SpringCloudFunctionDemo.java`: Main application class with function definitions
- `Unicorn.java`: Simple data model class
- `template.yaml`: AWS SAM template for deployment
- `TestHttp.http`: HTTP request examples for local testing

## Functions Implemented

This demo includes several function implementations:

- `lowerCase`: Converts input string to lowercase
- `upperCase`: Converts input string to uppercase
- `reverse`: Reverses the input string
- `helloUnicorn`: Processes a Unicorn object and returns a greeting
- `asyncProcessor`: Consumes SQS events and logs the number of messages processed
- `noOpFunction`: Default function when no proper routing is found

## Custom Routing

The application demonstrates custom routing using `MessageRoutingCallback`. The router inspects the `x-routing-key` header and routes to the appropriate function:

- `uppercase` → `upperCase` function
- `lowercase` → `lowerCase` function
- `reverse` → `reverse` function
- `unicorn` → `helloUnicorn` function

## Local Development

### Prerequisites

- Java 21
- Maven
- AWS SAM CLI (for deployment)

### Building the Application

```bash
mvn clean package
```

### Running Locally

```bash
mvn spring-boot:run
```

Once running, you can test the functions using the provided `TestHttp.http` file or with curl:

```bash
# Test lowercase function
curl -X POST http://localhost:8080/lowerCase -H "Content-Type: text/plain" -d "HELLO WORLD"

# Test uppercase function
curl -X POST http://localhost:8080/upperCase -H "Content-Type: text/plain" -d "hello world"

# Test custom routing
curl -X POST http://localhost:8080/functionRouter -H "Content-Type: text/plain" -H "x-routing-key: uppercase" -d "hello world"
```

## AWS Deployment

This project uses AWS SAM for deployment to AWS Lambda.

### Prerequisites

- AWS CLI configured with appropriate credentials
- AWS SAM CLI installed

### Deployment Steps

1. Build the application:
```bash
mvn clean package
```

2. Deploy using SAM:
```bash
sam deploy --guided
```

3. Follow the prompts to complete the deployment.

### AWS Resources Created

The SAM template creates the following resources:

- **API Gateway**: Exposes the Spring Cloud Function as a REST API
- **Lambda Functions**:
- `SpringCloudFunction`: Handles API requests
- `MessageProcessor`: Processes SQS messages
- **SQS Queue**: For asynchronous message processing

## Testing in AWS

After deployment, you can test the API using the endpoint URL provided in the outputs:

```bash
# Test the deployed API
curl -X POST https://{api-id}.execute-api.{region}.amazonaws.com/Prod/uppercase/ -H "Content-Type: text/plain" -d "hello world"
```

To test the SQS integration, send a message to the created queue:

```bash
aws sqs send-message --queue-url {QueueURL} --message-body "Test message"
```

## Function Composition

Spring Cloud Function supports function composition. For example:

```bash
# Apply reverse then uppercase
curl -X POST http://localhost:8080/reverse,upperCase -H "Content-Type: text/plain" -d "hello world"
```

This will first reverse "hello world" to "dlrow olleh" and then convert it to uppercase: "DLROW OLLEH".

## Additional Resources

- [Spring Cloud Function Documentation](https://spring.io/projects/spring-cloud-function)
- [AWS Lambda Java Runtime](https://docs.aws.amazon.com/lambda/latest/dg/lambda-java.html)
- [AWS Serverless Application Model (SAM)](https://aws.amazon.com/serverless/sam/)

## License

This project is licensed under the MIT License - see the LICENSE file for details.
54 changes: 54 additions & 0 deletions samples/spring-cloud-function-demo/TestHttp.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
### Test lowerCase function
POST http://localhost:8080/lowerCase
Content-Type: text/plain

HELLO SPRING I/O

### Test upperCase function
POST http://localhost:8080/upperCase
Content-Type: text/plain

hello spring i/o

### Test reverse function
POST http://localhost:8080/reverse
Content-Type: text/plain

hello world

### Test helloUnicorn function
POST http://localhost:8080/helloUnicorn
Content-Type: application/json

{
"name": "Sparkles",
"age": 5
}

### Test function composition (upperCase after reverse)
POST http://localhost:8080/reverse,upperCase
Content-Type: text/plain

hello world


### Test routing to lowerCase function using custom router
POST http://localhost:8080/functionRouter
Content-Type: application/json
x-routing-key: lowercase

HELLO WORLD

### Test routing to upperCase function using custom router
POST http://localhost:8080/functionRouter
Content-Type: application/json
x-routing-key: uppercase

hello world


### Test routing to upperCase function using custom router
POST http://localhost:8080/functionRouter
Content-Type: application/json

hello world
107 changes: 107 additions & 0 deletions samples/spring-cloud-function-demo/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.5</version>
</parent>
<groupId>com.amazonaws.demo</groupId>
<artifactId>spring-cloud-function-demo</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>Example application to route to different Spring Cloud Functions</name>
<properties>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-cloud-function.version>4.2.2</spring-cloud-function.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-adapter-aws</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-function-web</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-events</artifactId>
<version>3.15.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.17</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-dependencies</artifactId>
<version>${spring-cloud-function.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot.experimental</groupId>
<artifactId>spring-boot-thin-layout</artifactId>
<version>1.0.31.RELEASE</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>aws</shadedClassifierName>
</configuration>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.amazonaws.demo;

import com.amazonaws.services.lambda.runtime.events.SQSEvent;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.function.context.MessageRoutingCallback;
import org.springframework.cloud.function.context.config.RoutingFunction;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.Message;

import java.util.function.Consumer;
import java.util.function.Function;

@SpringBootApplication
public class SpringCloudFunctionDemo {

public static void main(String[] args) {
SpringApplication.run(SpringCloudFunctionDemo.class, args);
}

@Bean
public Function<String, String> lowerCase() {return String::toLowerCase;}

@Bean
public Function<String, String> upperCase(){
return String::toUpperCase;
}

@Bean
public Function<String, String> reverse(){
return value -> new StringBuilder(value).reverse().toString();
}

@Bean
public Function<com.amazonaws.demo.Unicorn, String> helloUnicorn(){
return value -> "Hello %s! You are %d years old!".formatted(value.name(), value.age());
}

@Bean
public Consumer<SQSEvent> asyncProcessor(){
return value -> System.out.printf("Processed %d messages!", value.getRecords().size());
}

@Bean
public Function<String, String> noOpFunction(){
return value -> "No proper function found!";
}

@Bean
public MessageRoutingCallback customRouter() {
return new MessageRoutingCallback() {
@Override
public String routingResult(Message<?> message) {
System.out.println("Hello from MessageRoutingCallback");
var routingKey = message.getHeaders().getOrDefault("x-routing-key", "").toString();
return switch (routingKey) {
case "uppercase" -> "upperCase";
case "lowercase" -> "lowerCase";
case "reverse" -> "reverse";
case "unicorn" -> "helloUnicorn";
default -> "noOpFunction";
};
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.amazonaws.demo;

public record Unicorn(String name, int age) { }

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Function configuration
spring.cloud.function.routing.enabled=true
Loading