Book Image

Serverless Programming Cookbook

By : Heartin Kanikathottu
Book Image

Serverless Programming Cookbook

By: Heartin Kanikathottu

Overview of this book

Managing physical servers will be a thing of the past once you’re able to harness the power of serverless computing. If you’re already prepped with the basics of serverless computing, Serverless Programming Cookbook will help you take the next step ahead. This recipe-based guide provides solutions to problems you might face while building serverless applications. You'll begin by setting up Amazon Web Services (AWS), the primary cloud provider used for most recipes. The next set of recipes will cover various components to build a Serverless application including REST APIs, database, user management, authentication, web hosting, domain registration, DNS management, CDN, messaging, notifications and monitoring. The book also introduces you to the latest technology trends such as Data Streams, Machine Learning and NLP. You will also see patterns and practices for using various services in a real world application. Finally, to broaden your understanding of Serverless computing, you'll also cover getting started guides for other cloud providers such as Azure, Google Cloud Platform and IBM cloud. By the end of this book, you’ll have acquired the skills you need to build serverless applications efficiently using various cloud offerings.
Table of Contents (12 chapters)

Dev Practices – dependency injection and unit testing

In this recipe, I will implement some of the common dev practices for creating Lambdas, such as using lightweight frameworks for dependency injection and writing unit tests for your code.

For dependency injection, we will use Guice, which is one of the dependency injection (IoC) frameworks suggested by AWS at https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html. For unit testing, we will use JUnit and Mockito libraries.

Getting ready

You need an active AWS account, and read and follow the Getting started section of the recipes, Your first AWS Lambda and Your first Lambda with AWS CLI to set up Java, Maven, the parent project, serverless-cookbook-parent-aws-java, and AWS CLI, and other code usage guidelines.

This recipe also assumes you are familiar with general software development concepts and practices such as dependency injection, unit testing, and coding to interfaces. Familiarity with libraries such as JUnit and Mockito will be good to have.

Code refactoring

We will be improving the code we created in the Using AWS SDK, Amazon CloudWatch and AWS CLI with Lambda recipe. Before doing Dependency Injection, you need to refactor your code to follow the principle of programming to interfaces.

Refactor the service class into an interface and its implementation. I will also add lombok's @AllArgsConstructor annotation to generate an all args constructor, which will be used during unit testing to inject the mock object.

  1. We will first create an interface IAMService:
/**
* Interface for IAM operations.
*/
public interface IAMService {

We will define the corresponding implementation as IAMServiceImpl:

/**
* Implementation of {@link IAMService}.
*/
@AllArgsConstructor
public class IAMServiceImpl implements IAMService {
  1. Extract the methods as well, and then replace the usage of the implementation with an interface:
private IAMService service;

public MyLambdaHandler() {
service = new IAMServiceImpl();
}
Most IDEs will provide refactoring support to extract an interface from an implementation. IDEs will also help you in replacing the usages of your implementation with interface wherever possible.

How to do it...

Let us do dependency injection with Guice, which is a lightweight framework suggested by AWS.

  1. Add Maven dependency for Guice:
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>4.2.0</version>
</dependency>
  1. Create the Guice configuration class to bind interfaces to implementation:
public class ApplicationModule extends AbstractModule {
protected final void configure() {
bind(IAMService.class).to(IAMServiceImpl.class);
}
}
  1. Configure the handler class for using Guice:
public final class MyLambdaHandler implements RequestHandler<IAMOperationRequest, IAMOperationResponse> {

private static final Injector INJECTOR =
Guice.createInjector(new ApplicationModule());

private IAMService service;

public MyLambdaHandler() {
INJECTOR.injectMembers(this);
Objects.requireNonNull(service);
}

@Inject
public void setService(final IAMService service) {
this.service = service;
}

I created a static Injector class and initialized it with our Guice configuration class. I added a default constructor to add this class to be injected by Guice. Objects.requireNonNull verifies if the implementation was injected successfully. I annotated it with Java's @Inject annotation for Guice to inject dependency.

Let us write unit tests for our code.

  1. Add Maven dependency for JUnit and Mockito:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.21.0</version>
<scope>test</scope>
</dependency>
  1. Create a simple test class for the handler that checks if the service implementation is injected:
package tech.heartin.books.serverlesscookbook;

import org.junit.Test;

public class MyLambdaHandlerTest {
@Test
public void testDependencies() throws Exception {
MyLambdaHandler testHandler = new MyLambdaHandler();
}
}
  1. Create a test class for the service class that uses Mockito to mock AWS calls:
@RunWith(MockitoJUnitRunner.class)
public class IAMServiceImplTest {

@Mock
private AmazonIdentityManagement iamClient;

private IAMService service;

@Before
public void setUp() {
service = new IAMServiceImpl(iamClient);
Objects.requireNonNull(service);
}
// Actual tests not shown here
}
  1. Add the test method for create user:
@Test
public void testCreateUser() {
IAMOperationResponse expectedResponse = new IAMOperationResponse(
"Created user test_user", null);
when(iamClient.createUser(any()))
.thenReturn(new CreateUserResult()
.withUser(new User().withUserName("test_user")));
IAMOperationResponse actualResponse
= service.createUser("test_user");
Assert.assertEquals(expectedResponse, actualResponse);
}
  1. Add the test method to check user:
@Test
public void testCheckUser() {
IAMOperationResponse expectedResponse = new IAMOperationResponse(
"User test_user exist", null);
when(iamClient.listUsers(any()))
.thenReturn(getListUsersResult());
IAMOperationResponse actualResponse
= service.checkUser("test_user");
Assert.assertEquals(expectedResponse, actualResponse);
}

private ListUsersResult getListUsersResult() {
ListUsersResult result = new ListUsersResult();
result.getUsers().add(new User().withUserName("test_user"));
  1. Add the test method to delete user:
@Test
public void testDeleteUser() {
IAMOperationResponse expectedResponse = new IAMOperationResponse(
"Deleted user test_user", null);
when(iamClient.deleteUser(any()))
.thenReturn(new DeleteUserResult());
IAMOperationResponse actualResponse
= service.deleteUser("test_user");
Assert.assertEquals(expectedResponse, actualResponse);
}
  1. To Package, deploy, and verify, follow the Using AWS SDK, Amazon CloudFormation and AWS CLI with Lambda recipe, and package, deploy, and verify by invoking the Lambda.
In real-world projects, you may follow the Test Driven Development (TDD) principle and write tests before actual code.

How it works...

We added a lightweight dependency injection framework, Guice, and modified code to incorporate it. We also used JUnit and Mockito to do unit testing of the code. Going deep into the working of Guice, JUnit, or Mockito is outside the scope of this book. But, you may ask any questions on the open source repository for the project (given in the introduction in Chapter 1, Getting Started with Serverless Computing on AWS).

There's more...

You may also use Dagger instead of Guice for dependency injection. Dagger is also a recommended framework from AWS for lightweight dependency injection. You can technically use Spring for dependency injection, but it is not recommended because of its bigger size.

You may use TestNG instead of JUnit for unit testing. TestNG provides additional features such as DataProviders. DataProviders allow you to supply an array with all possible inputs and their expected values for a single test method. With JUnit, you will have to write a test method per input combination. You may also use Hamcrest to create more flexible expressions in tests.

See also

  • You may refer to other books at PacktPub to become familiar with the dependency injection and testing frameworks.