├── .gitignore ├── README.md ├── pom.xml ├── src └── main │ ├── java │ └── com │ │ ├── cagataygurturk │ │ └── example │ │ │ ├── config │ │ │ └── SpringConfig.java │ │ │ ├── lambda │ │ │ ├── AbstractHandler.java │ │ │ └── MainHandler.java │ │ │ └── services │ │ │ ├── AnotherService.java │ │ │ └── Service.java │ │ └── example │ │ ├── config │ │ └── SpringConfig.java │ │ ├── lambda │ │ ├── AbstractHandler.java │ │ └── MainHandler.java │ │ └── services │ │ ├── AnotherService.java │ │ └── Service.java │ └── resources │ └── log4j.properties └── tests └── com └── cagataygurturk └── example └── services └── ServiceTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | 4 | ## File-based project format: 5 | *.iws 6 | 7 | ## Plugin-specific files: 8 | 9 | # IntelliJ 10 | /out/ 11 | 12 | # mpeltonen/sbt-idea plugin 13 | .idea_modules/ 14 | 15 | # JIRA plugin 16 | atlassian-ide-plugin.xml 17 | 18 | aws 19 | target -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS Lambda Boilerplate for JAVA and Spring IoC 2 | 3 | Do you miss JAVA, its great features and all Spring framework sugar in AWS Lambda? Use this boilerplate code to author JAVA functions in Lambda and enjoy all features of Spring IoC. It also supports local debugging of JAVA functions. 4 | 5 | ## Usage 6 | 7 | The code is already documented. **MainHandler** is the main entrance point of your lambda function. **services** package includes Spring Beans. For default, MainHandler fetches **Service** Bean from IoC container but once you have Service instance you can use Autowiring features. 8 | 9 | ### Local running 10 | 11 | In root folder fire 12 | 13 | ``` 14 | mvn compile exec:java 15 | ``` 16 | 17 | You can configure your IDE to run com.example.lambda.local.LocalRunner as Main Class and com.cagataygurturk.lambda.MainHandler as program argument to debug locally your function. 18 | 19 | ### Deployment 20 | 21 | In root folder fire 22 | 23 | ``` 24 | mvn package -Denv=production 25 | ``` 26 | 27 | This creates a JAR package in target folder. 28 | 29 | As you can see in `pom.xml`, "-Denv=production" activates production profile and it excludes `aws-lambda-local-runner` dependency from deployment package in order to get rid of a unnecessary dependency which is not needed in production environment. Forgetting this does not affect the project but it increases JAR package size. 30 | 31 | You can upload the created JAR folder to AWS Lambda console. Handler function should be configured to `com.cagataygurturk.example.lambda.MainHandler`. 32 | 33 | ## More about the topic 34 | 35 | - Check out [Lambada Framework](https://github.com/lambadaframework/lambadaframework) for seamless AWS Lambda and JAVA integration. 36 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.cagataygurturk 6 | aws-lambda-java-boilerplate 7 | jar 8 | 0.0.1 9 | aws-lambda-java-boilerplate 10 | 11 | 12 | 4.2.4.RELEASE 13 | 14 | 15 | 16 | 17 | production 18 | 19 | provided 20 | 21 | 22 | 23 | env 24 | production 25 | 26 | 27 | 28 | 29 | test 30 | 31 | true 32 | 33 | 34 | compile 35 | 36 | 37 | 38 | 39 | 40 | 43 | 44 | cagatay-gurturk 45 | http://maven.cagataygurturk.com/releases 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | org.springframework 54 | spring-context 55 | ${spring.version} 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | com.cagataygurturk 64 | aws-lambda-local-runner 65 | 0.0.1 66 | 71 | ${localrunner.scope} 72 | 73 | 74 | 75 | 76 | 77 | com.amazonaws 78 | aws-lambda-java-core 79 | 1.1.0 80 | 81 | 82 | 83 | com.amazonaws 84 | aws-lambda-java-log4j 85 | 1.0.0 86 | 87 | 88 | 89 | 90 | 91 | junit 92 | junit 93 | 4.12 94 | test 95 | 96 | 97 | 98 | 99 | 103 | 104 | org.apache.maven.plugins 105 | maven-shade-plugin 106 | 2.3 107 | 108 | false 109 | 110 | 111 | 112 | package 113 | 114 | shade 115 | 116 | 117 | 118 | 119 | 124 | 125 | org.codehaus.mojo 126 | exec-maven-plugin 127 | 1.2.1 128 | 129 | com.cagataygurturk.lambda.LocalRunner 130 | 131 | com.cagataygurturk.example.lambda.MainHandler 132 | 133 | 134 | 135 | 136 | 137 | maven-compiler-plugin 138 | 3.2 139 | 140 | 1.8 141 | 1.8 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /src/main/java/com/cagataygurturk/example/config/SpringConfig.java: -------------------------------------------------------------------------------- 1 | package com.cagataygurturk.example.config; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | 7 | @Configuration 8 | @ComponentScan("com.cagataygurturk") 9 | public class SpringConfig { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/cagataygurturk/example/lambda/AbstractHandler.java: -------------------------------------------------------------------------------- 1 | package com.cagataygurturk.example.lambda; 2 | 3 | import org.springframework.context.ApplicationContext; 4 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | import java.lang.reflect.ParameterizedType; 8 | 9 | /** 10 | * This AbstractHandler class includes two generic functionality: 11 | *

12 | * - Local runner uses getExampleEvent() method to find the example event that you use during your events. 13 | * 14 | * @param 15 | * @link https://github.com/cagataygurturk/aws-lambda-local-runner 16 | *

17 | * - It configures automatically Spring IoC. To enable this support Concrete handler classes extends this class with T type 18 | * which should implement @Configuration interface. 19 | * @see MainHandler 20 | *

21 | *

22 | * Every lambda handler class should extend this abstract base class and normally you do not touch this class 23 | */ 24 | @SuppressWarnings("unused") 25 | public abstract class AbstractHandler { 26 | 27 | /** 28 | * Spring IOC application context 29 | */ 30 | private ApplicationContext applicationContext; 31 | 32 | /** 33 | * Every handler can override this String variable to provide an example event JSON for local running 34 | * 35 | * @link https://github.com/cagataygurturk/aws-lambda-local-runner 36 | */ 37 | protected String exampleEvent = "{}"; 38 | 39 | public AbstractHandler() { 40 | /** 41 | * Gets config class to create an Application context 42 | */ 43 | Class typeParameterClass = ((Class) ((ParameterizedType) getClass() 44 | .getGenericSuperclass()) 45 | .getActualTypeArguments()[0]); 46 | 47 | /** 48 | * Check if T has @Configuration annotation, 49 | * if no throws an exception 50 | */ 51 | if (!typeParameterClass.isAnnotationPresent(Configuration.class)) { 52 | throw new RuntimeException(typeParameterClass + " is not a @Configuration class"); 53 | } 54 | 55 | /** 56 | * Create Spring application context 57 | */ 58 | applicationContext = new AnnotationConfigApplicationContext(typeParameterClass); 59 | } 60 | 61 | /** 62 | * Use this getter to access to Spring Application Context 63 | * 64 | * @return ApplicationContext 65 | */ 66 | public ApplicationContext getApplicationContext() { 67 | return applicationContext; 68 | } 69 | 70 | /** 71 | * This method is only used by local lambda runner. 72 | * 73 | * @return String 74 | */ 75 | public String getExampleEvent() { 76 | return exampleEvent; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/cagataygurturk/example/lambda/MainHandler.java: -------------------------------------------------------------------------------- 1 | package com.cagataygurturk.example.lambda; 2 | 3 | import com.amazonaws.services.lambda.runtime.Context; 4 | import com.amazonaws.services.lambda.runtime.RequestHandler; 5 | import com.cagataygurturk.example.config.SpringConfig; 6 | import com.cagataygurturk.example.services.Service; 7 | import org.apache.log4j.Logger; 8 | 9 | /** 10 | * Handler class should extend AbstractHandler abstract class 11 | * T should be a Spring @Configuration class for Spring DI manager 12 | */ 13 | @SuppressWarnings("unused") 14 | public class MainHandler 15 | extends AbstractHandler 16 | implements RequestHandler { 17 | 18 | public MainHandler() { 19 | /** 20 | * Set example event for local running 21 | */ 22 | exampleEvent = "{\"firstName\":\"John\",\"lastName\":\"John\"}"; 23 | } 24 | 25 | /** 26 | * Standard logger. For logger configuration check resources/log4j.properties file. 27 | * 28 | * @link http://docs.aws.amazon.com/lambda/latest/dg/java-logging.html 29 | */ 30 | static final Logger log = Logger.getLogger(MainHandler.class); 31 | 32 | 33 | /** 34 | * Request class is a POJO. You should modify this class according to your needs. 35 | *

36 | * Event json that lambda function got is automatically serialized to this POJO. For more details see Lambda documentation: 37 | * 38 | * @link http://docs.aws.amazon.com/lambda/latest/dg/java-handler-io-type-pojo.html 39 | */ 40 | public static class Request { 41 | String firstName; 42 | String lastName; 43 | 44 | public String getFirstName() { 45 | return firstName; 46 | } 47 | 48 | public void setFirstName(String firstName) { 49 | this.firstName = firstName; 50 | } 51 | 52 | public String getLastName() { 53 | return lastName; 54 | } 55 | 56 | public void setLastName(String lastName) { 57 | this.lastName = lastName; 58 | } 59 | 60 | public Request(String firstName, String lastName) { 61 | this.firstName = firstName; 62 | this.lastName = lastName; 63 | } 64 | 65 | public Request() { 66 | } 67 | } 68 | 69 | /** 70 | * Response is also a POJO that will handler return and 71 | * Lambda runtime automatically serializes it to JSON. Again see the documentation. 72 | * 73 | * @link http://docs.aws.amazon.com/lambda/latest/dg/java-handler-io-type-pojo.html 74 | */ 75 | public static class Response { 76 | String greetings; 77 | 78 | public String getGreetings() { 79 | return greetings; 80 | } 81 | 82 | public void setGreetings(String greetings) { 83 | this.greetings = greetings; 84 | } 85 | 86 | public Response(String greetings) { 87 | this.greetings = greetings; 88 | } 89 | 90 | public Response() { 91 | } 92 | 93 | } 94 | 95 | 96 | /** 97 | * Main handler method is invoked when Lambda function is invoked. You should configure the name of this method in the AWS Console. 98 | * In this example, the value would be com.cagataygurturk.lambda.MainHandler. As we implement RequestHandler interface Lambda runtime 99 | * detects this method automatically and invokes it. 100 | *

101 | *

102 | *

103 | * As a best practice, this method should be kept very short and all the business logic should sit 104 | * in "Service" instance that we will fetch from Spring IoC container and will enjoy from all 105 | * Spring IoC features. 106 | *

107 | * Lambda specific code ends here and beginning from this point old good JAVA starts. 108 | * 109 | * @param request Request object 110 | * @param context Context object 111 | * @return Response 112 | * @throws RuntimeException 113 | * @see RequestHandler 114 | */ 115 | public Response handleRequest(Request request, Context context) 116 | throws RuntimeException { 117 | 118 | /** 119 | * BusinessService is where all our business logic sits. 120 | */ 121 | Service businessService = getApplicationContext().getBean(Service.class); 122 | return new Response(businessService.getText(request.getFirstName() + " " + request.getLastName())); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/com/cagataygurturk/example/services/AnotherService.java: -------------------------------------------------------------------------------- 1 | package com.cagataygurturk.example.services; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | @Component 6 | public class AnotherService { 7 | 8 | public String getText(String text) { 9 | return "Hi " + text; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/cagataygurturk/example/services/Service.java: -------------------------------------------------------------------------------- 1 | package com.cagataygurturk.example.services; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Component; 5 | 6 | @Component 7 | public class Service { 8 | 9 | /** 10 | * Autowiring another Spring Bean 11 | */ 12 | @Autowired 13 | AnotherService anotherService; 14 | 15 | public String getText(String text) { 16 | return anotherService.getText(text); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/example/config/SpringConfig.java: -------------------------------------------------------------------------------- 1 | package com.example.config; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | 7 | @Configuration 8 | @ComponentScan("com.example") 9 | public class SpringConfig { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/example/lambda/AbstractHandler.java: -------------------------------------------------------------------------------- 1 | package com.example.lambda; 2 | 3 | import org.springframework.context.ApplicationContext; 4 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | import java.lang.reflect.ParameterizedType; 8 | 9 | /** 10 | * Every lambda handler class should extend this abstract base class 11 | *

12 | * T type parameter is used to determine the Spring @Configuration class, thus T should be a class that declares @Configuration annotation. Otherwise, an exception is thrown. 13 | * 14 | * @param 15 | */ 16 | @SuppressWarnings("unused") 17 | public abstract class AbstractHandler { 18 | 19 | /** 20 | * Spring IOC application context 21 | */ 22 | private ApplicationContext applicationContext; 23 | 24 | /** 25 | * Every handler can override this String variable to provide an example event JSON for local running 26 | */ 27 | protected String exampleEvent = "{}"; 28 | 29 | public AbstractHandler() { 30 | /** 31 | * Gets config class to create an Application context 32 | */ 33 | Class typeParameterClass = ((Class) ((ParameterizedType) getClass() 34 | .getGenericSuperclass()) 35 | .getActualTypeArguments()[0]); 36 | 37 | /** 38 | * Check if T has @Configuration annotation 39 | */ 40 | if (!typeParameterClass.isAnnotationPresent(Configuration.class)) { 41 | throw new RuntimeException(typeParameterClass + " is not a @Configuration class"); 42 | } 43 | 44 | /** 45 | * Create Spring application context 46 | */ 47 | applicationContext = new AnnotationConfigApplicationContext(typeParameterClass); 48 | } 49 | 50 | public ApplicationContext getApplicationContext() { 51 | return applicationContext; 52 | } 53 | 54 | public String getExampleEvent() { 55 | return exampleEvent; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/example/lambda/MainHandler.java: -------------------------------------------------------------------------------- 1 | package com.example.lambda; 2 | 3 | import com.amazonaws.services.lambda.runtime.Context; 4 | import com.amazonaws.services.lambda.runtime.RequestHandler; 5 | import com.example.config.SpringConfig; 6 | import com.example.services.Service; 7 | import org.apache.log4j.Logger; 8 | 9 | /** 10 | * Handler class should extend AbstractHandler abstract class 11 | * T should be a Spring @Configuration class for Spring DI manager 12 | */ 13 | @SuppressWarnings("unused") 14 | public class MainHandler 15 | extends AbstractHandler 16 | implements RequestHandler { 17 | 18 | public MainHandler() { 19 | /** 20 | * Set example event for local running 21 | */ 22 | exampleEvent = "{\"firstName\":\"John\",\"lastName\":\"John\"}"; 23 | } 24 | 25 | /** 26 | * Standard logger. For logger configuration check resources/log4j.properties file 27 | */ 28 | static final Logger log = Logger.getLogger(MainHandler.class); 29 | 30 | 31 | /** 32 | * Request class is a POJO 33 | * Event json that lambda function got is automatically serialized to this POJO 34 | */ 35 | public static class Request { 36 | String firstName; 37 | String lastName; 38 | 39 | public String getFirstName() { 40 | return firstName; 41 | } 42 | 43 | public void setFirstName(String firstName) { 44 | this.firstName = firstName; 45 | } 46 | 47 | public String getLastName() { 48 | return lastName; 49 | } 50 | 51 | public void setLastName(String lastName) { 52 | this.lastName = lastName; 53 | } 54 | 55 | public Request(String firstName, String lastName) { 56 | this.firstName = firstName; 57 | this.lastName = lastName; 58 | } 59 | 60 | public Request() { 61 | } 62 | } 63 | 64 | /** 65 | * Response is also a POJO that will handler return and 66 | * Lambda runtime automatically serializes it to JSON 67 | */ 68 | public static class Response { 69 | String greetings; 70 | 71 | public String getGreetings() { 72 | return greetings; 73 | } 74 | 75 | public void setGreetings(String greetings) { 76 | this.greetings = greetings; 77 | } 78 | 79 | public Response(String greetings) { 80 | this.greetings = greetings; 81 | } 82 | 83 | public Response() { 84 | } 85 | 86 | } 87 | 88 | 89 | /** 90 | * Main handler method is invoked when Lambda function is invoked. 91 | *

92 | * As a best practice, this method should be kept very short and all the business logic should sit 93 | * in "Service" instance that we will fetch from Spring IoC container and will enjoy from all 94 | * Spring IoC features. 95 | *

96 | * Lambda specific code ends here and beginning from this point old good JAVA starts. 97 | * 98 | * @param request 99 | * @param context 100 | * @return Response 101 | * @throws RuntimeException 102 | */ 103 | public Response handleRequest(Request request, Context context) 104 | throws RuntimeException { 105 | 106 | Service businessService = getApplicationContext().getBean(Service.class); 107 | return new Response(businessService.getText(request.getFirstName() + " " + request.getLastName())); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/com/example/services/AnotherService.java: -------------------------------------------------------------------------------- 1 | package com.example.services; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | @Component 6 | public class AnotherService { 7 | 8 | public String getText(String text) { 9 | return "Hi " + text; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/example/services/Service.java: -------------------------------------------------------------------------------- 1 | package com.example.services; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Component; 5 | 6 | @Component 7 | public class Service { 8 | 9 | @Autowired 10 | AnotherService service2; 11 | 12 | public String getText(String text) { 13 | return service2.getText(text); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log = . 2 | log4j.rootLogger = DEBUG, LAMBDA 3 | 4 | #Define the LAMBDA appender 5 | log4j.appender.LAMBDA=com.amazonaws.services.lambda.runtime.log4j.LambdaAppender 6 | log4j.appender.LAMBDA.layout=org.apache.log4j.PatternLayout 7 | log4j.appender.LAMBDA.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss} <%X{AWSRequestId}> %-5p %c{1}:%L - %m%n -------------------------------------------------------------------------------- /tests/com/cagataygurturk/example/services/ServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.cagataygurturk.example.services; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | 8 | public class ServiceTest { 9 | @Test 10 | public void CreateTest() { 11 | Service service = new Service(); 12 | assertTrue(true); 13 | } 14 | } --------------------------------------------------------------------------------