Front-End Web & Mobile

Building a Mobile Backend Using AWS Lambda

AWS Mobile SDKs for iOS and Android now supports AWS Lambda that make it easy to invoke Lambda functions. AWS Lambda is a compute service that allows you to run code in response to events. Starting today, you can now create AWS Lambda functions that respond to events from your application synchronously (in real time) as well as asynchronously. Functions responding synchronously operate with low latency so you can deliver dynamic, interactive experiences to your users. Combining this feature with SDK support, you can leverage AWS Lambda to create scalable, secure, and highly available custom backend for your mobile app without requiring management of any infrastructure. All you need to do is to write Lambda functions and invoke them from your mobile app using the AWS Mobile SDK.

When invoked through the mobile SDK, the Lambda function automatically has access to data about the device, app, and end user identity (context.clientContext and context.identity), making it easy to create rich, personalized responses to in-app activity.

Let’s do a quick walkthrough of invoking Lambda function from your mobile app:

Writing the Lambda Function

Let’s say you have the following Lambda function already defined on the AWS Lambda console with the name “hello”. (To see how to set up Lambda functions, please refer to the following guide)

exports.handler = function(payload, context) {
    console.log("Received event");
    context.succeed("Hello "+ payload.firstName + ". Your user ID is " + context.identity.cognitoIdentityId  + " and your platform is " + context.clientContext.client.platform);    
};

The Client Context is captured on the device by the SDK and delivered to the Lambda function so you can take platform or app specific decisions if required. The SDK generates the client context so no work is required by the developer. You can learn more about client context here.

The user identity originates from Amazon Cognito. When you use Amazon Cognito as your credential provider, it creates a unique identity ID for each user. This ID is passed along to your Lambda function, allowing you to implement personalized experiences in your function.

Invoking Lambda Function from an Android App

1. Define methods corresponding to Lambda functions

You can define an interface that describes the set of functions you want to invoke in Lambda. For each method, add a @LambdaFunction annotation. The implementation of this interface is provided to you by LambdaInvokerFactory.

public interface MyInterface {
    // Invoke lambda function "hello". The function name is the method name.
    @LambdaFunction
    String hello(NameInfo nameInfo);
}

As the example shows, you can define custom data types to be passed to the Lambda function. By default, the Android SDK uses LambdaJSONBinder that is backed by Gson for serialization/de-serialization. To learn how Java classes are serialized/de-serialized with Gson, see the following link. For this example, the Lambda function is passed NameInfo object as an argument.

public class NameInfo {

    String firstName;
    String lastName;

    public NameInfo(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

2. Initiate LambdaInvokerFactory

Next we will use Amazon Cognito to initialize LambdaInvokerFactory. When you use Amazon Cognito, your app is provided with temporary, limited-privilege credentials that it can use to access AWS resources. This means your app can access the resources it needs and that you can follow security best practices by not hardcoding credentials in your app.

AWSCredentialsProvider provider = new CognitoCachingCredentialsProvider(myActivity, <COGNITO_IDENTITY_POOL>, <REGION>);
LambdaInvokerFactory factory = new LambdaInvokerFactory(myActivity, <REGION>, provider);

3. Create Proxy Object and Invoke Lambda function

Now you can create a proxy object using the interface. Invoking the Lambda function is as simple as calling a Java method.

MyInterface invoker = factory.build(MyInterface.class);
String result = invoker.hello(nameInfo);

Note that his will result in a network call so do not invoke the Lambda function from the main thread.

4. Handle Failure

In case Lambda fails to invoke the required function, LambdaFunctionException will be thrown:

try {
    result = invoker.hello(nameInfo);
} catch (LambdaFunctionException lfe) {
    Log.e(TAG, "Failed to execute echo", lfe);
}

Custom Data Binders

Often you may want to perform custom serialization/de-serialization of input parameters and responses. To do this you can provide your own data binder by implementing LambdaDataBinder.

public class JacksonDataBinder implements LambdaDataBinder {
   private final ObjectMapper mapper;

   public JacksonDataBinder() {
      mapper = new ObjectMapper();
      mapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
   }

  @Override
  public  T deserialize(byte[] content, Class clazz) {
     try {
         return mapper.readValue(content, clazz);
     } catch (IOException e) {
         throw new AmazonClientException("Failed to deserialize content", e);
     }
   }

   @Override
   public byte[] serialize(Object object) {
      try {
          return mapper.writeValueAsBytes(object);
      } catch (IOException e) {
          throw new AmazonClientException("Failed to serialize object", e);
      }
  }
}

// create a Lambda proxy object
MyInterface myInterface = lambdaInvokerFactory.build(MyInterface.class, new JacksonDataBinder());

Invoking Lambda from an iOS App

1. Set Up AWSCognitoCredentialsProvider

Set up a credentials provider in the ‘- application:didFinishLaunchingWithOptions:’ application delegate.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    AWSCognitoCredentialsProvider *credentialsProvider = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:AWSRegionUSEast1
                                                                                                    identityPoolId:@"YourCognitoIdentityPoolId"];
    AWSServiceConfiguration *configuration = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionUSWest2
                                                                         credentialsProvider:credentialsProvider];
    AWSServiceManager.defaultServiceManager.defaultServiceConfiguration = configuration;

    return YES;
}

2. Call an AWS Lambda function

Invoking the Lambda function is as simple as passing @”hello” as a function name and @{@”firstName” : @”Your Name”, @”identity” : @”My ID”} as a parameters:

AWSLambdaInvoker *lambdaInvoker = [AWSLambdaInvoker defaultLambdaInvoker];

[[lambdaInvoker invokeFunction:@"hello"
                    parameters:@{@"firstName" : @"Your Name",
                                 @"identity" : @"My ID"}] continueWithBlock:^id(BFTask *task) {
    if (task.error) {
        NSLog(@"Error: %@", task.error);
    }
    if (task.exception) {
        NSLog(@"Exception: %@", task.exception);
    }
    if (task.result) {
        NSLog(@"Result: %@", task.result);
        NSString *result = task.result;
        // Do something with result.
    }
    return nil;
}];

The hello Lambda function returns a string. You can cast task.result to an NSString.

3. Handle Failure

When a Lambda function fails, it returns an NSError object.

if (task.error) {
    NSLog(@"Error: %@", task.error);
    NSLog(@"Function error: %@", task.error.userInfo[AWSLambdaInvokerFunctionErrorKey]);
}

When AWS Lambda fails to execute the function, the domain of the error object is AWSLambdaErrorDomain, and the error code is one of AWSLambdaErrorType based on the reason for the failure. When the developer defined function returns an error, the domain of the error object is AWSLambdaInvokerErrorDomain, and the error code is AWSLambdaInvokerErrorTypeFunctionError.

Resources

Here are some resources to help you get started with AWS Lambda