This is the first article in a series illustrating the ideas in Chapter 8 of the book "Designing Distributed Systems" by Brendan Burns, published by O’Reilly and available for free from Microsoft. The goal is to show you how to migrate monolithic three-tier web applications to a user interface that integrates services, and how you can host those services using serverless functions.
To demonstrate the benefits of this, we show you how to use the Azure Dev/Ops platform and its pipelines to implement two microservices. One to cast a vote and one to obtain a report. The microservices use serverless Azure Functions. These services are deployed on the Azure platform. They are not sent to a server until an HTTP request is made because they run on a Consumption plan. This means that hardware resources are not dedicated to their operation. You’ll be able to change and extend these services to see for yourself how they work. And, you will be able to explore the tools services available on the Azure platform.
Although we do not use Cosmos DB in this project, you can migrate to it on your own, as an exercise.
In the first article, we introduce you to the concept of cloud native applications and why you should use them. We also explain how to migrate your three-tier web applications to a cloud native platform, how to set up a project and which supporting tools and applications you need.
In the second article, you create three projects in one organization. The article demonstrates how to create and add one project to an organization. You repeat those steps to create and add the other two projects.
In the third article, you create simple polls similar to those that appear on Facebook pages. The application provides an election title, start and end dates for the voting period, a set of voting options, and a report that contains the results.
In this series of articles, we’ll touch on containerization and Kubernetes briefly, but you do not require expertise in the use of Kubernetes. However, if you want to learn more about Kubernetes, have a look at these articles:
What Does "Cloud Native" Mean?
The term "cloud native" is applied to services that are distributed across multiple platforms and interact over shared or public networks. It can also be applied to applications designed to run on "off-premises" resources and which use those services.
A cloud native application contains only what it needs to do a specific job. It relies on outside services for specialized support. In comparison, monolithic, traditional three-tier web applications assume they are running on a specific network architecture with specific support services. For example, Internet Information Services v10, SQL Server 2019 and .NET Framework 4.5. They also assume that each application brings its own set of dynamic-link libraries (DLLs) to support server processing.
In applications not designed for the cloud, all the functional components of the application are included, but they are not always clearly defined or separated. In cloud native applications, functional components are separated. For example, a cloud native application calls a service using a service interface to get a list of items in a shopping cart instead of executing a query to a specific database server. A database operation occurs, but it’s hidden inside a service, and the application doesn’t know or care how this is done.
Cloud native design is based on the principle of "separation of concerns." This principle requires that software does the fewest tasks needed to be useful. Software should not do things that can be delegated to other services. For example, an application interface that acquires data from a user, should delegate the storage of the data to other services, rather than storing the data itself.
For more information on this topic, see Architectural principles.
Why Cloud Native?
What do we gain by breaking the tightly coupled services running in a single, fixed environment into a set of loosely coupled services distributed over many processors? Since the user interface hides the connection between the application and the service, the cloud platform might not have instances of the service running until a request for that service arrives. This reduces the need for physical servers.
The service must support only the connection to the user interface. If necessary, the interface can ask for more instances of the service. For example, the best way to handle authentication is with the user interface because it is the service that must accept or reject the authentication request. The user’s identity can be passed to the service, which lets the service authorize the use of specific features. This means the service is not dependent on a specific version of an operating system, or on the authentication methods being used.
This makes it possible to deliver the service in a lightweight container. With all services running in containers, orchestration services like Kubernetes can be used to monitor and manage these containers. This ensures they are always running, and that they have enough active instances to meet demand.
Designing and building applications with a service-oriented architecture means that you are free to locate each service on the platform that best meets that service’s requirements. For example, if your SQL database cannot handle the transaction volume, you can move it to a "not only SQL" (NoSQL) database like Cosmos DB. As long as you don’t change the interface, the application continues to work. Or, if you need to update your Authentication service at 3 A.M. to fix a login security defect. You can update the Authentication service to tell the user interface to keep incoming connections alive. At the same time, you can terminate connections to the Authenticator, stop all running services, and restart connections. This automatically restarts the new version. To users, this is invisible. And, then you can get a good night’s sleep.
How Do I Get There?
There are many reasons to move to cloud services like Microsoft Azure, but how do you get there? And where is "there"? Let’s take look at some technologies that answer these questions:
- DevOps are processes and supporting tools that integrate development with operational support requirements, so that deployment and operations become part of the development process.
- Continuous Integration/Continuous Delivery (CI/CD) are processes used to build and deploy new versions of an application quickly and reliably.
- Microservices are lightweight, focused, typically stateless services often accessed using an HTTP request.
- Serverless is a method of deploying apps on-demand that does not require a dedicated server.
- Containerization is a method of packaging applications such that each application interacts with a virtual machine that isolates the application from the actual machine. This allows the application to be moved without having to update any application parameters.
- Docker is a platform for building application containers.
- Kubernetes is a platform that orchestrates loading, running, and monitoring containers,
- Scalable cloud databases (like Cosmos DB) are databases capable of handling very high transaction volumes. They are often NoSQL databases, and they are capable of capturing the entire history of a transaction (for example, tracking every change to a shopping cart).
Now that you are familiar with these tools, we can say that "getting there" means creating an application that integrates services using these tools, instead of implementing them. This is similar to mashups, which take information from many external services. Mashups are collections of independent external functions held together with a small amount of special purpose code, often provided by scripts. Each service provides one focused process or dataset, and the application passes data between them to get things done.
To get there you must divide your application into services and consumers. That is, identify the parts of the code that provide information — such as a database — and those that use information — such as the user interface. Then look at the integration code between the services and the consumers to identify the "service layer interface." This will become the basis for your stateless service API.
Next, decide how to reimplement your application by integrating services. Keep in mind how you will store application state information and control access to services.
Finally, you need to determine how each service is deployed so that you can update any service at any time without disrupting the overall operation of the application. Part of the reason for this requirement is that you no longer know who is calling a service or when they are connected to it, as that link no longer exists.
Microsoft provides a wealth of knowledge about architecting and developing applications using cloud services, as well as about migrating to cloud services. Here are a few examples of the information available:
In addition to these articles, "Designing Distributed Systems" provides an excellent set of tools for developing cloud native applications. The author discusses several strategies to support migration to the cloud: repackaging applications in containers, redirecting requests for services to other providers (for example, providing HTTPS services to secure an application built for HTTP services), and developing a new application on a new architecture.
Set Up the Project
To illustrate a common use case, we’re going to build a three-tier application: a small database, a set of services to manipulate the data, and a simple HTML page to act as the user interface. The application will use OAuth 2 to authenticate users with the Azure Active Directory (AAD).
We’ll also show you how to construct a complete CI/CD pipeline to build and deploy your changes with just a few mouse clicks. We’ll do this in Eclipse using Java and HTML. The code is stored in GitHub so that you don’t need to move your code to make the application work.
The downside of this approach is that it assumes you have the resources to rebuild your application on this new architecture. If not, containerization provides a way to move to the cloud to reduce your infrastructure constraints. This way, you will have time to fully migrate your applications. While we don’t have room to discuss containerization in this article, Microsoft provides two excellent sets of books on this topic:
Now let’s discuss some specifics of our example application.
The service portion of our application is implemented using Azure Functions. Microsoft provides overviews of these services:
An Azure Function hosts code that implements a "unit of work." We’ll use the Consumption plan, which means that these functions only exist when they are called, and that Microsoft can host as many instances of our functions as our service plan supports to meet demand. This requires that our services be atomic and stateless. However, because the functions implement independent services and they do not hold state data, we can update or replace any of them at any time without any impact on any other service. Imagine being able to provide an emergency patch to your authentication process without stopping it and without any downtime as a result of pushing a complete rebuild of your application.
This application uses the Azure Database for MySQL. Microsoft provides many other data services, but MySQL is arguably the most popular database. The database we’re going to build is developed using MySQL Workbench. Our application is simple, so we’re not pushing the platform to its limits. However, it will handle most of the services required for a typical three-tier application.
Because our application is focused on services, and there are many ways to implement user interfaces, we’re going to keep our user interface to a single web page using basic HTML. The HTML is adapted from Bootstrap’s Bootstrap Started Page.
Modern applications must be secured, so we can’t expose data services to the public. Our application accepts requests only from someone with a Microsoft login. We’ll use Azure Active Directory (AAD) to ask users to log in and to provide their identity.
Supporting Tools and Process
This series focuses on applications built using Java and related technologies to show how to use these tools to implement applications based on Azure Functions. We use Eclipse as the Integrated Development Environment (IDE), and we extend it with EGit to integrate GitHub source control.
One other piece of the puzzle is the pipeline between GitHub and Azure to implement deployment. You can implement one of these two pipelines:
While GitHub Actions is simpler to implement, the Azure DevOps platform is a fully collaborative IDE that includes source code control services, pipelines, and other project management functions in one place.
This article introduces Azure DevOps services by setting up a DevOps organization and a pipeline.
For additional information on these services, you can examine some of Microsoft’s related documentation:
The articles in this series only scratch the surface of what Azure offers. Do you need Microsoft SQL Server or Cassandra for data services? Azure has it. Want to monitor changes to storage? Use Azure Functions triggered by Blob Events. Do you want to run microservices? Use Spring.
Here’s a short list of the Azure services you can investigate:
Reading about cloud native development is necessary and important, but building your own application helps you put it in perspective. The next article in this series shows you how to set up an automated CI/CD pipeline using Azure DevOps Pipelines integrated with GitHub. The third, and final, article implements a complete microservice. At the end of each article, you’ll have a working environment that you can expand to learn more.