AWS Lambda APIs using Micronaut along with basic CI & CD (Gitlab CI) steps.

AWS Lambda APIs using Micronaut along with basic CI  & CD (Gitlab CI) steps.

Just to begin AWS Lambda is server less architecture, which allows you to run the code without managing the server and cost is pay per use, usually its based on number of invocation and execution time.

Usually  lambda  is preferred for small task or to have platform specific trigger. But when we need to maintain the entire application it becomes tedious, For example if we do it  via console, First we need to create the function and upload the code to S3, and set up the HTTP gateway trigger. so each time when you update the code it becomes very difficult and time consuming,  just imagine for 50 URLs of a project. so when we are starting with lambda Function or any server less design, it better to start with  Automated deployment  as bare minimum instead of   spending most of the time  in deploying or playing inside the Cloud console.

So when develop any hobby project, minimum CI & CD pipelines is mandatory for me. And I prefer GitLab as my CI tool. So this preference led  me to find easier ways to deploy the the java application in to AWS lambda. So found these tools Micronaut as web framework, server less Framework for managing deployment and GitLab for CI & CD.

Lets Start with Micronaut, I am a spring advocate but I choose Micronaut due to its features similar to spring, light weight, low memory foot print, and quick start up time, as we know cold start up time is concern in Aws lambda functions.

Serverless framework is Open Source, lets you develop and deploy serverless applications to AWS, Azure, GCP & more. and it has very good documentation to understand  to begin with.

GitLab CI as this is integrated with my repo provider so I did not to use another tool.

Step 1: Create the Dummy controller or two in Micronut

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.ashrithgn.link_note.news</groupId>
  <artifactId>news</artifactId>
  <version>0.1</version>
  <packaging>${packaging}</packaging>

  <parent>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-parent</artifactId>
    <version>2.3.1</version>
  </parent>

  <properties>
    <packaging>jar</packaging>
    <jdk.version>8</jdk.version>
    <release.version>8</release.version>
    <micronaut.version>2.3.1</micronaut.version>
    <exec.mainClass>com.ashrithgn.link_note.news.Application</exec.mainClass>
    <micronaut.runtime>lambda</micronaut.runtime>
  </properties>

  <repositories>
    <repository>
      <id>central</id>
      <url>https://repo.maven.apache.org/maven2</url>
    </repository>
    <repository>
      <id>jcenter.bintray.com</id>
      <url>https://jcenter.bintray.com</url>
    </repository>
  </repositories>

  <dependencies>
    <dependency>
      <groupId>io.micronaut</groupId>
      <artifactId>micronaut-inject</artifactId>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>io.micronaut</groupId>
      <artifactId>micronaut-validation</artifactId>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>io.micronaut</groupId>
      <artifactId>micronaut-http-client</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.micronaut</groupId>
      <artifactId>micronaut-runtime</artifactId>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>javax.annotation</groupId>
      <artifactId>javax.annotation-api</artifactId>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>io.micronaut.aws</groupId>
      <artifactId>micronaut-function-aws-api-proxy</artifactId>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>io.micronaut.aws</groupId>
      <artifactId>micronaut-function-aws-api-proxy-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.micronaut.test</groupId>
      <artifactId>micronaut-test-junit5</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.micronaut</groupId>
      <artifactId>micronaut-http-client</artifactId>
    </dependency>
      <dependency>
          <groupId>io.micronaut</groupId>
          <artifactId>micronaut-http-client-core</artifactId>
      </dependency>
  </dependencies>

  <build>
    <finalName>news</finalName>
    <plugins>
      <plugin>
        <groupId>io.micronaut.build</groupId>
        <artifactId>micronaut-maven-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <!-- Uncomment to enable incremental compilation -->
          <!-- <useIncrementalCompilation>false</useIncrementalCompilation> -->
          <annotationProcessorPaths combine.children="append">
          </annotationProcessorPaths>
          <compilerArgs>
            <arg>-Amicronaut.processing.group=com.ashrithgn</arg>
            <arg>-Amicronaut.processing.module=news</arg>
          </compilerArgs>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.1.0</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

Application start up file of Micronaut

public class Application extends MicronautLambdaHandler {

    public Application() throws ContainerInitializationException {
    }

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

Sample Controller

@Controller("/news")
public class NewsController  {

    @Get("/categories")
    public NewsCategories categories() {
        return new NewsCategories();
    }
    
    @Get("/categories")
    public NewsFeeds feeds() {
        return new NewsFeeds();
    }

}

There is another way writing lambda as Function model in Micronaut, i choose Application model as it does not create different lambda functions for each end points, So it is easier to maintain, but there is a small hit in boot up time for every cold start.

Step 2: Setting up Serverless Framework

Installing  the serverless (on ubuntu)

sudo apt install nodejs
sudo apt install npm
sudo npm install -g serverless

Preparing serverless.yaml

This is yaml file  more  or like steps and rules for the deployment. create in the root of the project, or the place where ever the artifacts are accessible.

service: <project-name> # provide the project name
provider:
  name: aws
  runtime: java8

package:
  artifact: <path of the jar file> # provide path of the jar file

functions:
  get_news_categories:
    handler: <package-name>.Application
    events:
    - http:
        path: news/categories
        method: GET # Http metodod [GET,POST,etc]
        cors: true # allows cors
    - http:
        path: news/categories
        method: GET
        cors:
          origin: '*'
          headers:
            - Auth # to provide the custom custom header
          allowCredentials: false

 Adding Aws Credentials to system environment  variable

export AWS_ACCESS_KEY_ID=<VALUE>
export AWS_SECRET_ACCESS_KEY=<****************>

Make sure the AWs use has these permission policy IAMFullAccess, AmazonS3FullAccess, CloudWatchLogsFullAccess , AmazonAPIGatewayAdministrator, AWSCloudFormationFullAccess, AWSLambda_FullAccess.

CI & CD : I am using the the GitLab CI, instead u can use any CI tool. For build pipeline use `mvn clean package shade:shade` to generate the artifact. to deploy deploy u can use `serverless deploy --stage production --verbose`.

Git lab CI & CD example

image: node:latest

stages:
  - package
  - deploy

mvn_build:
  stage: package
  image: maven:3-jdk-8
  script: "mvn package shade:shade"
  artifacts:
    paths:
      - target/news.jar
production:
  stage: deploy
  before_script:
    - npm config set prefix /usr/local
    - npm install -g serverless
  script:
    - serverless deploy --stage production --verbose
  environment: production