diff --git a/samples/quarkus/pet-store/README.md b/samples/quarkus/pet-store/README.md new file mode 100644 index 000000000..3ff1371d7 --- /dev/null +++ b/samples/quarkus/pet-store/README.md @@ -0,0 +1,68 @@ +# Quarkus Native Pet store example + +The [Quarkus framework](https://quarkus.io/) is compatible with Spring's annotations and makes it easy to use [GraalVM](https://www.graalvm.org/) to build application images into native binaries. Further, Micronaut includes builtin support for AWS Lambda. + +This demo application shows how to use Quarkus to compile our standard pet store example, using Spring annotations, into a native binary with GraalVM and execute it in AWS Lambda. To run this demo, you will need to have [Maven](https://maven.apache.org/) installed as well as [Docker](https://www.docker.com/) to build GraalVM native image. + +With all the pre-requisites installed including: + +* JDK 8 or above +* Maven 3.5.x + +You should be able to build a native image of the application by running mvn from the repository's root. + +```bash +$ mvn clean install -Pnative +``` + +The output of the build is a deployable zip called `function.zip` in the `target` folder. + +To run the lambda locally, you can utilize the SAM cli. This should start up the listeners in the `PetsController`, and you can test locally with your preferred http client. + +```bash +sam local start-api -t sam.native.yaml +``` + +For example, to test the GET /pets endpoint via curl: +```bash +curl localhost:3000/pets +``` + +You should see JSON output of pets. + +To deploy the application to AWS Lambda you can use the pre-configured `sam-native.yaml` file included in the repo. Using the AWS or SAM CLI, run the following commands: + +```bash +sam deploy -g -t sam.native.yaml +``` + +You should see the stack deployed successfully: + +```bash +Stack quarkus-sample-pet-store outputs: +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +OutputKey-Description OutputValue +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +PetStoreNativeApi - URL for application https://xxxxxxxxxx.execute-api.xx-xxxx-1.amazonaws.com/Prod/ +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Successfully created/updated stack - quarkus-sample-pet-store in xx-xxxx-1 + +``` + +Make a test request to the API endpoint using curl or your preferred http client. + +For example, to check the GET /pets endpoint via curl: +```bash +curl https://xxxxxxxxxx.execute-api.xx-xxxx-1.amazonaws.com/Prod/pets +``` + +Finally, there’s an environment variable that must be set for native GraalVM deployments. If you look at sam.native.yaml you’ll see this: + +```bash + Environment: + Variables: + DISABLE_SIGNAL_HANDLERS: true +``` + +This environment variable resolves some incompatibilites between GraalVM and the Amazon Lambda Custom Runtime environment. \ No newline at end of file diff --git a/samples/quarkus/pet-store/pom.xml b/samples/quarkus/pet-store/pom.xml new file mode 100644 index 000000000..0147be882 --- /dev/null +++ b/samples/quarkus/pet-store/pom.xml @@ -0,0 +1,141 @@ + + + 4.0.0 + com.amazonaws.serverless.sample + serverless-quarkus-example + 1.0-SNAPSHOT + + 3.8.1 + true + 1.8 + 1.8 + UTF-8 + UTF-8 + 1.0.1.Final + quarkus-universe-bom + io.quarkus + 1.0.1.Final + 2.22.1 + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-amazon-lambda-http + + + io.quarkus + quarkus-spring-web + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus-plugin.version} + + + + build + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + + + + + + + + native + + + native + + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.1.0 + + + zip-assembly + package + + single + + + function + + src/assembly/zip.xml + + false + false + + + + + + + + native + + + + diff --git a/samples/quarkus/pet-store/sam.native.yaml b/samples/quarkus/pet-store/sam.native.yaml new file mode 100644 index 000000000..54ca55d07 --- /dev/null +++ b/samples/quarkus/pet-store/sam.native.yaml @@ -0,0 +1,36 @@ + AWSTemplateFormatVersion: '2010-09-09' + Transform: AWS::Serverless-2016-10-31 + Description: AWS Serverless Quarkus HTTP - com.amazon.quarkus.demo::pet-store + Globals: + Api: + EndpointConfiguration: REGIONAL + BinaryMediaTypes: + - "*/*" + + Resources: + PetStoreNativeFunction: + Type: AWS::Serverless::Function + Properties: + Handler: not.used.in.provided.runtime + Runtime: provided + CodeUri: target/function.zip + MemorySize: 128 + Policies: AWSLambdaBasicExecutionRole + Tracing: Active + Timeout: 15 + Environment: + Variables: + DISABLE_SIGNAL_HANDLERS: true + Events: + GetResource: + Type: Api + Properties: + Path: /{proxy+} + Method: any + + Outputs: + PetStoreNativeApi: + Description: URL for application + Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/' + Export: + Name: PetStoreNativeApi diff --git a/samples/quarkus/pet-store/src/assembly/zip.xml b/samples/quarkus/pet-store/src/assembly/zip.xml new file mode 100644 index 000000000..ce2cb6421 --- /dev/null +++ b/samples/quarkus/pet-store/src/assembly/zip.xml @@ -0,0 +1,17 @@ + + lambda-package + + zip + + false + + + ${project.build.directory}${file.separator}${artifactId}-${version}-runner + / + bootstrap + 755 + + + diff --git a/samples/quarkus/pet-store/src/main/java/com/amazonaws/serverless/sample/quarkus/PetsController.java b/samples/quarkus/pet-store/src/main/java/com/amazonaws/serverless/sample/quarkus/PetsController.java new file mode 100644 index 000000000..571887850 --- /dev/null +++ b/samples/quarkus/pet-store/src/main/java/com/amazonaws/serverless/sample/quarkus/PetsController.java @@ -0,0 +1,63 @@ +package com.amazonaws.serverless.sample.quarkus; + +import com.amazonaws.serverless.sample.quarkus.model.Pet; +import com.amazonaws.serverless.sample.quarkus.model.PetData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.Optional; +import java.util.UUID; + +@RestController +public class PetsController { + + private PetData petData; + + @Autowired + public PetsController(PetData data) { + petData = data; + } + + @RequestMapping(path = "/pets", method = RequestMethod.POST) + public Pet createPet(@RequestBody Pet newPet) { + if (newPet.getName() == null || newPet.getBreed() == null) { + return null; + } + + Pet dbPet = newPet; + dbPet.setId(UUID.randomUUID().toString()); + return dbPet; + } + + @RequestMapping(path = "/pets", method = RequestMethod.GET) + public Pet[] listPets(@RequestParam("limit") Optional limit) { + int queryLimit = 10; + if (limit.isPresent()) { + queryLimit = limit.get(); + } + + Pet[] outputPets = new Pet[queryLimit]; + + for (int i = 0; i < queryLimit; i++) { + Pet newPet = new Pet(); + newPet.setId(UUID.randomUUID().toString()); + newPet.setName(petData.getRandomName()); + newPet.setBreed(petData.getRandomBreed()); + newPet.setDateOfBirth(petData.getRandomDoB()); + outputPets[i] = newPet; + } + + return outputPets; + } + + @RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET) + public Pet getPet(@RequestParam("petId") String petId) { + Pet newPet = new Pet(); + newPet.setId(UUID.randomUUID().toString()); + newPet.setBreed(petData.getRandomBreed()); + newPet.setDateOfBirth(petData.getRandomDoB()); + newPet.setName(petData.getRandomName()); + return newPet; + } + +} diff --git a/samples/quarkus/pet-store/src/main/java/com/amazonaws/serverless/sample/quarkus/model/Pet.java b/samples/quarkus/pet-store/src/main/java/com/amazonaws/serverless/sample/quarkus/model/Pet.java new file mode 100644 index 000000000..475213362 --- /dev/null +++ b/samples/quarkus/pet-store/src/main/java/com/amazonaws/serverless/sample/quarkus/model/Pet.java @@ -0,0 +1,50 @@ +package com.amazonaws.serverless.sample.quarkus.model; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.quarkus.runtime.annotations.RegisterForReflection; + +import java.util.Date; + +@RegisterForReflection +public class Pet { + private String id; + private String breed; + private String name; + private Date dateOfBirth; + + @JsonProperty("petId") + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getBreed() { + return breed; + } + + public void setBreed(String breed) { + this.breed = breed; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @JsonFormat(pattern = "YYYY-mm-dd") + public Date getDateOfBirth() { + return dateOfBirth; + } + + public void setDateOfBirth(Date dateOfBirth) { + this.dateOfBirth = dateOfBirth; + } + +} diff --git a/samples/quarkus/pet-store/src/main/java/com/amazonaws/serverless/sample/quarkus/model/PetData.java b/samples/quarkus/pet-store/src/main/java/com/amazonaws/serverless/sample/quarkus/model/PetData.java new file mode 100644 index 000000000..90b837d0d --- /dev/null +++ b/samples/quarkus/pet-store/src/main/java/com/amazonaws/serverless/sample/quarkus/model/PetData.java @@ -0,0 +1,102 @@ +package com.amazonaws.serverless.sample.quarkus.model; + +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; + +@Component +public class PetData { + private static List breeds = new ArrayList<>(); + static { + breeds.add("Afghan Hound"); + breeds.add("Beagle"); + breeds.add("Bernese Mountain Dog"); + breeds.add("Bloodhound"); + breeds.add("Dalmatian"); + breeds.add("Jack Russell Terrier"); + breeds.add("Norwegian Elkhound"); + } + + private static List names = new ArrayList<>(); + static { + names.add("Bailey"); + names.add("Bella"); + names.add("Max"); + names.add("Lucy"); + names.add("Charlie"); + names.add("Molly"); + names.add("Buddy"); + names.add("Daisy"); + names.add("Rocky"); + names.add("Maggie"); + names.add("Jake"); + names.add("Sophie"); + names.add("Jack"); + names.add("Sadie"); + names.add("Toby"); + names.add("Chloe"); + names.add("Cody"); + names.add("Bailey"); + names.add("Buster"); + names.add("Lola"); + names.add("Duke"); + names.add("Zoe"); + names.add("Cooper"); + names.add("Abby"); + names.add("Riley"); + names.add("Ginger"); + names.add("Harley"); + names.add("Roxy"); + names.add("Bear"); + names.add("Gracie"); + names.add("Tucker"); + names.add("Coco"); + names.add("Murphy"); + names.add("Sasha"); + names.add("Lucky"); + names.add("Lily"); + names.add("Oliver"); + names.add("Angel"); + names.add("Sam"); + names.add("Princess"); + names.add("Oscar"); + names.add("Emma"); + names.add("Teddy"); + names.add("Annie"); + names.add("Winston"); + names.add("Rosie"); + } + + public List getBreeds() { + return breeds; + } + + public List getNames() { + return names; + } + + public String getRandomBreed() { + return breeds.get(ThreadLocalRandom.current().nextInt(0, breeds.size() - 1)); + } + + public String getRandomName() { + return names.get(ThreadLocalRandom.current().nextInt(0, names.size() - 1)); + } + + public Date getRandomDoB() { + GregorianCalendar gc = new GregorianCalendar(); + + int year = ThreadLocalRandom.current().nextInt( + Calendar.getInstance().get(Calendar.YEAR) - 15, + Calendar.getInstance().get(Calendar.YEAR) + ); + + gc.set(Calendar.YEAR, year); + + int dayOfYear = ThreadLocalRandom.current().nextInt(1, gc.getActualMaximum(Calendar.DAY_OF_YEAR)); + + gc.set(Calendar.DAY_OF_YEAR, dayOfYear); + return gc.getTime(); + } +} diff --git a/samples/quarkus/pet-store/src/main/resources/application.properties b/samples/quarkus/pet-store/src/main/resources/application.properties new file mode 100644 index 000000000..ee9b09c98 --- /dev/null +++ b/samples/quarkus/pet-store/src/main/resources/application.properties @@ -0,0 +1,4 @@ +quarkus.native.container-build=true +quarkus.native.container-runtime=docker +quarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-native-image:19.2.1 +quarkus.log.level=INFO \ No newline at end of file