The allure of serverless technology has been captivating the industry for some time with many companies seeking ways to make a switch. Cost reduction, diminished maintenance demands and reduced possibility of issues are strong motivators for many to question old habits. What possibilities are there for those contemplating migration of Ruby applications?
We tried to compile a comprehensive list of options for those who are considering AWS as the destination. Other platforms might also have some specialized libraries but here we will be concentrating exclusively on AWS Lambda, as the most mature and widely used platform.
For anything beyond the simplest requirements, creating a Lambda function and leaving it at that is not feasible. When requirements expand to include database, asset delivery, various responses and utilization of environment variables, the overhead for setting it up in each separate function would be too big. Wrapping the whole Rails app in a function is not a viable option in the real world even if it is possible as a demonstration. This prompted creation of various solutions, some created by Amazon and some as an open source emerging from the community.
Possible approaches are listed from the simplest solutions to more sophisticated gems and frameworks.
Serverless Application Model (SAM) presents itself as a prospective solution. This approach streamlines a significant portion of the setup through a SAM template — a concise YAML file that defines function properties and associated resources. Non-trivial issue with this is that many parts of the app still have to be adjusted and configured separately in order for the whole system to function. In the complicated Rails app, this might be the hardest part of your migration and some problems could surprise you when you least expect. Session management, logging, VPC configuration... all must be dealt with and set manually which makes this way of migrating a bit cumbersome for most.
Named λake but spelled yake for convenience, this gem might be suitable if you intend to add only a few lambda functions to your Rails application. Using it gives a Rake-like experience, and its intended use case is as a proxy for API gateways. In this case, you might end up with Sinatra-looking routes that are readable and centralized in one file. A notable advantage of using this gem is its complete independence from other libraries. This ensures that you can extract maximum speed from your routes, enabling functions to conclude as swiftly as feasible. This is particularly advantageous as it's charged per millisecond of compute time.
On the other hand, there is virtually no support, community is non-existent, while deployment still has to be configured separately in AWS SAM or Serverless.
Lamby positions itself in your Rails application as a replacement for a web server: Puma, Passenger etc. It scales functions as the number of requests increases and maintains them open for a few minutes thereafter. This results in a small pool of concurrent functions that handle a large number of requests.
Using AWS SAM in the background, Lamby works by packaging your Rails app in the Docker container with important settings already defined for you. An embedded interface, known as the Runtime Interface Client, serves as the entry point for the created container that loads the Rails application into the Lambda function. Deployment and CI/CD are easy and compatible with some widely used solutions such as CircleCI and Github Actions.
Other aspects of the Rails ecosystem, such as the database or asset serving, are thoroughly designed and shouldn't present challenges during project setup. It's not hard to implement CloudFront, which can boost your asset serving via geographical caching and it's only one of the implemented helpers that you almost 'get for free' with Lamby. In case of any issues, well-written tutorials and an existing community make finding help easy.
Structure of this framework ensures that loading the whole Rails application happens only once, resulting in an instance that is blazingly fast and can last more than 30 minutes. In some cases, this is the best cold-start solution for AWS Lambda and combined with automatic pre-initialized instances sometimes can seem instantaneous.
Jets is an advanced framework with a straightforward philosophy that strives to mirror Rails as closely as possible. This intentional similarity significantly eases the adaptation process for most Ruby programmers, as, for practical purposes, it could readily pass as a conventional Rails application. When crafted as a Jets app from the ground up, replacing "rails" commands with their "jets" counterparts allows for seamless functionality ("jets generate model user" instead of "rails generate model user," and so on).
The underlying mechanism of this project involves transforming each controller action and job into distinct lambda functions. As you develop your web application in the same manner as you would with a typical Rails project, Jets automatically translates your endpoints into discrete functions. If your application adheres to standard CRUD operations, your work is nearly complete. Once paths and variables are configured, a simple "jets deploy" command deploys your functions to AWS. This inherent simplicity also ensures compatibility with any existing CI/CD processes or allows the creation of your customized workflows without additional complications.
Along with the controller actions, each job takes on the role of a distinct Lambda function. It's crucial to bear in mind that AWS Lambda might not be the optimal choice for extended tasks, given its nature. However, it's worth noting that functions can initiate other functions, making it an ideal environment for parallelizing tasks that can be reconceptualized as concurrent operations. Other resources are created and managed automatically, CloudWatch Event Rules, logging, IAM policies, multiple environments... all can be set from the framework and thus save a lot of developer time.
Tung Nguyen, the creator of Jets, is also the founder of BoltOps and remains actively involved in shaping the community. If you intend to deploy development builds on AWS, evading payment for precompiled gems can prove challenging. If you find the lock-in aspect problematic, it might be best to consider other options.
Ruby on Jets also has a way of adapting classical Rails apps and there is more than one way of doing it:
Designed to achieve deployment of a Rails app in the simplest way possible. After installing the jets gem (although not bundled with the app), you simply execute jets deploy, and if all the variables are properly configured, that should complete the process. Naturally, all environment values utilized within the Rails app must be incorporated into the Jets framework, and this process is quite straightforward.
It's important to note that there are limitations for Rails apps in serverless mode. Certain gems and operations assume the presence of a file system, which is not the case with Lambda functions. Size limitations present another concern, so for some special cases we should reach to Mega Mode.
Furthermore, when feasible, it's advisable to opt for a native Jets application over a Rails app in Afterburner mode. This recommendation arises due to the presence of underlying overhead and obfuscation.
Designed for intricate scenarios in which there's a requirement to keep Rails and Jets processes and environments distinct, Mega Mode comes into play. It's simple in its design, initiating Rack server and forwarding Jets requests to it. The Rack application operates as a distinct process, ensuring a complete separation between the two projects. This insulation guarantees that dependencies cannot clash.
One might expect a significant overhead for such a setup, but thanks to a feature known as 'Lambda Execution Context,' the impact is remarkably modest during cold starts—ranging from just 30 to 300 milliseconds.
As always, selecting the optimal solution relies on your specific circumstances, considering the inherent trade-offs. However, serious consideration should be focused on Lamby and Jets because other options are bringing too many issues. Lamby should be chosen for simpler cases, where straightforward deployment with minimal overhead is required. For any more complicated application, choice is between a range of Jets options. If starting from scratch, you should consider full-fledged Jets application. When migrating from an existing Rails app, it would be best to use Afterburner mode whenever possible. Consider Mega mode when you are sure that it's the only way of addressing problems that you actually have.
By carefully evaluating the specific demands of your application, you can confidently embark on your serverless journey, armed with the insights to make the best choice for your unique needs.
If you require expert guidance, Ruby on Cloud, specializing in migrating Rails apps, can provide invaluable assistance throughout your migration process.