An independent guide to building modern software for serverless and native cloud

Adopting Strategies to Minimize Lock-in

This lesson references the code in the aws-connectedcar-dotnet-serverless repository. If you're new to this course, see the introduction for information about setting up your workstation and getting the sample code.

In discussions involving the adoption of serverless, Lambda often suffers in comparison to a technology like containers because of the perception that with Lambda your code will be “locked into” into the platform. In this lesson we’re going to show that this is largely not the case. We’ll show that there are two basic steps you can take to make it relatively easy to migrate away from Lambda should the need arise.

Using Service Components

The first step to take to avoid lock-in with Lambdas is to not actually put most of your code inside them. I think this is the source of concern that people have about Lambdas, because they have this picture in their minds:

Now, theoretically you could write all your data access and business logic code inside Lambdas, but a good development team wouldn't do that. Instead, they would organize their backend code into a series of interconnected service components, as you can see in the sample code does, like this:

Organizing your code in this way properly leverages Lambda as a lightweight event-handling mechanism that wraps calls to service components. Here’s an example Lambda method from the sample code that illustrates this point:

As you can see from this example, the Lambda code is only responsible for calling the service, and in support of that it’s translating inputs from the APIGatewayProxyRequest and translating outputs to the APIGatewayProxyResponse.

Managing Dependencies

However, minimizing lock-in by putting your implementation code into service components is only the first step. The second step is making sure that the interfaces for your service components have no dependencies on any technologies that might at some point be replaced. These interfaces, and the data classes that they export, should only express the underlying system model for the service they’re implementing. You want this system model to outlive any particular technology, which includes a request-handling technology like Lambda as well as a backend database technology like DynamoDB.

In practical terms what this means is that the interfaces for your service components, in particular the data classes that are exported by them, should have no dependencies on the request-handling frameworks used above them, nor on the database frameworks used below them. This is why the sample code has a separate set of data classes that are exported by the service interfaces. Inside the services, these classes are translated to a separate set of internal data classes whose property annotations depend on the DynamoDB libraries.

Here’s a diagram that shows the overall packaging of the .Net version of the sample code. This packaging is central to how you prevent unwanted framework references from working their way into the component interfaces:

First, you’ll note that the service component code, shown in the shaded box on the right, is kept in a separate repository from the Lambda and API request-handling repositories shown on the left. Next, all the component interfaces and their exported data classes are kept separate in the Core.Shared project. Packaging the code in this way makes it possible to enforce the desired dependencies. The Lambda project depends on the Lambda frameworks and on the shared project. The services project depends on the DynamoDB frameworks and also the shared project. The shared project, however, depends on neither of these projects, nor on their associated technology frameworks.

Enabling Migration Paths

The result of packaging the code and managing its dependencies in this way is that you’re enabling a migration path away from Lambda should the need arise. For proof of this, look no further than the sample code for the companion ECS course. That sample code integrates with the same backend service components as do the Lambdas for this course. Migrating to something like what you see in the ECS sample code is not a zero-effort endeavour. But having this path available means that, fundamentally, you’re not locked into Lambdas.