{'fraction_non_alphanumeric': 0.02585484226781381, 'fraction_numerical': 0.004191484667990294, 'mean_word_length': 4.980474934036939, 'pattern_counts': {'":': 0, '<': 0, '<?xml version=': 0, '>': 0, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
Infrastructure in Code: Towards Developer-Friendly Cloud Applications

The popularity of cloud technologies has led to the development of a new type of applications that specifically target cloud environments. Such applications require a lot of cloud infrastructure to run, which brought about the Infrastructure as Code approach, where the infrastructure is also coded using a separate language in parallel to the main application. In this paper, we propose a new concept of Infrastructure in Code, where the infrastructure is deduced from the application code itself, without the need for separate specifications. We describe this concept, discuss existing solutions that can be classified as Infrastructure in Code and their limitations, and then present our own framework called Kotless - an extendable cloud-agnostic serverless framework for Kotlin that supports two cloud providers, three DSLs, and two runtimes. Finally, we showcase the usefulness of Kotless by demonstrating its efficiency in migrating an existing application to a serverless environment.


I. INTRODUCTION
In the last several years, cloud computing has been getting more and more popular with developers and enthusiasts alike [1]. Modern cloud-native applications [2] that are being developed specifically for cloud provide better performance and scalability, while being more cost effective and fault tolerant [3]. This is especially true for serverless applications [4]. Cloud-native applications differ structurally from traditional web applications: their complex and distributed nature means that their development is trickier [5]. Developers need to not only create a new application that uses some cloud API but also create an infrastructure for it. Basically, the way an application is created is twofold: it requires both writing code and deploying the accompanying infrastructure.
One of the popular solutions to this problem is the IN-FRASTRUCTURE AS CODE (IAC) [6] approach. Within this technique, the developer creates special definition files that declare infrastructure alongside the rest of the code and uses these files to deploy the application into the cloud platform. However, this approach also has its drawbacks: deploying a simple static website of about 1,500 lines of code to Amazon Web Services (AWS) could require one to write more than 600 lines of additional IAC code in a separate domain-specific language, as well as to know the granular specifics of this particular cloud platform.
In this paper, we present a novel approach to the infrastructure declaration that makes the development of cloud-native applications a lot easier and does not require the developer to have expert-level knowledge about the particular cloud platform. We call this approach INFRASTRUCTURE IN CODE (IIC), which implies, contrary to the INFRASTRUCTURE AS CODE, that it does not require one to create separate infrastructure declaration files and instead deduces the infrastructure right from the code itself. In the paper, we describe our vision of the concept and its key characteristics, as well as discuss existing solutions and their drawbacks.
We also showcase the IIC approach in our tool called Kotless [7], a serverless framework for Kotlin. This tool parses developers' Kotlin code to create the necessary serverless infrastructure automatically. We presented the prototype of Kotless in our previous work [8]. Since then, the prototype grew into a full-fledged framework that now supports two different cloud providers (AWS and Microsoft Azure), three DSLs (Ktor, Spring Boot, and our own Kotless DSL), and two runtimes (JVM and GraalVM binaries), with more than 800 stars on GitHub and a constantly growing community. We describe how Kotless operates, how it implements the IIC features, and show how it drastically reduces the complexity of developing serverless applications in comparison to the IAC approach. Our case study shows how a web application can be moved to a serverless format using Kotless in several simple actions, reducing the cost of hosting by 80%.

II. INFRASTRUCTURE IN CODE
We define INFRASTRUCTURE IN CODE as a way of managing and provisioning infrastructure through automatic inference of infrastructural requirements from the application code. For example, a developer may use a @Get("/get") annotation in the code, declaring that this code is an HTTP endpoint responding to GET requests. The IIC tool should be able to understand this and automatically provision an HTTP server that would be able to serve as an HTTP endpoint.
The overall pipeline of developing an application with the IIC tool is as follows: 1) The developer writes an application using predefined statements that are interpreted as infrastructural requirements by the tool. 2) Once the application is finished, the developer triggers its deployment to the cloud. 3) The IIC tool analyzes the application to infer all the necessary infrastructure. 4) The IIC tool generates all the required infrastructure. 5) The application is deployed to the created infrastructure. Therefore, for this approach to work, an IIC framework should provide the following two main components: • Development API that is used during the development of an application and helps to express infrastructural requirements; • Cloud Implementation -a set of specific cloud services that would be generated in response to the infrastructural requirements. 1) Development API: Overall, Development API defines the way developers express infrastructural requirements in code. IIC tools can be divided into two different types depending on the Development API: • tools that introduce their own Domain Specific Languages (DSLs) to declare infrastructural requirements in code; • tools that are able to analyze existing applications written with existing frameworks and translate their API into infrastructural requirements automatically.
While both approaches make sense, only the second one makes it possible to migrate existing applications to automatically generated infrastructure. As noted by Baldini et al. [9], the inability to automatically convert existing applications into cloud-native (serverless, in this case) is one of the biggest challenges for cloud adoption as a whole. Thus, we believe that an IIC tool following the second approach would be much more useful in practice.
2) Cloud Implementation: Cloud implementation is an array of services of a cloud provider that are deployed to meet infrastructural requirements of an application. However, we believe that the definition of IIC should not define what specific cloud implementation should be chosen. For example, there are different computing models available within modern cloud platforms: the code can be executed inside long-living virtual machines or using short-living serverless functions, and both choices have advantages and disadvantages [10]. Moreover, there are different cloud providers to support. We believe that each IIC tool should be implementing infrastructure in a way that is suitable for a particular purpose. For instance, there should be IIC tools that deploy serverless applications and those that create Kubernetes deployments. Such a diversity makes it possible for developers to choose the tool that suits their needs best.
3) Limitations: The IIC approach has its limitations, since not all kinds of infrastructure can be or should be inferred from the code. For example, it is rather difficult to get a schema of a NoSQL database from the application code, and even impossible to do so if the schema is being built in runtime. At the same time, an IIC tool would be simply unable to handle applications with an arbitrary logic. 4) Interoperability: Because of these limitations, we do not consider IIC tools to be an instant replacement for the IAC approach. Instead, we believe that both approaches can work together, and the natural way of creating new applications should be to create applications fast via the IIC approach and, if necessary, migrate to IAC later.
Such an interoperability can be achieved by using an IAC tool under the hood of the given IIC tool: instead of using the cloud platform's API directly and doing all the routines of infrastructure generation, an IIC tool can just generate IAC code and use it to deploy the infrastructure. This solves two problems at once: we use reliable tools for deploying infrastructure and support interoperability with an IAC instrument. Users can even use IAC tools with IIC tools in one project -part of the infrastructural code will simply be generated during deployment.

III. EXISTING IIC IMPLEMENTATIONS
There exist tools that can be considered implementations of the IIC approach. Most of them are implemented for the serverless computing model, which is understandable given the growing popularity of serverless computations [4].
AWS Chalice [11] is an IIC tool for Python developed by Amazon Web Services (AWS). The tool uses its own DSL to define HTTP APIs and event handlers, and makes it pretty easy to create simple serverless applications. However, AWS Chalice has certain drawbacks. It introduces its own DSL to define even the most basic HTTP handlers, which makes it impossible to automatically migrate to it from existing Python Web frameworks, and only supports the AWS cloud platform.
Zappa [12] is an IIC tool for Python that infers infrastructural requirements from Django API. Similar to Chalice, this tool only supports AWS. Unfortunately, under the hood, Zappa just wraps the entire application into a container and publishes it to the AWS API Gateway. Because of this, if a developer wishes to customize their application somehow or integrate it with other cloud services, they would need to use Zappa's own IAC dialect to define permissions and integrations.
Osiris [13] is an IIC tool for Kotlin with its own DSL to define HTTP API events. It is serverless-oriented and only supports AWS. Osiris does not interact with AWS services directly, like other tools do, instead, it generates CloudFormation [14] definitions and uses them to deploy infrastructure to AWS, allowing for the interoperability.
All of the listed tools have certain disadvantages. First of all, all of them target only one cloud service provider, AWS. Secondly, all the tools support only one specific DSLeither their own custom one or API of some specific web framework. Finally, two of the three tools interact with the cloud platform directly instead of using an IAC tool, which hampers interoperability.

IV. KOTLESS
We present our own implementation of the IIC approach that addresses the issues of the existing tools. Kotless [7] is a Kotlin IIC tool that uses the serverless computation model to execute applications. Its main features are: • cloud-agnostic: it supports AWS and Microsoft Azure, and could be extended to other could service providers; • DSL-agnostic: it has its own DSL, but also supports two popular Web frameworks, Ktor [15] and Spring [16]; • runtime-agnostic: it runs on JVM, but also compiles into binary via GraalVM; • interoperable: it integrates well with Terraform, one of the most popular IAC tools. The pipeline of Kotless refines the overall IIC pipeline and is presented in Figure 1:

{}
1) The developer is writing code with one of the supported DSLs (see Section IV-2).
2) The Parser module starts the Kotlin compiler and, during the analysis phase, gets infrastructural requirements via the adapter for the specific DSL.
3) The infrastructural requirements are translated into the cloud-agnostic Schema. 4) The Engine module runs the generator for a specific cloud platform and generates the necessary IAC code. 5) The application is deployed via the IAC tool. Let us now describe each module in more detail. 1) Cloud-agnostic schema: The cloud-agnostic schema is one of the key points of the Kotless architecture. It decouples the entire tool into two isolated components (analysis and generation) and makes it possible to introduce new DSLs and support new cloud platforms independently of each other without changing the existing code.
It consists of two main parts: general schema, which expresses infrastructural requirements for different cloud platforms in a uniform way, and cloud-dependant extensions, which allows to add events specific to particular cloud platforms (for example, S3 notifications).
The general schema introduces three main entities: • lambda, which is a dynamic handler for events; • static resource, which is a static file that may be used in a response; • permission, which describes permissions associated with a specific lambda. For these entities, we introduce HTTP API definitions: • dynamic route, which is a definition of an HTTP endpoint that should be processed by a lambda; • static route, which is a definition of an HTTP endpoint by request to which a static resource should be returned. Of course, modern cloud platforms support events other than HTTP requests for serverless applications. The schema can be extended with custom events, for example, we added the Scheduled event to define scheduled jobs within the cloud platform. To support specific events of particular cloud platforms, we implemented interoperability with Terraform, an IAC tool. The developer can define a lambda and just subscribe it to some cloud-specific event with a piece of Terraform code. For example, this can be used to create a certain resource and then share it across different applications that are implemented in different architectures or on different clouds.
2) Parser module: The Parser module is a platform for code analysis and a set of extensions that perform the inference of infrastructural requirements for specific DSLs. In this paper, we refer to the APIs of supported frameworks (annotations of Spring, extension functions of Ktor, etc.) also as DSLs, since from the perspective of IIC tools they are used to define infrastructural requirements in code.
Internally, the Kotless Parser module is implemented as a plugin to the Kotlin compiler. This gives us a great deal of flexibility: Kotless is able to not only get the information about annotations, but also to retrieve functions, types, and even perform compile-time calculations if needed. To make the implementation of the DSL analysis easier, Kotless provides a platform with extension points. Currently, Kotless supports three DSLs: Ktor Web Framework API, Spring Boot API, and our own Kotless DSL. Their support was implemented via a common analysis platform and reusable analysis logic. The comparison of these three DSLs is available online [17].
3) Engine module: Finally, to perform the actual deployment of the application to the cloud platform, we use the Engine module. As mentioned in Section II-4, we opt for supporting interoperability between our IIC tool and popular IAC tools, so we do not implement the integration of Kotless with each cloud platform, but instead simply generate the IAC code. For that purpose, we chose Terraform-a well-known mature tool that supports almost all existing cloud platforms via a common language, HashiCorp Configuration Language (HCL). This language is typed and supports variables, links, string templates, etc. Each cloud platform has its own pack of so called resources that are basically types in HCL and that are defined in HCL Schemas. To provide a convenient way of working with HCL code, we created a library called Terraform.kt [18] that generates type-safe Kotlin DSL from HCL schemas and packs it into libraries. With Terraform.kt, writing generators of Terraform code in Kotlin is almost as simple as writing the Terraform code itself. Terraform.kt is also fully open-source.
The generation infrastructure consists of a set of generators. Each generator awaits for some Schema element as input and can depend on some other generator's output, thus, forming a dependency graph. During the generation process, the platform runs a visitor through the Kotless Schema object and executes all the generators that are able to run in the current context. Their outputs are added into the context, so other generators may run as soon as their dependencies are satisfied. When nothing else is able to run, the generation process stops and all the Terraform.kt entities are serialized into Terraform code.
Based on this infrastructure, we implemented support for AWS and Microsoft Azure. Currently, the support of Google Cloud and Yandex.Cloud is in process (the support of Yandex.Cloud is being added by Yandex developers themselves).
In the case of AWS, we decided to use API Gateway as the main provider of HTTP endpoints. It supports the integration of static and dynamic routes and is fully serverless, which means that users do not have to pay for the application if it is not being currently used. To run lambdas, we used AWS Lambda, and we chose S3 to host static resources, since it is a reliable storage of static files, fully integrated with API Gateway. Finally, as a trigger for scheduled events, we used CloudWatch Events and their integration with AWS Lambda.
In the case of Microsoft Azure, we considered two HTTP endpoint providers: Azure Functions Proxy and API Management. While the latter is more powerful and supports more integrations, both were enough to implement static and dynamic routes. Since API Management was too expensive and slow to deploy, we chose Azure Functions Proxy. We decided to use Azure Functions to run lambdas and Azure Storage Blob to host static files. The scheduled trigger was implemented via Azure Functions Time Trigger. 4) Running in the cloud: Finally, the application should somehow integrate with the cloud platform API to work after being deployed. Each cloud platform provides a number of interfaces that applications should implement to be able to run and respond to events. To conform to this, Kotless implements so-called adapters. Each adapter is specific to the particular cloud platform and is basically a special library that translates requests from a cloud platform into a necessary Web framework (Spring, Ktor, or any other) and translates back responses. Such adapters are implemented for each supported DSL and also for each supported cloud platform.
An important aspect of running an application in the cloud is the specific runtime that the application uses. Kotlin is a multi-platform language that can be compiled into Java Virtual Machine (JVM) bytecode, JavaScript, and native binaries (via Kotlin/Native or GraalVM). Kotless supports two main runtimes for Kotlin: JVM (for all cloud platforms and DSLs) and GraalVM binaries (currently, only for AWS and Ktor).
Supporting GraalVM is a unique Kotless feature that speeds up JVM-based serverless applications by orders of magnitude. It also does not have a problem of a cold start [9] that is specific to the JVM runtime. The drawback of GraalVM is that it requires special attention to the used Java APIs. For example, because of this limitation, the GraalVM runtime is currently supported only by Ktor DSL that does not heavily use reflection, in contrast to Spring and Kotless DSL.

V. CASE STUDY
To demonstrate the usefulness of the IIC approach and Kotless, let us consider the Play KotlinLang project [19]. This is a Kotlin playground with a fully-featured built-in IDE: it includes a Kotlin compiler, syntax highlighting, and code completion, as well as virtual machines able to perform compilation of code and secure its execution.
Overall, Play KotlinLang has an average workload of 2.23 requests per second, with a daily maximum of 17 requests per second. In total, it processes 5.8 million requests per month. However, the number of requests is not distributed evenly: we observe clear spikes during mornings and evenings. Because of that, with the old infrastructure that was based on permanent computing instances provided by the cloud platform, we had to over-provision those instances just to be sure the infrastructure would be able to handle these peak loads. This setup cost us about $250 per month.
Such unevenly used application is a good candidate for moving to serverless. The only problem for us was the fact that Play KotlinLang was written using Spring Web Framework, and it was not clear how to migrate it into a serverless environment. The application is rather large (7,075 lines of Kotlin code) and too complex to simply deploy it as one serverless function: it has a lot of dynamic and static resources that belong to the front-end application. Thus, we decided to use Kotless to perform the automatic migration to the serverless environment. All we had to do was to add one new class [20] that would serve as an entry point for the serverless platform and set up credentials to AWS. Kotless automatically introspected the entire code base and generated 989 lines of Terraform code. Then, the application was deployed to the cloud platform [21].
Overall, the process of migrating to serverless took about 15 minutes, while writing almost a thousand lines of Terraform code would take several hours in the best case scenario, including debugging via repeated deployments. The resulting Kotless-based version of the service costs us about $50 per month, resulting in an 80% saving rate. This example shows that Kotless is able to solve the problem of legacy applications migration [9] and facilitates the decrease in hosting costs for such applications.

VI. CONCLUSION
In this paper, we present the concept of INFRASTRUCTURE IN CODE -a way of writing cloud-native applications, where the infrastructure code is generated automatically from the application code itself, in contrast to popular INFRAS-TRUCTURE AS CODE tools that require developers to write separate deployment code. We discuss the main features and requirements of the IIC approach and describe existing tools supporting it.
To overcome the drawbacks of existing solutions, we present and describe Kotless -a framework for creating serverless applications in Kotlin. Kotless can be extended in many directions: currently, it supports two different clouds (AWS and Microsoft Azure), three different DSLs, and two runtimes. Kotless was used to develop several applications, it saves developers from writing infrastructural code that could be twice as large as the main application. Kotless is also used in production at JetBrains, in other open-source projects, and has a growing community. We hope that it can be useful to practitioners all over the world. Also, while Kotless itself focuses on Kotlin, we would be happy if its assets were used by future developers to introduce brand new IIC tools for other languages, for example, Java or Python.