How Framework Defined Infrastructure Works
HIGH LEVEL TENDENCIES, WEB DEVELOPMENT.Framework-defined infrastructure (FdI) is an evolution of IaC (Infrastructure as Code), where the deployment environment automatically provisions the derived infrastructure and written applications.
In this way, a compile-time program analyzes the source code, understands the intent behind it, and then automatically generates the IaC configuration needed to run the software. This means more predictable, lower-cost, lower-risk DevOps through a serverless architecture.
In this article, we'll explain how framework-defined infrastructure fits into modern views of definition and automation and show examples of how it enhances the open source development experience.
Contextualization of Infrastructure as Code (IaC)
At the dawn of servers and the Internet, development was done on individual machines, running installation scripts or even configuring production software through graphical user interfaces to provision the infrastructure.
As the Web scaled, it quickly became clear that this approach would not scale with it and, along with the DevOps movement, IaC became the best practice for provisioning infrastructure in a repeatable and reliable manner.
The code in IaC represents a version (controlled description of the desired state of the infrastructure) that, when executed, will create the described system state. In this scenario, infrastructure can refer to:
Network equipment and configurations
Web and application servers
Databases and message queues
What is framework-defined infrastructure?
Framework-defined infrastructure abstracts from cloud primitives such as servers, message queues and Serverless functions, making them implementation details with the following advantages:
Provide portability between different target infrastructure providers.
Eliminate the need to manually configure the infrastructure to run an application in production.
Increase the time spent writing product code on system management.
Enable unchanged use of the framework's native local development tools.
Standardize previously reviewed secure services.
Frameworks use well-established patterns to provide structure and abstraction to applications, making them easier to write and understand. Thus, they manage the flow of a high-level application, while the developer writes code within the hooks provided.
Framework-defined infrastructure leverages both this inversion of control and the predictable structure of applications to automatically map framework concepts into the appropriate infrastructure without the need for explicit configuration.
*This article provides examples based on Vercel's platform-as-a-service offering. However, the concept can be applied more broadly, as the basic idea of understanding a framework and generating the IaC configuration can also be used for more traditional infrastructure implementations.
Application of the infrastructure defined by the framework
These are examples of how frameworks automatically map their concepts to the Vercel infrastructure. Although we will use Next.js here for the demonstration, the Vercel implementation supports a multitude of frameworks based on the same underlying mechanism.
* Flowchart showing the process from user code to automatically inferred infrastructure.
Next.js uses a file-based router that maps files in a directory structure to URLs in a web application. For example, the file pages/blog/index.ts creates a URL path that maps the URL /blog to the code defined in that file.
The framework-defined infrastructure begins by elevating this understanding of a web application's routing table from a framework implementation detail to something the infrastructure understands.
In Vercel's case, the routing table generated from a framework would eventually be implemented in the gateway service, which then knows how to invoke the correct infrastructure for any given route.
The contents of pages/blog/index.tsx could look like this:
export default function BlogPosts({ posts }) {
return posts.map(post => <BlogPost key={post.id} post={post} />)
}
export async function getServerSideProps() {
const posts = await getBlogPosts();
return {
props: { posts }
}
}
*Example of a dynamically rendered page in Next.js.
In this example the code defines a getServerSideProps function that gets the blog posts on the site. Next.js invokes this function and passes the result to the default BlogPosts export function that generates a list of posts.
Next.js pages with a getServerSideProps function are dynamically rendered in each page view. That means that a production setup needs a compute service (such as an application server or serverless function) to perform this data fetching and rendering operation.
With the infrastructure defined by the framework pages with getServerSideProps require a compute resource to perform the rendering operation that is used, automatically infer and provision the compute infrastructure to perform the rendering operation.
In the case of Vercel, this means that a serverless function based on AWS Lambda is created and implemented with the necessary code to render the page. The knowledge of how to invoke this function is then implemented in the gateway service as part of the application's routing table.
Now, let's slightly modify the code example above, to change getServerSideProps to getStaticProps:
export default function BlogPosts({ posts }) {
return posts.map(post => <BlogPost key={post.id} post={post} />)
}
export async function getStaticProps() {
const posts = await getBlogPosts();
return {
props: {posts}
}
}
*The change from getServerSideProps to getStaticProps now generates the HTML at compile time.
In Next.js, getStaticProps implies that, instead of rendering the page in each view, the function can be invoked at compile time to generate a static HTML page that can then be delivered to the user.
Again, with the infrastructure defined by the framework, it can be inferred that no further production computing infrastructure is needed to serve this particular web page. Instead, the artifacts generated at compile time can be deployed on a static, lower cost web service infrastructure.
In the case of Vercel this means that no serverless function is implemented for this page and instead the application routing table is updated to point to the respective static file service.
While this is just one example of how code changes within a given file can trigger the implementation of various pieces of infrastructure, the concept can be expanded to a multitude of infrastructure resources.
Here are a few examples:
Gatsby has a concept called deferred static generation. When used, a serverless function is implemented to perform deferred generation on user request, similar to the server-side representation. However, the function output is then stored in AWS S3 and pushed globally so that it can be served from the Vercel edge without another function invocation.
Svelte Kit has the concept of form actions. When used it automatically creates a serverless function to perform the action. The same is true when using Remix.
Next.js supports the concept of middleware that triggers automatic provisioning of edge computing resources that execute the code during the request processing phase.
The use of the image optimization component automatically triggers the configuration of this high-performance system that adapts the image to the visiting user's device.
Internally, the framework primitives are compiled into the Vercel compiler output API, which is a more traditional declarative IaC configuration API that consumes the platform to provision the production infrastructure.
Serverless in an IaC world
IaC uses readable files that developers can version in a software-like manner to provision infrastructure such as physical servers, virtual machines and higher-level services such as databases or applications. Serverless architecture, which despite its name still uses servers, eliminates the need for developers to manage specific physical or virtual servers.
Serverless products still require developers to define and implement their respective serverless primitives, similar to more traditional infrastructure. For example, in the serverless product archetype, AWS Lambda, developers must create a lambda function and implement code in it.
In other words, the process for creating a lambda function, while a little less involved, is similar to the process for creating a new server-side service.
Here is an example of how to create a lambda function using HashiCorp's Terraform, one of the most popular IaC configuration tools. As you can see, the lambda function is just another resource:
resource "aws_lambda_function" "my_lambda" {
filename = "lambda_function_payload.zip"
function_name = "lambda_function_name"
role = aws_iam_role.iam_for_lambda.arn
handler = "index.test"
source_code_hash = filebase64sha256("lambda_function_payload.zip")
runtime = "nodejs16.x"
environment {
variables = {
foo = "bar"
}
}
}
*Creation of a Lambda function using HashiCorp Terraform
Solving the local development problem for serverless
While serverless architecture greatly simplifies management of the production infrastructure, systems running serverless code are often complex, which can cause problems with local development.
In the worst case, local development is not possible and developers must test local changes by making comparatively slow deployments to the production infrastructure.
In the best case, developers must create a local simulation of the complex serverless stack, which must then be kept up to date with the production environment.
*Comparison of the local development environment with and without framework-defined infrastructure.
Framework-defined infrastructure completely eliminates this problem, as it allows the framework to dictate production behavior. Local development can only use the framework's own tools, while the production infrastructure is automatically configured to do a scaled and optimized representation of the same underlying behavior.
This means that, by using the infrastructure defined by the framework, developers can be confident that the application will work the same way in both the local development and production environments.
In addition, this can also reduce the number of errors that occur during the deployment process, which can lead to a more reliable and stable application in production. In short, the infrastructure defined by the framework can make the development process more efficient, faster and more reliable.
The infrastructure defined by the framework and immutable implementations
Currently application code is developed almost exclusively within version control systems such as Git. Code-defined infrastructure maintains the definition in the same version control system or in a sister version control system.
Often, however, the production infrastructure itself can only represent limited points in time in the version history of the infrastructure. This is fundamentally incompatible with the infrastructure defined by the framework, which expects the production infrastructure to map directly to code in the same commit hash.
Immutable deployments resolve this incompatibility between application version history and infrastructure. Instead of deploying to a limited number of infrastructures such as "production" and "pre-production", immutable deployments create a completely new one for each deployment that is performed, so that it is immutable.
*Each commit gets an immutable implementation and generates a virtual infrastructure.
Naturally, immutable deployments are impossible in a world where infrastructure is allocated to finite physical resources, which would be consumed as new deployments are made.
However, in a serverless world, where unused infrastructure can scale to zero, immutable deployments become possible, since unused deployments do not occupy physical computing resources beyond the basic storage needed for their contents.
For example, Vercel's platform creates an immutable deployment for each git hash that is committed to a repository, which is then mapped to a completely new, serverless infrastructure.
Acknowledgements
The idea of frameworks-defined infrastructures stands on the shoulders of giants. It relies on IaC as the ultimate infrastructure interface and therefore could not work without the work of its community.
It also relies on infrastructure abstractions like Kubernetes to make it feasible given the relatively low amount of detail that can be extracted from the original source code, and the approach is particularly well suited for targeting PaaS offerings like the original Google AppEngine Platform.
Frameworks, not primitives
Here we have discussed how framework-defined infrastructure builds on IaC by understanding how a framework's patterns map to infrastructure primitives needed to run code on a scalable production system, which greatly simplifies local development.
Immutable deployments based on serverless architecture allow the production infrastructure to map seamlessly to the version of the code being executed, while being exceptionally scalable and cost-effective.
Vercel implements framework-defined infrastructure for more predictable, lower cost and lower risk DevOps through a serverless architecture.
Contact us
If your organization is interested in implementing application development projects from the hand of experts, we invite you to contact us.
*Article based on: https://vercel.com/blog/framework-defined-infrastructure