Guest post originally published on NGINX’s blog by Libby Meren, Principal Technical Program Manager at F5
Developers, architects, and DevOps engineers are no longer satisfied with the status quo when it comes to designing and building applications. We know this because we talk to them all the time. They’re tired of the challenges in porting applications from one cloud to another. They’ve experienced the intense pressure of spinning up additional compute infrastructure quickly to meet unexpected demand. They’ve struggled to put the right assets in place after suffering a major failure or outage. Basically, they are tired of the stress and hassles of legacy application architectures.
What we’ve heard over and over again from customers and community members is that they want to build “modern apps.” The term is broad enough to mean different things to different people, so we asked our Dev and DevOps colleagues what a modern app means to them. We wanted to create a rock‑solid definition that incorporates all the crucial elements required for modern apps, to act as a checklist and reference point for application development and design efforts going forwards.
This article is in some ways a direct descendant of Thomas Wiggins seminal work, The Twelve Factor App. In fact, almost all of what Wiggins laid out continues to apply today in some form or another. But the application world has evolved to embrace containers, APIs, and truly distributed computing. The Cloud Native Computing Foundation (CNCF) has also laid out a strong high‑level vision for applications, including a supremely useful Trail Map outlining the layers and some architectural considerations for cloud‑native applications including containers, CI/CD, service mesh, observability, and distributed databases.
We wanted to build on these strong foundations and create definitions that developers, architects, and DevOps engineers can benchmark against as they design and deploy applications. We created this definition after discussing application development patterns internally for a number of months and then continuing the discussion with our customers and community members. In our thinking, we divided the definition into two parts – four high‑level concepts and six principles that support those concepts.
The Four Pillars of Modern Apps (in Eight Flavors)
During our research, we heard over and over again that modern apps must possess crucial attributes which we call the Four Pillars of Modern Apps: scalability, portability, resiliency, and agility. Granted, these terms are buzzwords and commonly used. So, let’s dive in and define what each of these means in the context of designing applications for the modern world of cloud computing and ubiquitous customer connectivity.
Pillar 1: Scalability
There are two elements to scalability: expansion of compute capacity and speed of expansion. They may be inter‑dependent, and both address the same concern. Let’s consider them in the context of “fast scaling” and “long scaling”:
- Fast scaling – The ability to increase an application’s capacity by 100% within 5 minutes. Being able to double capacity in such a short time implies that an application can quickly expand capacity to meet most unforeseen increases in demand. The spin‑up time of most cloud instances and their component workloads is roughly five minutes or less. So a five‑minute period implies a modern application is built to scale instantly, factoring in the constraints of the infrastructure layer.
- Long scaling – The ability to increase an applications capacity 10x over the period of a year or more, without requiring a major refactoring of code or large shifts in infrastructure requirements. A modern app that can “long scale” is the result of clean design with loose dependencies and loose couplings to infrastructure components. In a nutshell, for modern apps massive scalability is a built‑in, not a bolt‑on.
Pillar 2: Portability
In the contemporary “cloud era”, the dream has always been that a DevOps team can simply move an application from one cloud to another and expect it to run perfectly. The reality is quite a bit different. Even with today’s wide use of containers, the differences between clouds – in terms of dependencies, tooling, configuration nuances, and more – make it unrealistic for organizations to expect instant portability as they try to move to a hybrid multi‑cloud architecture. For example, the characteristics of load balancers and data stores on GCP may not directly mirror those on AWS or Azure. Here we lay out a definition of portability that is more realistic and appropriate.
- Functional portability – The core functional elements, code, and logic of an application must remain the same regardless of the environment in which it is running. This means that your code runs cleanly inside a container without external dependencies tightly coupled to a single environment. Designing in this way enables DevOps teams to more cleanly move an application or spin up instances of a running application in multiple clouds without having to worry about the logic and requirements of the core application.
- Management portability – The management elements of your application are environment agnostic, including observability, security, and monitoring. This enables your application to be monitored, secured, and observed in the same way – with the same tooling and same sets of reporting capabilities – no matter the environment. Management portability may require that the app designer cleanly segment these key capabilities so as not to leverage the default services or offerings of a public cloud. While initially inconvenient, designing modern apps for management portability is necessary for fulfilling the other three pillars.
Pillar 3: Resiliency
Resiliency is a generic term that might describe high availability with strict SLAs regarding downtime or more general “reliability” that leaves wiggle room for longer downtimes or time periods when an application is not required to be online, and accepts eventual consistency and service delivery as sufficient. With this in mind, we propose two types of resiliency that every modern app must have to properly serve its purpose.
- User‑facing resiliency – Application users, either machine or human, must never notice a performance issue or problem caused by a fault or failure of either a modern app itself or any service or infrastructure upon which it depends. When a user notices something is not working with an application, the implicit contract between user and application team has been broken. Today’s human users expect nearly 100% uptime on any service or application they wish to access and use. Machines may be more tolerant of outages for internal automated services, but failures of any sort inevitably cascade, impacting even automated services and highly dependent microservices.
- Failover resiliency – A modern app is able to restore within 5 minutes any critical service to 100% of what is necessary to handle average workloads. This may seem like a tall order; when a major service like Cloudflare or AWS’s storage services go down, the entire Internet can grind to a halt. That said, the point here is to encourage application designers to think about failing over to unaffected compute resources as a key part of application design and one that is implicit in self‑healing, environmentally aware modern apps.
Pillar 4: Agility
Agility is now a watered‑down buzzword that’s been beaten to death. In the context of modern apps, agility is important in that it describes an aspirational state of moving quickly and decisively with minimal toil and churn. Application dev teams benefit from agility by quickly and easily accessing the resources they need to test and push new features. DevOps teams benefit from agility through simplified and automated checking and deployment of code and infrastructure.
- Code agility – The ability to ship new code as frequently as desired by the application development team. High‑performing companies that build modern apps must create processes and environments that enable them to ship new code multiple times per day. The application itself must be designed to constantly absorb new code. In most cases, this means an application is composed of microservices and linked via APIs to enforce loose coupling and reduce intra‑application code dependencies and rigidity.
- Infrastructure agility – The ability to spin infrastructure up or down to satisfy the needs of all “customers” including application development teams, security teams, and DevOps teams. This ladders up to the previously mentioned pillars, scalability and portability. To maintain truly agile development and a modern app ethos, some type of infrastructure self‑service and catalog of ready services and applications must be available to teams.
The Six Principles
To satisfy the Four Pillars of Modern Apps, most modern apps employ architectures following many of these six principles:
- Be platform agnostic – This is related to, but not the same as, portability. Agnosticism means an application is built to run without any consideration of the platforms or environments where it is likely to run. Containers have become the de facto standard for platform‑agnostic runtimes, but an app doesn’t have to be containerized to be considered modern.It is important to bake this principle into the app at the very start of the development process, to avoid later having to abstract the code away from the platform it was developed on.
- Prioritize open source software – Open source software (OSS) is rapidly eating the world. Because modern apps require teams to be able to look under the hood of the code in order to design for portability and scalability, using OSS wherever possible is crucial to maintaining the modern apps ethos.
- Define everything (possible) by code – Modern apps must move at faster-than-human speed. Automation and programmatic definition of every aspect of modern app requirements and attributes is now table stakes. Granted, no application can be fully automated as yet; humans must remain in the loop for key decisions and to solve anomalous issues. But modern apps can distinguish themselves by steadily and continuously driving more definitions and functionalities into code and away from “tribal knowledge” or runbooks.
- Design with automated CI/CD as a native/default state – For the most part, applications are built and then poured over into CI/CD pipelines. Modern apps are designed from the ground up to take into account the eventual requirement of CI/CD automation, for code pushes, infrastructure deployment, and even security requirements.
- Practice secure development – Everyone agrees this is important, but few actually do it. It means testing all code as early as possible in the development process using software composition analysis (SCA), static application security testing (SAST), dynamic code analysis (DCA), and formatting checkers. It also means testing any code prior to deployment. CI/CD pipelines must require a “test completed” flag prior to live deployment. For DevOps teams, this also means ensuring that security best practices are followed, such as encrypting internal microservice traffic, not deploying applications unless WAFs are up and running, and automating digital certificate renewals and rotations.
- Widely distribute storage and infrastructure – End users of modern apps today can be anywhere. This doesn’t mean just in terms of at home, working, or out shopping, either – it means anywhere on the globe even or flying above it in a WiFi‑enabled plane! Modern apps must acknowledge this reality and design for a global and distributed delivery and storage capability.In addition, modern apps leverage the benefits of distributed computing for greater resiliency, performance, and scaling. Storage that is truly distributed (not just sharded) can dramatically enhance the ability to deliver on the Four Pillars by making sure data is consistent and always available. Infrastructure that is truly distributed – not just in a single geolocation – is also crucial to delivering on the requirements of the Four Pillars. Moving storage and compute closer to customers, wherever they are, can improve performance. Replicating storage and compute across multiple zones or clouds or hybrid deployments can ensure greater resiliency and scalability.
Conclusion: Modern Apps Are Both a Checklist and a State of Mind
Like Schrödinger’s cat, striving towards modern apps means simultaneously existing in two states. There is the real world of the current cloud and hybrid environments, and the time and budgetary constraints under which we develop and deliver applications. And there’s the ideal world of fully automated, highly secure, completely agnostic, instantly scalable, perfect modern apps. The reality is, of course, messy. You may be running a monolithic app that isn’t in containers, can’t double capacity in five minutes, and can’t easily be shifted to other environments without refactoring code. But that doesn’t mean the pillars and principles of modern apps laid out here won’t help you or guide you to a better state. In addition, we anticipate that as the world of technology changes and expands, we’ll have to modify our definition.
Already there are software models that don’t fit neatly into the definition: serverless computing, increasingly sophisticated low‑code/no‑code application platforms and AI‑driven security applications, CDNs with edge‑node compute capacity, and more. That’s why this definition to stretch and grow along with the always‑expanding body of better technology. Just as the The Twelve Factor App still applies today, we hope the definition of a modern app holds up well into the future. In its current state, we hope it can guide your quest to build applications in the best possible way for your users, developers, and all other stakeholders.