Customizing a BaaS API, Serverless

Christoph Neijenhuis
commercetools tech
Published in
5 min readFeb 20, 2017

--

A hosted Backend-as-a-Service can be a great time- and cost-saver, because one doesn’t have to do Ops. However, unlike in a self-hosted scenario, we can’t edit or extend the code. If we need to write custom code, we don’t want to self-host that code either.

In this blogpost we want to explore how to write a wrapper with custom code around the API without doing Ops. We’ll use the commercetools API and AWS Lambda functions. The pattern is applicable for other BaaS APIs and Serverless technologies, too.

Use Cases

We use APIs to super-charge our development, because those APIs implement a lot of the business functionality we need. But usually they don’t cover 100% of what we need. Often multiple clients use a Backend-as-a-Service API. For example a web server, an Android app, an iOS app and a chatbot can use the commercetools API. We want to implement our business functionality independently of those clients. Doing so would be a lot of overhead, and we can’t force all clients, such as mobile apps, to update.

Some examples for applicable use cases we see at commercetools:

  • Add data before saving. E.g. select the closest warehouse to ship the product from. We’ll use this example for the blog post.
  • Validate data before saving. E.g. a certain product category like alcoholic beverages can only be purchased in limited quantity.
  • Replace data with a custom calculation. E.g. a different service calculates prices, which is also used in physical stores.

Goals

We want to inject arbitrary pre- and postprocessing code into the API calls. However, API client applications shouldn’t notice. At commercetools, we offer SDKs and a template shop which should continue to work. Additionally, we don’t want to update the API clients when we change the business code.

The pattern applies for slight modifications and additions to an existing API, not additional APIs or the replacement of an API.

We only discuss direct responses to API calls. There are different patterns for asynchronous events.

Architecture

HTTP Routing

We use the AWS API Gateway only for HTTP routing. In our example, we want to intercept updates to a cart and send those to the Lambda function. We want to forward all other calls to the commercetools API.

Architecture of the Lambda function

The Lambda function either works with the request or the response. It can validate the request, and if the validation fails return an error in the API error format, or modify the request, e.g. by adding a field.

The Lambda function can also work with the response, and may do another API call based on it. It will return the final API response to the caller. E.g. it can recalculate the discounts and total price for a modified cart, save the result via an API call, and return that final response.

In our example, we want to modify the request and add the closest warehouse to ship the product from.

Implementation

HTTP Routing setup

The commercetools SDKs allow us to specify the base URL of the API (e.g. api.commercetools.co). They construct the path (e.g. /<project>/carts/<id>) for each request.

The API Gateway URLs have the format {api_id}.execute-api.{region}.amazonaws.com/{stage_name}/. If stage_name is the same as the name for our project, the SDKs will construct a correct URL if we specify {api_id}.execute-api.{region}.amazonaws.com as the base URL. Otherwise, we’d have to setup a new URL via Route 53.

For the API forwarding, we save the project name as a Stage Variable. We need to setup two rules: One for the base path, and one for any other URL (a proxy resource, in AWS terminology).

We intercept POST requests to /carts/id and proxy them to our Lambda function. The AWS Swagger export is available here.

Lambda Code

We want to forward the request — sometimes with a small change — to the commercetools API, and return the response.

We’ll need to forward the HTTP method, path, body and headers (which includes the authorization). The event object contains those. The Host header is overwritten by the API Gateway, but the commercetools API will reject requests with invalid Host headers.

Now we need to modify requests that add something to the cart without a warehouse. We look for addLineItem update actions that don’t have the supply channel (=the warehouse) set. To keep the code simple, we add a default warehouse. The rest of the request stays untouched.

Finally, we return the response (also any error response) to the client. We have to callback the API Gateway with the HTTP status, headers and the body. The full gist is available here.

API Clients

Our Sunrise template shop is available as a web and a mobile app. We deploy the web app to Heroku in the EU. Instead of the default URL api.commercetools.co we configure the API Gateway ourid123.execute-api.eu-west-1.amazonaws.com. We do the same for the mobile app.

Both frontends keep working without further changes! Whenever we put a product into the cart, the warehouse is added by the Lambda function.

Performance Evaluation

How does this architecture influence the overall performance as seen by the client? To keep network latency low, we’re using the same AWS datacenter for the web frontend and the Gateway/Lambda. We measured both the overhead of HTTP routing only, and hitting the Lambda function.

For the HTTP routing, we found that the API Gateway has a high deviation. While some requests have an overhead of only 20ms, some add more than 100ms. On average, we saw an overhead of 26ms.

Updating the cart added an average overhead of 35ms, but was more consistent in its response times. The 35ms includes both the API Gateway and the Lambda function, which itself adds an overhead of less than 20ms.

In our opinion, the overhead is acceptable. We actually think the Lambda function performs quite well. We see more room for improvement on the API Gateway side, especially because a typical frontend will use it way more often.

Conclusion

We’ve shown how to add business functionality to a BaaS API without modifying its code. Existing clients continued to work without any code changes.

In the AWS ecosystem, the configuration and code is straightforward. The performance loss for API calls we don’t intercept has room for improvement, but is acceptable. Like the BaaS API, the Serverless implementation will scale and we don’t have to worry about other Ops tasks.

Many thanks to Nikolaus Kühn for helping me research this blogpost!

--

--