Overview
When building any new application, it is important to keep in mind the long-term implications of the architectural decisions made during the initial design phase. For serverless applications, future support for the runtimes and libraries chosen when initially designing the application is often a huge unknown. Serverless functions, such as AWS Lambda, are particularly difficult to predict as AWS continually deprecates older runtimes as they add support for new ones.
Recently, we were working with a client that had a collection of AWS Lambda functions that were running Python 2.7. With support for the 2.7 runtime coming to an end, we had to expedite a plan to modernize these functions. We distilled the list of possibilities down to the following options when evaluating each function: refactor, replace or re-platform.
The Solutions
Refactor
This solution would involve making small modifications to the function in order to convert them to a Python 3 runtime, which is supported by AWS Lambda. This would be a relatively quick fix as there are libraries that can convert Python 2 scripts to Python 3 such as 2to3. However, there were unknown risks in addressing problems that would inevitably surface from the libraries and modules that were deprecated between major versions, or worse yet, trying to reimplement nested custom libraries and imports from non-standard modules. These issues can prove to be costly when you factor in the time required to effectively debug, resolve and test each function.
As you can see, the refactor solution is not a perfect approach, but it does serve as the best option for many scenarios that you might find yourself in. Refactoring should be used when Lambda provides a mainstay functionality (that cannot be replicated by any other service) in your environment. If you know the function is going to exist in your environment for a while, the cons of the refactor strongly outweigh the pros of having your code completely up to date.
If you have a function that fulfills core functionality for your application, you will want to make sure that the runtime and code are using the most up-to-date versions (in other words, maintain your code); this will ensure you receive relevant and valuable support where it counts.
Replace
The replace solution would involve using a different AWS service to achieve the same functionality that the Lambda function was providing. This is ideal as the issue of versions being deprecated would not occur in most AWS-managed services. The downside of this approach is that there may not even be a combination of AWS services that can replace the specific functionality your function provides. You also have to take into consideration the time it would take to learn and successfully adopt the new service or services.
This solution is a tricky one as not every Lambda function you write (or implement) may have its functionality replicated using another AWS service. However, when that is the case, it provides an excellent opportunity to simplify and streamline your operations. With a new AWS service, you won’t need to worry about managing your codebase and handling runtime version changes. The downside is the potential time it could take to learn new service and set it up with your required needs. You may also lose the “pay only for what you use” pricing model that initially attracts many to serverless applications.
Any time that a service can provide the same functionality as your Lambda, replacing should be strongly considered as your solution.
Replatform
Even though AWS no longer supports Python 2.7, you could still host the function using a container with Python 2.7. That is where this solution comes in. Replatforming is a fairly simple fix outlined by the AWS docs here. This would most likely be the quickest fix to our issue. The downside to this is that you would still be using an outdated version of Python that is quickly losing support.
While using outdated runtime versions is never ideal in a well-architected environment, there are some scenarios where choosing to re-platform is the correct decision to make. If you have plans to replace, refactor, or just retire the functionality that the function provides, then using a container with the deprecated runtime to run your code will suffice as an appropriate interim solution.
This will allow you to easily continue to use your function without investing significant time until you are ready to retire or fully replace it. The solution can also be useful if you received the code from a 3rd party and have little ability to refactor it.
The Conclusion
As you can see, no solution is perfect; each comes with its own advantages and disadvantages. That is why it is imperative that you understand your environment and long-term architectural direction. This table will help you decide which solution is best suited for you.
Below are some sample scenarios along with a recommended approach.