14
Migrating to serverless

This chapter covers

  • Learning how to approach migrating to serverless
  • Structuring your app according to serverless provider characteristics
  • Organizing your application architecture so it’s business-oriented and able to grow
  • Dealing with the architectural differences between serverless and traditional server-hosted applications

At some point, you’ll start thinking about how to apply changes to your in-production serverless applications, migrate your existing apps, and assess the impact of your business needs on the migration.

You’ll be concerned with the quantity of your serverless functions and how to organize and maintain them. You might also start wondering about your serverless provider’s limitations, such as function “cold starts” and how they may affect your application. In this chapter, we recap the architecture of a serverless app and then examine some of these issues, helping you understand the basics of migrating to serverless and how to take serverless apps into production.

14.1 Analyzing your current serverless application

Before any migration to serverless, a good starting point is to look at an existing serverless application and the organization of its underlying services. Throughout the book, you’ve helped Aunt Maria and her pizzeria flourish, mostly due to the following serverless services you’ve created:

  • An API—This API lists pizzas, takes pizza orders, and stores them in a serverless database. It connects to a delivery service, stores pizza images in a serverless storage, and also enables authorization.
  • An image-processing service—This service converts pizza photos from large scale into thumbnails, preparing them for potential web or mobile usage.
  • A Facebook Messenger chatbot—The chatbot can, on customer request, list pizzas, make pizza orders, and create delivery requests. It also has natural language processing, which you enabled, so it can respond to small talk initiated by your customers.
  • A Twilio SMS chatbot—This chatbot can also list pizzas and take pizza orders.
  • An Alexa skill—This Alexa skill enables the customer’s Echo device to list Aunt Maria’s pizzas and helps customers order pizzas.
  • A payment service—This independent payment service is connected to Stripe and allows you to charge your online pizza customers for pizza orders.
  • Uncle Roberto’s taxi application—You migrated your Uncle Roberto’s Express.js taxi application to serverless with ease. This application is not connected to Aunt Maria’s, but it’s worthwhile looking at its migration as a possible solution for one or more of your current applications.

Having a list like this is great, but to have a better understanding of an application and its service relationships, seeing them in a diagram is always more convenient. A complete diagram of the serverless services you’ve developed for Aunt Maria is shown in figure 14.1. Because Uncle Roberto’s application is outside of Aunt Maria’s system, it’s not displayed.

The diagram shows exactly how your serverless services are working and how they are separated. But you may be wondering why Aunt Maria’s services are structured like that, and how you can migrate your existing applications to serverless.

14.2 Migrating your existing application to serverless

Building serverless applications from scratch requires a mind shift. But once you start thinking in a serverless way, all the dots connect quickly. With the help of tools such as Claudia, development and deployment cycles are short and easy.

If you already have an application running and serving customers, it’s unlikely that you’ll just start from scratch. Instead, you have an app with a few thousand lines of code and a couple thousand daily active users, with a history of decisions caused by business requests or other issues that shaped your code in a specific way.

Can you and should you migrate such an application to serverless? The answer is not a simple one, because it depends on the specifics of your application, the structure of your team, and many other things. But in most cases, serverless can be beneficial for legacy applications.

figure-14.1.eps

Figure 14.1 A detailed diagram illustrating Aunt Maria’s serverless services and their relationships

Once you migrate your application, the serverless architecture will push you to keep it in good shape. It encourages further refactorings, because they can reduce costs; having a good and efficient codebase will become a business decision.

If you decide to try serverless, you might wonder how you should approach migration. The first and most obvious approach is to start small, with the less-important parts of your application that can be easily decoupled from the monolith.

One of our clients had a service that converted PDF catalogs into JPG images, so they could be annotated, linked, and served inside their mobile applications. The service was a part of their bigger monolithic application. When a PDF file was uploaded, the service processed the PDF, then generated a JPG image for each page, and notified almost 100,000 users via mobile push notifications that a new catalog was available.

The problem arose when they tried to upload a second big PDF catalog immediately after the first one. Users that received the push notifications started opening the app, but the same server had two things to do at the same time: serving API requests and converting files. Because PDF-to-JPG conversion is CPU-intensive, and the autoscaling process took two to three minutes, API requests often failed at the worst possible moment—when the users clicked the push notifications.

They had a few options, including having a separate server for PDF processing (which would be idle 99% of the time) or triggering autoscaling before they needed it. But the cost of the infrastructure was already too high, so they decided to migrate that service to AWS Lambda and make it 100% serverless.

Just a few days later, they had a fully operational PDF-to-JPG converter service that was independent from the API server. They were able to upload PDFs directly to AWS Simple Storage Service (S3), Amazon’s serverless static file storage service, from their dashboard. S3 would then trigger their AWS Lambda function, which converted PDF files to JPG images using ImageMagick. You can read more about S3 integration with AWS Lambda in chapter 7, and you can read more about S3 service on its official website: https://aws.amazon.com/s3/.

Because PDF conversion is slow, and some of the PDF catalogs had a few hundred pages, they used the Fanout pattern: the first Lambda function receives the request and downloads the PDF file, then it triggers a new Lambda function for each page using an Amazon Simple Notification Service (SNS) event. When all pages are converted, the converter tool contacts the API, which sends push notifications to all the users. The flow of the converter service is shown in figure 14.2.

figure-14.2.eps

Figure 14.2 Moving a small part of the application to serverless: PDF-to-JPG converter with Lambda Fanout pattern

If moving one service to serverless goes well, your next step is to tackle the monolithic application step by step. You can use the technique we explained in chapter 13 and have your full Express.js app running in AWS Lambda. That’s a good way to start using serverless, but don’t think of it as a final solution. Moving your monolithic application to AWS Lambda won’t make it faster and cheaper; it may be exactly the opposite. Going serverless requires a change in your development practices. We go over some of the most important challenges, such as cold starts, later in this chapter.

The other approach is to put API Gateway in front of your application and try to replace one route at a time with AWS Lambda functions or other serverless components that fit your needs (figure 14.3). Then you can observe the way your services work and optimize them.

figure-14.3.eps

Figure 14.3 Migrating an API to serverless, step by step

Migrating routes to AWS Lambda functions is relatively easy; the hard part is migrating all the other parts of the application, such as authentication and authorization or databases. To migrate your whole application to serverless, you’ll need to embrace the whole serverless platform, not just the functions.

14.3 Embrace the platform

Serverless architecture promises certain benefits, such as cheaper, faster, and more stable applications. But to get these promised benefits, you can’t use just a subset of serverless by applying the same principles you would apply to nonserverless apps. Instead, you need to go all in and adjust your application to use all serverless services and let users connect directly to them.

Having a user connect directly to the database or the file storage is an antipattern. But with serverless in combination with other services, such as Cognito, this becomes a pattern that can reduce the cost of your infrastructure significantly.

This section discusses some of the questions we hear most frequently from people who are trying to migrate their existing applications to serverless.

14.3.1 Serving static files

Similar to traditional servers (as you saw in chapter 13), API Gateway and AWS Lambda can serve static files such as server-rendered HTML and images. But this increases the cost (and possibly latency) of those static files significantly when you scale, because each time a user wants to see the file, you’ll pay per use for API Gateway for receiving the request and returning the response, and for AWS Lambda for processing the request and data transfer.

This cost may not seem like much, but API Gateway is significantly more expensive than Amazon S3. Also, serving static files from API Gateway and AWS Lambda can interfere with your limits and prevent more important requests from going through. So, how do you best serve static files in a serverless architecture?

You should let the user talk directly to Amazon S3 whenever possible. If you need to limit access to certain users, you can use Cognito to do that. If you want to grant only certain users permission to upload files, you can use a presigned URL (see chapter 7).

14.3.2 Storing state

Another important question is how to manage state in serverless applications. There’s a popular misconception that AWS Lambda is stateless. But it’s not, and treating it as stateless can cost you in terms of execution time and, of course, money.

Instead of treating serverless as stateless, you should design for shared-nothing architecture, according to Gojko Adzic, creator of Claudia and MindMup, a popular mind mapping tool. There is a virtual machine (VM) underneath each serverless function, but you don’t know how long it will live or if the same VM will handle your next request.

AWS Lambda shouldn’t be used for storing state, but it should be used for optimization. For example, in chapter 13 you started an Express.js app outside of your handler function; that way, you improved performance for all requests that reuse the same VM. For persistent state storage, you should use another service, such as DynamoDB or even S3, depending on the complexity of the state you want to save.

If you need a state machine, you might find AWS Step Functions helpful. With Step Functions, you can easily coordinate the components of distributed applications and microservices using visual workflows, and they allow you to have semi-persistent state. To learn more about Step Functions, see https://aws.amazon.com/step-functions/.

14.3.3 Logs

As you learned in chapter 5, CloudWatch has an out-of-the-box integration with other serverless components, such as AWS Lambda and API Gateway. But CloudWatch is not a great solution when you want to search for logs or just have a better overview.

Fortunately, there are other options that improve the experience of working with serverless logs, such as using a third-party solution or triggering Lambda functions or Elasticsearch from CloudWatch.

One of the most popular third-party solutions is IOpipe (https://www.iopipe.com), a metrics and monitoring service that allows you to see function performance metrics, real-time alerts, and distributed stack traces in a nice real-time dashboard. Setting up IOpipe is quite simple: you sign up for the service and get the client ID, then install the IOpipe module from NPM by running the command npm install @iopipe/iopipe --save. Then you need to wrap your handler with the iopipe function, as shown in the following listing.

Listing 14.1 Need Title FPO

const iopipe = require('@iopipe/iopipe')    ①  

const iopipeWrapper = iopipe({    ②  
  clientId: process.env.CLIENT_TOKEN
})

exports.handler = iopipeWrapper(    ③  
  function(event, context, callback) {
    // Your Code Here
  }
)

If you’re using IOpipe with Claudia API Builder, that integration should look like the next listing.

Listing 14.2 Need Title FPO

const iopipe = require('@iopipe/iopipe')
const iopipeWrapper = iopipe({
  clientId: process.env.CLIENT_TOKEN
})

// Your routes
api.proxyRouter = iopipeWrapper(api.proxyRouter)    ①  
module.exports = api

Another option is to stream logs to an AWS Lambda function or Amazon Elasticsearch service. This option streams all logs and allows you to connect logs to other tools you would normally use, such as the Elastic stack. The Elastic stack—also known as the ELK stack—is a combination of three open source products (Elasticsearch, Logstash, and Kibana) that help you perform easy log analysis and visualization. To learn more about the Elastic stack, see https://www.elastic.co/elk-stack.

Which option is better?

The answer again is that it depends on your application, tools, and preferences. Third-party logging libraries give you additional value and data that is not available in CloudWatch. But as shown in figure 14.4, they also add additional latency to your function runtime. Most of the time, the runtime is just slightly longer, but because Lambda function pricing is per 100 ms, it might directly increase your bill.

figure-14.4.eps

Figure 14.4 Request-runtime duration with the third-party logging option

Starting with third-party logging is probably a good idea, and then you can observe the effect and optimize it or replace it with built-in logging or, possibly, the Elastic stack.

14.3.4 Continuous integration

One of the big advantages of a serverless infrastructure is that you can have everyone on your team deploying to any environment with a single command. But that setup comes with a lot of potential issues, such as with running tests or doing a rollback if something fails.

Traditionally, some of the problems with frequent deployments are solved by continuous integration (CI). CI is a development practice that requires developers to integrate code into a shared repository several times a day. Each check-in is then verified by an automated build, allowing teams to detect problems early. CI therefore allows you to detect errors quickly and locate them more easily.

Here are a few of the popular CI tools:

All of these tools can work with serverless apps on AWS without any problems. To integrate them, you’ll need to commit a claudia.json file to your version control and run the claudia update command after each test suite runs successfully. Just make sure you add your AWS access key ID and secret access key to the environment variables.

In addition to the popular tools just listed, AWS offers a variety of integrated tools for using CI in your serverless app, including the following:

14.3.5 Managing environments: production and development

Each time a Lambda function is published, it gets assigned a sequential build number. You can invoke a particular version and set the triggers to a particular build number, which makes it easy to roll back the deployment and use multiple versions at the same time.

In addition to the numeric build numbers, AWS Lambda also supports aliases. These named pointers to a particular numeric version make it easy to use a single Lambda function for development, production, and testing environments.

For example, during development, you can deploy a new Lambda version and mark it with the development alias, then push the testing alias to the same version and test it thoroughly. Finally, if your function works as expected, you can point the production alias to the same numeric version and put that version into production. Because you can set most of the triggers to invoke an alias, as soon as your production flag is pointing to the new numeric version, production triggers will invoke it directly without additional changes.

One of the most important things to keep in mind when you’re building serverless functions that support different environments is to keep your function environment-agnostic. You should never hardcode the services your function accesses—for example, an S3 bucket or a DynamoDB table name. Instead, try to use the same bucket that sent the event or get the table name from the environment variables.

14.3.6 Sharing secrets

One of the key parts of successful serverless applications with multiple environments is managing app secrets. Throughout this book, we’ve managed secrets in two ways: as API Gateway stage variables and AWS Lambda environment variables. Both have their strengths and weaknesses, and which one you use will depend on your use case and preferences.

If you’re using aliases to manage testing/production stages, Lambda environment variables are tied to a numeric version of your Lambda function, not the aliases, which means that all aliases that point to that same build version share the same environment variables. For example, if you point both production and development aliases to build 42 of your Lambda function, they can’t have a different TABLE_NAME environment variable. See visual representation of how Lambda environment variables work in figure 14.5.

figure-14.5.eps

Figure 14.5 Visual representation of how Lambda environment variables work

As opposed to Lambda environment variables, API Gateway stage variables are tied to the API Gateway stage so, as shown in figure 14.6, two API gateway stages can point to the same Lambda build number and have different variable values.

figure-14.6.eps

Figure 14.6 Visual representation of how API Gateway stage variables work

On a new deployment, Lambda environment variables are reused from previous versions, unless you provide the new set of variables. That means that variables from your development environment will be passed to production if you deploy it using claudia update --version production without overriding them using --set-env or --set-env-from-json flags. Also, to update Lambda environment variables, you need to provide all the active variables again, because each update overrides all existing variables. For example, if you want to change TABLE_NAME but keep the S3_BUCKET variable, you’ll need to provide both of them again, or the S3_BUCKET variable will be lost. On the other hand, Claudia helps with that situation: it has an additional command --update-env you can use to update a single environment variable without having to specify the other environment variables.

API Gateway stage variables are preserved for each API Gateway stage, which means that if you push a new version to the development stage, it will still have all the stage variables from the previous version of the development stage. Or you can add one stage variable, and it will not affect other stage variables for the state you’re updating or any other state.

With API Gateway, you can update a single stage variable without overriding others.

But the Lambda environment variables have their strengths, too. For example, they are stored encrypted, which makes them more secure than API Gateway stage variables, which are not encrypted. They also can be used regardless of the event that triggered AWS Lambda.

The common weakness of both Lambda environment variables and API Gateway stage variables is sharing them between different Lambda functions. If you have many functions that should share secrets, such as DynamoDB table name, you can’t do it without passing the same variable to each of them. For example, your pizza-orders DynamoDB table is used by the Pizza API, but it is also used by your Alexa skill, so you pass the name of the table to both of the Lambda functions.

That weakness can be solved using the AWS Systems Manager Parameter Store, which provides access to central, secure, durable, and highly available storage for application configuration and secrets. It also integrates with AWS Identity and Access Management (IAM), to allow fine-grained access control to individual parameters or branches of a hierarchical tree. One of the downsides of Parameter Store is an additional latency it introduces. To read more about AWS Systems Manager Parameter Store, see https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-paramstore.html.

14.3.7 VPC (Virtual Private Cloud)

When deciding whether to migrate to serverless, your company might have the challenge of complying with specific rules or laws—for example, rules for personal data storage and handling, or even special network security measures within the company itself. Those restrictions might prevent you from using AWS Lambda or other serverless resources.

Fortunately for these cases, serverless providers have a solution called the Virtual Private Cloud (VPC). A VPC is a service that allows you to create serverless resources within a virtual private network. It enables you to have complete control of your network environment, such as IP ranges, network gateways, and so on. Having your serverless resources in a virtual private network provides increased security and helps your company keep its resources in certain areas, regions, or countries. For example, if you’re dealing with sensitive customer data (which may be required within the customer’s country of residence), a VPC enables you to have your serverless resources within the same network as your data center in that country.

Simply put, a VPC enables you to have a virtual private network with your serverless provider resources (say AWS Lambda) and restrict access to these resources so that they are accessible from only the instances or resources in your VPC.

There are drawbacks to a VPC, however. A closed network has problems with cold starts, which are increased significantly, because AWS Lambdas are required to create elastic network interfaces (ENIs) to your VPC. Creating an ENI on a single request can easily add up to 10s to your cold starts. Also, by default, your Lambda function in a VPC doesn’t have internet access, which can be configured using a network address translation (NAT) gateway. Therefore, you need to be careful when using them.

14.4 Optimizing your application

Going serverless can indeed reduce the cost of your application infrastructure, but only when it is done correctly. One of the most important things to understand is that serverless architecture is a new architecture, and some common best practices are not important anymore and can even be counterproductive. The cost savings of a serverless application are seen not just in the bill from AWS; a serverless application can also provide many savings because of shorter time to market, increased efficiency, and easier pivoting.

Although good practices and patterns are emerging, the only way to have a good serverless application is to continuously observe and optimize, which will reduce the cost of your application and improve the user experience.

14.4.1 Bundled or single-purpose functions

You may have noticed in Aunt Maria’s serverless application diagram (figure 14.1) that her pizza listing and ordering API is a single serverless function doing multiple tasks. Isn’t this a monolith within a function? Some may argue that it’s an improper use of serverless functions. The argument is based on the FaaS (Function as a Service) concept that each of your functions should have a single purpose and that you shouldn’t have monoliths in your serverless functions because of the benefits of looser coupling, reusability, and easier maintenance.

Others may argue that you could have joined some of the independent services into the same API—for example, the payment service. Because these services are not used that much, the cold starts may be slower; you’d be better off having a warmed-up serverless function and not making the user wait when paying for your services.

These are the viewpoints of opposing “tribes.” Now, which approach should you take? First, you should remember the famous saying, “no size fits all.” Although this may put you on the edge with both of those tribes, your goal is to create a customer-oriented service. You should have a rational approach, and it will always depend on the type of application you’re building.

Initially, it’s recommended that you divide your features based on domains, like the one in Aunt Maria’s serverless application. Divide the features into a bundled “pizza-related” service and a payment service, and then separate functions for each of the additional services, such as chatbots or image processing. Later, when your system starts to grow, you should strive for single-purpose functions; in this case, you can separate the pizza listing and ordering application into two functions: one for showing a list of pizzas and the other for ordering those pizzas. Because your customer base will have grown, the cold starts are going to happen less and your response rate is going to be faster.

14.4.2 Choosing the right memory size for your Lambda function

Each of your serverless functions has a specified memory allocation. Even though serverless architecture promises no server configuration, certain tasks can require more memory or more computing power. Therefore, serverless providers give you the ability to specify how much memory to allocate. You may notice that we didn’t mention CPU power here, because it’s tied to memory size, meaning that if you want a larger CPU share, you must increase memory. For example, if you configured your Lambda function to have 2 GB instead of 1 GB of memory, it most likely will have more CPU power. For precise information and because it might change in the future, we suggest that you read more on the AWS documentation page about Lambda configuration at https://docs.aws.amazon.com/lambda/latest/dg/resource-model.html.

But specifying a bigger memory size also has a higher cost. That free 1 million requests per month can easily turn into just a hundred thousand if you increase the memory allocation for your Lambda to 1 GB. Choosing the right memory size for a Lambda is tricky. You can easily get pulled into one of the two following traps:

  • Minimizing the memory size for your Lambda to minimize the cost by trying to guess how much memory or CPU power it will need.
  • Maximizing the memory size for your Lambda to speed up all requests and subsequent computation, and also to be ready for any kind of potential increase in memory use.

Again, the best advice is that “no size fits all.” You should try to use logging or a monitoring tool to inspect the function memory usage. For example, in CloudWatch logs you can find how much function memory you used and how long it took to finish, among many other details. Based on those facts, you may be able to get a proper estimate, but always try to base your estimate on various times of the day and also specific events.

14.5 Facing the challenges

With a new architecture comes a new set of challenges, but there are also old challenges that still apply in the same or in a slightly different way. With serverless, some of the new challenges are timeouts and cold starts. But you’ll also have to deal with some old challenges such as vendor lock-in, security, and distributed denial of service (DDoS) attacks. This section discusses some of the most important challenges you’ll face when migrating to serverless architecture.

14.5.1 Handling timeouts

One of the first challenges you might face when migrating is understanding serverless function limitations, one of which is the function timeout limit. Timeouts allow functions to safely shut down without wasting your money or the functions' time if for some reason they stop or block. The timeout limit forces you to think about how to finish your request in the specified timeframe. Although that might be a challenge in itself, the actual challenge is how you monitor, track, and debug issues when a timeout occurs. Also, you might not have a bug, but a certain service may take longer than expected to complete or return a response.

There are several ways to track this: the first and the simplest is to look at your CloudWatch logs, which log all your Lambda functions that time out. Doing so might not be useful though, because you still aren’t handling the timeout itself. There is a better way to handle this. You want to be able to handle these timeout cases and at least log which service took too long or identify a bug. The solution is to create a watchdog timer, an essential service whose only purpose is to detect when your application or software is going to time out. It is a simple timer you put inside your Lambda function to check how much time your Lambda function has left. It calculates the remaining time based on your Lambda function timeout setting, which is defined in the Lambda function dashboard on the AWS Console website.

The watchdog timer detects when the function is reaching its timeout, as you need to handle situations when the function didn’t finish everything on time. When the Lambda function is close to its timeout, the timer invokes another Lambda function. This other Lambda function’s purpose is to log this event or handle it in some other way you might need. To implement this watchdog timer, you need to create a timer function that is constantly checking if there is less than one second until the Lambda function timeout, and to invoke another Lambda in that situation and send it the current function context. Then you can log the new timeout event details or just track this timeout occurrence.

As an alternative to another AWS Lambda function, you can also use some third-party error-handling services, such as Bugsnag (https://www.bugsnag.com) or Sentry (https://sentry.io).

14.5.2 Cold starts

Another challenge you’ll face with serverless applications is function latency, also known as cold start. Because AWS manages scaling and containers for you, each function has additional latency when invoked the first time. This is because a container needs to start, and your function needs to be initialized. After the first invocation, your function will stay warm for a certain amount of time (a few minutes at most), so it can handle the next requests faster (figure 14.7).

figure-14.7.eps

Figure 14.7 Cold versus warm start of the function

A cold start doesn’t happen only the first time your function is invoked, however. If you have multiple parallel or nearly parallel executions, a cold start will occur for each, because AWS may spin up multiple VMs to handle all your requests (figure 14.8).

How do you fight cold starts? You can’t avoid them completely. You can try pre-warming a certain number of your functions, but that adds an additional layer of complexity and forces you to try to predict the number of requests you’ll have under peak load. Another and probably better option is to keep your functions as small as possible (up to a few megabytes), because the cold start is faster if the application can initiate faster.

Also, with serverless, the choice of programming language and the set of libraries you use directly affects the price of your application hosting, so it becomes an important business decision. Running Node.js or Golang functions will cost you significantly less than using Java for writing your functions, because of faster initialization.

figure-14.8.eps

Figure 14.8 Cold starts affect all VMs during parallel requests.

14.5.3 DDoS attacks

Serverless completely changes the business model by charging for time used instead of time reserved. That is a marvelous change, but you may wonder about handling DDoS attacks. These attacks send a large volume of junk data to your application so your application servers can’t scale quickly enough to respond to valid customer requests, thereby preventing your servers from providing service to your customers. Now, because your serverless provider handles scaling and load balancing for you automatically, you might think that a DDoS attack would bankrupt you. The chances of that happening, however, are virtually nonexistent because your serverless provider (AWS, for example) is able to do a much better job of providing security and stability in the face of such attacks than typical server-hosted providers.

In addition to this, you can also specify the following:

14.5.4 Vendor lock-in

When going serverless, one of the main concerns is vendor lock-in. It’s easy to become reliant on your serverless provider’s resources and their corresponding APIs. This may not sound so bad, but the issue comes when you want to move from one serverless provider to another. The original provider’s resources will no longer be available to you, and that might necessitate a complete refactoring or even a rewrite of your application.

In some cases that may be a blocker. Businesses don’t want to tightly couple with a single resource provider, requiring them to submit to any potential changes in pricing or resource stability and availability. The counter-argument for server-hosted applications is that no matter which provider you use, you can always install the same versions of the needed databases or tools on your servers.

But there is an occasional confusion of concerns. When analyzing vendor lock-in, two distinct layers sometimes get confused and mixed together:

  • Infrastructure
  • Service

Coupling in the infrastructure layer refers to the coupling of your application to a specific infrastructure, whereas coupling in the service layer refers to the coupling of your application to a specific software service (a database, file store, search service, and so on).

When going serverless, you might be tempted to start comparing servers and serverless computing services (such as AWS Lambda). But this is in fact comparing apples to oranges, because a serverless computing service belongs to the service layer, whereas a server belongs to the infrastructure layer. You might be asking yourself what the difference is or why you should care. The main point is that AWS Lambda doesn’t fill the previous role of the server.

Even with an understanding of this separation of concerns, some might think, “But I’m still locked in to a vendor. This doesn’t change anything, because I’m still required to use AWS resources when using AWS.” This is correct, but this understanding reveals two benefits that were hiding in plain sight:

  • Switching serverless providers/vendors is like switching your MySQL database to a PostgreSQL one.
  • Applying Hexagonal Architecture instead of directly interacting with the serverless resources can almost completely remove all potential vendor lock-in issues.

When switching serverless providers, the rules that apply are the same as when migrating one service to another—and you can safely migrate a single serverless service to another just by changing the interaction with its API.

There are serverless frameworks that abstract away the serverless provider specifics, but by using them you’ll then be locked in to your serverless framework, instead of the vendor lock-in with your serverless provider. Also, you’ll still be tied to your serverless provider, because you’ll still be expecting that the responses and messages coming from your serverless provider services are going to be in the serverless provider’s format. For example, if you’re using AWS Kinesis and you’re expecting the Kinesis message format in your code, you’re still locked in to a vendor; you won’t be able to just jump in to a Google or someone else’s alternative.

For that reason, Claudia.js does not abstract away serverless provider details and therefore is AWS-specific.

The recommended method for mitigating this “vendor lock-in” is to apply Hexagonal Architecture, where you’ll be defining boundary objects whose sole purpose will be to interact with the specifics of the serverless provider’s resource APIs. Your business logic will stay intact, meaning that switching to another serverless provider will just require a change in the boundary object protocol logic.

14.6 Taste it!

The exercise for this chapter is simple, but it’s not easy: migrate one of your existing Node.js applications to serverless.

Unfortunately, there’s no solution you can copy and paste. But we’re sure it will be fun, and much more importantly, it will have a great business impact, both on your application and on the way you’re going to develop applications from now on. Good luck!

Summary

  • Running your existing application in AWS Lambda and API Gateway is a good start when migrating to serverless, but it can end up costing you more if you don’t fully embrace the platform.
  • You can migrate your application to serverless by putting an API Gateway in front of it, and then moving the routes one by one to serverless.
  • To get the full benefits of serverless, such as lower cost and faster development time, you’ll need to use all serverless services.
  • With serverless, the choices you make with programming languages, libraries, and when to refactor become business decisions and risks, because they directly affect the cost of your infrastructure. Therefore, continuous observation and optimization are important for a successful serverless application.
  • Serverless is a new architecture that requires a new set of patterns and best practices.
  • Migrating to serverless brings a new set of challenges, such as cold starts and handling timeouts. But some of the old challenges still apply—some more than ever, such as vendor lock-in.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset