While designing modern applications, the term ‘cloud-native architecture’ is often showcased as the desired trait for applications that you migrate or build on cloud platforms. But what exactly does being cloud-native mean? Let’s delve deeper and hope to understand this paradigm.
On an abstract level, a cloud-native application should be able to make the best use of modern cloud platforms. Compared to traditional on-prem infrastructure, the cloud offers a great deal of flexibility with the concept of ‘X’ as a service. However, it also brings along a different set of architectural constraints that software architects would have to deal with.
Let’s quickly run over some points that need to be in consideration:
- Develop with cognizance: It starts from choosing the right framework [Read my older article here]. It’s a good idea to utilize decoupled and microservices, adhering to the separation of concerns (SoC) design principle. While it may not always be possible, design your components to be stateless wherever they can. Such applications are easier to scale and load balance.
- Automate as much as possible: Changes to infrastructure, CI/CD, elasticity, alerts, backups, and even billing 😉 can all be automated. Although the upfront investment is often higher, favoring an automated solution will almost always pay off in terms of the maintainability and performance of your system.
- Use managed services whenever possible: Cloud providers like Azure have a myriad of services that alleviate your effort to varying degrees. We’ll get back to this in some time.
- Things can fail at any time: To ensure that your application is resistant, employ a strategy to graceful degrade during errors. The goal is to maintain a good user experience in every possible scenario. Site Reliability Engineers often practice ‘Chaos Engineering’ for this purpose. In the context of developing cloud-native solutions, an application is expected to be resilient, highly available, fault-tolerant, and self-healing.
- Security needs to be built-in, not bolted on: With a lot more touchpoints, it is essential to ensure that your architecture conforms to the CIA triad. With malicious actors designing targeted attacks on cloud platforms, it is recommended to adopt an approach of defense-in-depth by applying authentication & authorization between each component.
- Optimize resource usage and costs: When used correctly, cloud computing can significantly reduce your total cost of ownership. Certain services can scale on-demand and you get to pay only when your code is executed. In the end, every business would want to deliver the best possible solution with minimal costs.
Getting back to the point of using managed services, I’d like to use Azure to demonstrate how different services can be used to create a cloud-native application. Azure has a huge portfolio of services to help us build, deploy and operate serverless apps on an end-to-end platform. They span across categories such as Compute, Workflows, DevOps, AI/ML, Database, Storage, Monitoring, and Analytics.
To quote an example, I had to prototype an application for a hackathon that I had attended. My tech stack was to use Flask for the backend, React for the frontend, and Mongo for the database. While it is important to be cautious about ‘vendor lock-in’, managed services can often save the organization hugely in time and operational overhead.
The usage of these services can be summarized as follows:
- The Flask API server was deployed on Azure App Services, a high-productivity, fully managed environment. There is hardly any code modification required to use these environments. With extensions for VS Code, you can deploy your app with just one click, literally. For all interactions between application tiers, JWTs were used to enforce AuthN and AuthR.
- Since Azure Cosmos DB implements the wire protocol for MongoDB, migrating and establishing a connection to the database was a cakewalk. By using a managed DB wrapper, we can benefit from capabilities such as global distribution, automatic sharding, SLA guarantees, encryption, backups, and much more.
- Static resources such as images and libraries were served from Azure Content Delivery Network. With Azure CDN, we can cache static objects loaded from the blob storage or any publicly accessible web server by using the closest point of presence (POP) server.
- Azure Functions was used to host a Node.js function that would have to run when certain events were fired. Since this workload was not suited to be a part of the API Server, it was decided to have it as a standalone component.
- As the business logic required us to support custom file uploads, Azure Blob storage was used to store and retrieve user-uploaded files. It is massively scalable and secure object storage for cloud-native workloads.
- Finally, to ensure an agile development lifecycle, Azure Repos and Azure DevOps were used to setup CI/CD pipelines and collaborate effectively. In practice, these solutions can accelerate your time to market.
A mirror of the repository that I had used for this hackathon can be found here: https://github.com/techtocore/Scalable-Microfinance-Framework. The link to our project proposal can also be found as a part of the README.md file. Since we had to prototype this solution while being tight on budget, we made use of the $100 credit we get with a student’s account on Azure. While the services I had used might have been overkill for the demo, we tried our best to align our design principles with the best practices.
There are endless possibilities to creating a well-architected cloud-native application. That’s probably the reason why there isn’t any finite set of services that you can link together and be assured that things would work the way they should. With every requirement being unique, it is key to have a thought about these guidelines while finding your niche. The collection of patterns mentioned on ‘The Twelve-Factor App’ can be considered a good checklist while building cloud-native applications.
The needs of your organization, the landscape of your IT systems, and the capabilities of your cloud provider itself continue to evolve over time. As a cloud-native architect, you should always seek to refine, simplify and improve the architecture of the system.