Introduction
Almost everything runs in the cloud now. Music and television stream from the cloud, documents are stored and edited in the cloud, and cars’ navigation systems use the cloud to calculate the route to a destination. Today, we can’t imagine using a personal computer that isn’t connected to the Internet. Put a smartphone in airplane mode and it suddenly does much less because it doesn’t have cloud access.
Most applications either run in the cloud or on a device that connects to applications running in the cloud, but many of the applications running in the cloud don’t run as well as they could because they were never designed for the cloud. We begin with Cloud Applications, which will explore why we architect applications differently to deploy them to the cloud.
Before we get to that, to set the scope for this book, let’s consider the phases of adopting the cloud, some newer application development techniques and how the cloud can help, as well as the many aspects of managing a cloud application through its full lifecycle—bearing in mind that many of these phases, techniques, and aspects, while important, are beyond how to architect the application and therefore beyond the scope of this book. We will begin by reviewing how computer hardware architecture has evolved and how application architecture has changed along with it, leading us to today’s cloud platform hardware and cloud-native applications. Then we will review the pattern format used to structure most of the content in this book and how it documents best practices to make them reusable. Next, we will review how this book is organized, containing chapters that explore different aspects of how to architect an application for the cloud, with each chapter anchored by a root pattern—an overall best practice that leads to the other more detailed best practices in the chapter. Lastly, we’ll present some strategies for how to get started reading this book. With that, you’ll have a pretty good idea of what these best practices are going to teach you.
Phases of cloud adoption
Fundamentally, cloud applications run in these technologies: Linux, containers, and container orchestrators, all in a cloud-native architecture. In putting these fundamental technologies to work, IT professionals can adopt cloud computing in three main phases:
-
Application Architecture and Design - Structure an application such that it will work well when deployed to the cloud
-
Application Development and Deployment - Create an application iteratively, configure an application environment in the cloud to host the application, and deploy the application in the environment frequently to make the improvements available to users
-
Cloud Operations and Non-functional Requirements - Monitor and manage a deployed application to keep it running reliably, distribute it geographically to avoid single points of failure, and build in compliance and security
Cloud applications are designed for and deployed using the fundamental cloud technologies, and operated using them to keep the applications running.
This book focuses on the first phase of adopting the cloud (shown in bold), how to architect and design applications to work well in the cloud. The explanation for how to do that will start with the root pattern for this entire book, Cloud Application.
Modern Application Development
Let’s consider how applications are developed. Modern application development incorporates several desirable software development techniques:
-
Modular code — Code should be developed in modules, each built by a separate team working independently. Each team for developing a module should be about 5-10 people, often described as a two-pizza team because the team is small enough that two pizzas are enough to feed everyone. Modules lend themselves to the integration of external services (that are also modular), which rather than duplicating/recreating code that already exists, encourages not only the reuse of existing code but also the integration of existing services.
-
Polyglot development — All of an application’s modules shouldn’t need to be developed in a single computer language or technology. Some developers prefer one language over another, and some problems are more easily solved by some languages instead of others. Each module can be written in any preferred language regardless of what languages are used to implement the other modules.
-
Iterative development — One of the main tenets of agile development is that code should be developed in small batches that can be developed in short iterations. Large code changes should be deconstructed into these smaller batches and performed iteratively.
-
Continuous delivery — User functionality, bug fixes, and other improvements should be delivered frequently. When users report a bug or request a new feature, once it is fixed or developed, that code change should be quickly deployed into production so that the users can benefit from it immediately. When code in production hasn’t been updated in weeks or months, it apparently must have no bugs and the users don’t want any improvements, which probably means no one is using this functionality.
-
Automated builds — When a team produces a new or revised set of code, an automated system should build it into deployment artifacts, run automated tests on it, and ultimately deploy it into production. Frequent builds are known as continuous integration (CI) and frequent deployment is known as continuous deployment (CD). Together they’re known as a CI/CD pipeline. An important part of achieving continuous delivery — making code improvements frequently and available to users as soon as possible — is running code changes through the pipeline as soon as they’re available. In addition, CI/CD pipelines should be instrumented with automated evidence collection to provide auditors and security teams with significant pipeline events and results that might be needed for enterprise or regulatory compliance.
These techniques can be accomplished without cloud computing, but the cloud greatly facilitates this style of development. The first two techniques (shown in bold) impact an application’s architecture, so this book incorporates them.
Aspects of Software Development
So far, we’ve talked about modern application development techniques that can be performed better using the cloud. Yet creating the application is just the beginning. The full software development lifecycle (SDLC) for a cloud application includes many stages, from architecture, development, and testing the application to deploying and operating it. The lifecycle incorporates several aspects of software development, including:
-
Application architecture — Architect and design a new application to fulfill users’ functional requirements and run well in the cloud. Delegate common tasks to a set of backend services such as databases and messaging systems.
-
Application migration and modernization — Migrate an existing application to rehost it in the cloud and modernize it to make it run better in the cloud, such as adopting a cloud-native architecture and refactoring to microservices.
-
Application development — Continuously deliver the application with small development teams developing independent modules in short, agile iterations.
-
Build pipeline — Automate building source code into deployment artifacts, including enforcing quality controls and building images for virtual servers and containers.
-
Application deployment — Design deployment strategies such as virtual server management and container orchestration, service mesh, external access, and the means to automate the strategies such as GitOps.
-
Environment creation — Infrastructure as code (IaC) to build the application environment (aka landing zone) the application will be deployed into.
-
Application operations — Augment the environment with tooling for site reliability engineering (SRE), observability capabilities such as monitoring, log aggregation, activity tracking, and autonomic capabilities such as failover, restart, and elastic autoscaling.
-
Cloud topology — Architecture and strategy for deploying an application across more than one data center, including public cloud zones and regions, private cloud, hybrid cloud, and multicloud.
-
Security — Protecting data and functionality in a multitenant, public environment, as well as enforcing and auditing compliance.
All of these aspects are too broad and too numerous to cover adequately in a single book. This book will cover the first two aspects (shown in bold), with an eye towards enabling these other aspects that will come later in the lifecycle.
Evolution of Application Architecture
Some people describe the cloud like it’s a completely new technology, totally different from anything IT has done before. Others derisively dismiss the cloud as nothing new, just someone else’s computer, but with better marketing. The truth lies somewhere in between, that the cloud is an evolution from earlier computing technologies that have culminated in bringing those technologies together and making them more widely accessible. We can see how the industry has gotten to cloud computing by looking at how application architectures have evolved.
The architectural structure of a computer application has evolved over time until it became what today we call a cloud application. Here’s a whirlwind overview of some key milestones in the evolution of computer hardware architecture and how application architectures evolved along with it, which shows how the IT industry has ended up with today’s cloud-native architecture. This won’t teach you how to architect applications, but it is an interesting history of how we got to where we are today.
Mainframe Application
Starting in the 1950’s, the first computers were mainframe computers. The 1960s added minicomputers, so called because whereas a mainframe took up all the space in a raised-floor computer room, what was at the time considered a miniature computer took up only half of a room. Users in their offices accessed the mainframe using dumb terminals, so-called because the terminal was just a display and keyboard without processing capabilities, just a dedicated connection to the mainframe that served as a hardline network.
As shown in Figure I-1, a mainframe application was a monolith that ran entirely on the mainframe in the computer room. All of the CPU, memory, and storage was on the mainframe. Therefore, the entire computer program and all of its program logic ran on the mainframe. The terminal just provided input and output for people, but no computation. It could be housed in the office where the people were located, but still needed to be near the computer room because the terminal required a connection to the mainframe via a dedicated network cable that was short and slow with very low bandwidth.
Desktop Application
The advent of the personal computer came in 1970s and ’80s. Like a dumb terminal, a personal computer sat on the desktop, and provided input and output via a display, keyboard, and eventually a mouse. What made the personal computer unique is that it contained a CPU, memory, and storage. This gave the computer the ability to run its own programs; while it looked like a terminal, it worked like a very small mainframe.
Personal computers originally didn’t have network connections, although some personal computers had modems that dialed up over a telephone line to act as terminals to remote servers. In the late ’80s, companies adopted LANs (local area networks) to connect to nearby computers within a building. Offices with LAN networks added file servers enabling users to share files easily.
A personal computer application was a monolith that ran entirely on the user’s desktop computer. (Figure I-2.) Structurally, its architecture was very much like a mainframe application, but simpler. Whereas a mainframe application might support multiple users, a personal computer application was highly interactive but supported just one user. Multiple users in the same office each had their own computer. If they were all using the same application, each user had their own copy of the application installed on their computer, where it ran separately from all of the others, each copy using its own data stored locally on the computer. With a LAN and file server, the application could use centrally-stored files as well, but each desktop computer still ran its own copy of the application.
Client/Server Application
In the 1990’s, computing capacity became centralized once more. LANs added server computers with compute and storage that could be shared by all of the users on the LAN. Unlike the mainframe, the office workers didn’t use dumb terminals, they used the personal computers they already had on their desktops. So each worker had their own compute and storage capacity on their desktop, as well as access to shared compute and storage capacity on the server computers.
Companies first used server computers to host database servers. The application still ran on the desktop computer, but it could access not just centralized files but also managed data. This enabled multiple workers to use data at the same time, even editing data at the same time without overwriting each other and always having access to the data with the latest changes.
Databases on centralized servers were a big improvement over sharing files on file servers, but what office workers really needed was applications that run on the server computers. The application server emerged to centrally host applications so that running applications could be shared much the way files and data were, as shown in Figure I-3. Meanwhile, offices and homes became connected to the Internet. Application servers were hosted in centralized data centers that workers’ personal computers connected to through their LAN and the Internet.
With application servers, many of the applications that office workers relied on became centralized and shared once more, much like the mainframe applications before them. While simple applications for tasks like word processing still ran entirely on the desktop, many applications became hosted in a centralized application server. Multiple workers using the same application could all share a single application running in the application server. But these workers weren’t using dumb terminals, they had compute capacity on their desktops in their personal computers.
This led to the advent of client/server computing, where shared program logic and data ran on the server and were accessed by personalized program logic running on the client. As logic was spread across computers throughout the network, a slogan emerged: The network is the computer. In other words, the computer isn’t one machine, it’s all of the computers connected by the network. The desktop computers hosted thick client applications, full mini-applications that used the shared applications in the application server to perform the complex work that needed to be shared with other users. Server computers were much more powerful and expensive, so they performed complex work. But networks had limited bandwidth, so the thick client application performed as much of its individual user’s work as possible to avoid the network.
The application server didn’t eliminate the need for a centralized database server, it changed the purpose of a database. The application server and the database server ran side-by-side on one or more server computers. An application running in an application server didn’t need to manage its data, it could delegate that responsibility to the database server. Multiple applications and multiple users in an application could all share the same data in the database.
Service-oriented architecture (SOA) applied the client/server architecture to the server application, dividing it into components that perform work for other components. Service components within the application that model business domain entities are able to vary independently and be maintained by separate development teams.
Cloud-Native Application
In the 2000s, application servers evolved into cloud computing. Whereas application servers were typically specialized to run a particular programming language or technology, cloud computers were generalized to run any program in a virtual machine. The virtual machine acted logically like a server computer—a virtual server instance—with not just virtualized compute capacity but also virtual storage and virtual networking. The cloud also hosted databases; whereas data center operations personnel who installed application servers also installed the database servers, cloud could host databases as a service so that application developers could easily create their own databases without needing help from operations or a database administrator (DBA).
Cloud extended this database-as-a-service model to make everything a service, which became known as software-as-a-service (SaaS). Workflow engines, messaging systems, authentication directories, and anything else an application needed became hosted as a service, as shown in Figure I-4.
With the cloud, the application running on the server could focus entirely on domain logic. It accessed any of the capabilities common to multiple applications—database, workflow, messaging, authentication, etc.—as shared backend services. The application server simplified into a virtual machine with the technology to run the code the application was implemented in—a runtime for a language like Java, Node.js, Python, and so on. While the server was no longer necessarily an application server, the client was no longer necessarily a desktop computer or even a laptop computer. With the popularity of the Word Wide Web (WWW), web browsers became universal, and application developers learned to create user interfaces for their server applications that could run in any web browser. A user could access an application without having to install a thick client, they could just use a single web browser to access any server application. Furthermore, the client doesn’t even have to run on a desktop. With the advent of the smartphone and tablet, mobile devices became practical. They could also run web browsers, then quickly evolved to also run small thick client apps for accessing the server applications, often via a wireless network connection.
The cloud is mainframe-like compute capacity distributed across multiple server computers running generalized application servers with built-in SaaS services for common capabilities. Therefore the structure of a cloud application is a client/server application. The server application only runs the logic for user requirements and delegates all shared functionality to backend services. The client application can be a web browser, mobile app, or even a kiosk or a chatbot.
A cloud-native application is one written or modernized specifically for the cloud, to take full advantage of the cloud computing model. The application is designed to run well in the cloud, to take advantage of the strengths of cloud computing while avoiding and compensating for its limitations. Cloud native has become the defacto best practice for designing many applications, even those to be deployed on traditional IT, and so somewhat ironically refers less to where an application resides and more to how it is built and deployed.
Cloud facilitates a new world of massive computing power available on demand cheaply. Cloud-native applications are designed to take advantage of this on demand computing power, resulting in applications that are highly scalable, always available, and that any user can access with a device and an Internet connection.
Patterns and Pattern Format
This book documents best practices as patterns. We use the pattern format because we don’t want to simply enumerate what tasks an application architect should perform, we want to teach why these practices work well and how to apply them. While explaining how to solve a problem, a pattern teaches a reader about the problem, why it is difficult to solve, and why this solution solves it well. Each pattern is a decision that can be made. A set of patterns is a very efficient way for an expert to document their expertise and for a novice to not only learn that expertise but also how to apply that expertise.
Simply put, a pattern is a reusable solution to a problem in a context. More specifically, it’s a structured way of representing design information in prose and diagrams that facilitate communicating time-tested solutions to common problems. Developing complex solutions requires applying multiple related concepts, which is where a pattern language becomes important. A pattern language is a set of related patterns that shows how the patterns are interconnected, how they fit together to form a whole greater than the sum of the parts, and how each pattern leads to others. A pattern language is generative, showing when and how to apply patterns to build solutions.
Learning a new domain of knowledge is difficult, especially in domains that are just “inside” the bleeding edge of a field of study. One of the issues with traditional academic approaches is that they aren’t up to the problem of conveying “common” knowledge. There can be limited enthusiasm for documenting know-how if “everyone knows it.” Yet not documenting what experts know makes it difficult for a novice to gain all of the information they need to be able to start working on something that uses ideas from the bleeding edge. When something is just “relatively” new, it’s often hard to distinguish good ideas from bad ones, especially when you don’t have the experience in a field to make that distinction.
The pattern idea came to computer science not from software architecture, but instead from the brick-and-mortar architecture world. The architect Christopher Alexander published two important books, A Pattern Language and A Timeless Way of Building. In the bricks-and-mortar world, Alexander uses patterns to express the interaction of forces in a problem and shows how you can resolve those forces to arrive at an elegant solution. He is also concerned with demonstrating how each pattern fits in with other patterns to convey to a novice architect the broader scope of how all the different issues come together. Several computer scientists in the late 1980s and early 1990s picked up these techniques from Alexander and found that they can also be applied successfully for developing computer software.
This book uses a modified Alexander style for the patterns. Each pattern consists of these sections:
-
Name - An identifier the pattern that describes the solution in a short phrase, usually a noun, that can easily be used in a sentence to describe applying the pattern as part of a design.
-
Context - A description of the sort of work you might have been performing that caused you to encounter this problem. The context often refers to other patterns that you may already have applied.
-
Problem - Formatted in bold, this describes the difficulty you are facing, expressed as a question. The problem statement should quickly tell you whether this pattern is currently relevant to where you are in your design.
-
Forces - This elaborates on the problem and the opposing constraints that make it difficult to solve, exploring possible solutions and showing their shortcomings. This is where an expert can teach a novice about the problem, why it is difficult to solve, and help them appreciate that a solution isn’t necessarily easy.
-
Solution - Specific guidance that you can apply to solve the problem, not just in your current situation but in the range of situations where the problem can occur. The solution answers the question posed by the Problem. The Name, Problem, and Solution are the core of the pattern.
-
Sketch - An illustration of the solution and how it’s typically applied. This being a book on architecture, the sketch is often a part of an architectural diagram.
-
Results - How the design changes because of applying the solution, how the solution resolves the forces and balances them, improving the design better than the other possible solutions.
-
Consequences - After a divider, a consideration of the strengths of the solution and its challenges.
-
Related Patterns - Guidance on other patterns to consider after this one, especially because solving one problem can lead to new ones. The links to patterns in the Context and Related Patterns sections are what make the patterns into a pattern language.
-
Examples - Optional sections showing the pattern in use, often citing well-known solutions and explaining how they embody the pattern.
The patterns in a pattern language form a methodology for designing complex solutions in a domain and a vocabulary for discussing those designs. Once a team has internalized the patterns, design discussions become much more efficient. The team no longer needs to explain what they mean by an Adapter, Intention Revealing Interface, Data Transfer Object, or Message Channel —those concepts are well-known patterns, so the team’s discussion can focus on how the concept contributes to the design. In the seemingly endless quest to make components reusable, capturing knowledge as patterns in pattern languages has proven to be more reusable than any executable code.
Organization of This Book
The pattern language in this book forms a web of patterns referencing each other. At the same time, some patterns are more fundamental than others, forming a hierarchy of patterns introducing big concepts that lead to more finely detailed patterns. The big-concept patterns form the load-bearing members of the pattern language. They are the main ones, the root patterns that provide the foundation of the language and support the other patterns.
Table I-1 lists the root patterns in this book.
Chapter | Root Pattern |
---|---|
Chapter 1: Cloud Applications |
Cloud Application |
Chapter 2: Application Architecture |
(none) |
Chapter 3: Cloud-Native Application |
Cloud-Native Architecture |
Chapter 4: Microservices Architecture |
Microservice |
Chapter 5: Microservice Design |
Model around Domain |
Chapter 6: Event-Driven Architecture |
Event Choreography |
Chapter 7: Cloud-Native Storage |
Cloud Database |
Chapter 8: Cloud Application Clients |
Client Application |
Chapter 9: Application Migration and Modernization |
(none) |
Chapter 10: Strangling Monoliths |
Strangle the Monolith |
This book groups patterns into chapters by level of abstraction and by topic area. Figure I-5 shows the primary relationships between the chapters in this book.
Now we’ve seen the chapters in this book, how they relate to each other, and their root patterns. Next, let’s look at what the chapters are about.
Relationship of Root Patterns and Chapters
The pattern language is divided into eleven chapters, which follow the relationships shown in the diagram. The relationships are among these chapters:
-
Chapter 1: Cloud Applications — The pattern language begins with this chapter. It introduces the root pattern for the entire book, Cloud Application.
The way to adopt cloud is to host applications on the cloud. The architecture and design of an application that works well in the cloud are significantly different from one that works well in traditional IT. Cloud also facilitates adding many great capabilities to applications.
-
Chapter 2: Application Architecture — This chapter is a tangential discussion that applies to traditional IT as much as the cloud. It explores three of the main approaches to architect an application: Big Ball of Mud, Modular Monolith, and Distributed Architecture.
Application architectures have evolved to make applications easier to develop and to run more efficiently. We will discuss common architectures and how they have evolved over time.
-
Chapter 3: Cloud-Native Application — This chapter explores how to design an application to work the way the cloud works. It starts with the root pattern Cloud-Native Architecture.
Architect a cloud application to take advantage of the strengths of cloud computing while avoiding and compensating for its limitations. This requires designing a cloud application differently to give it advantages beyond any traditional IT application.
-
Chapter 4: Microservices Architecture — This chapter explores how to model an application as a set of individually deployable units that can be developed by separate teams. It starts with the root pattern Microservice.
The traditional IT application architecture is one big monolith, which makes it difficult for a large team to develop and cumbersome to deploy. To avoid the same challenges in one big cloud application, break it into many small applications that each performs a separate responsibility.
-
Chapter 5: Microservice Design — This chapter shows a strategy for discovering and scoping individual microservices in an architecture. It starts with the root pattern Model around Domain.
How can developers design one big application as many small applications that each perform a separate responsibility? Analyze interactions with the application to discover where one well-encapsulated responsibility ends and another begins.
-
Chapter 6: Event-Driven Architecture — This chapter explains how to choreograph microservices that react dynamically to each other and to external events. It starts with the root pattern Event Choreography.
Complex functionality is often decomposed into a predefined set of orchestrated steps. However, some components interact indirectly through dynamically discovered relationships that are more easily modeled as choreography.
-
Chapter 7: Cloud-Native Storage — This chapter explains how to model data the way the application works and manage it the way the cloud works. It starts with the root pattern Cloud Database.
Enterprise IT has embraced the relational database as the best and only way to store and access the data the enterprise depends on. Newer databases have emerged that model data more flexibly and simplify the applications using the data. These databases not only run better on the cloud but often are also included as part of the cloud platform.
-
Chapter 8: Cloud Application Clients — This chapter explores how users outside the cloud will interact with applications in the cloud. It starts with the root pattern Client Application.
Cloud applications run in the cloud, but their users do not. Users need to be able to access the cloud application from the device they’re using, via user interfaces that are simple to install and update, and that support an increasingly wide variety of device types.
-
Chapter 9: Application Migration and Modernization — This chapter explores how to transform and modernize existing applications into cloud applications. Strategies include Lift and Shift, Virtualize the Application, Containerize the Application, and Refactor the Monolith.
Cloud applications can be developed from scratch, but often they start as traditional IT applications that the enterprise later decides to host on cloud. Simply moving a traditional IT application onto the cloud as-is has limited success. The enterprise can achieve greater success by updating the application to make it work well on the cloud. It is best to Start Small and Pave the Road when you are migrating and modernizing existing applications to run in the cloud.
-
Chapter 10: Strangling Monoliths — This chapter describes how to iteratively transform an application from a monolith into microservices. It describes the process used while migrating and modernizaing a monolith application to a microservices application.
A traditional IT application can be updated for the cloud in one big bang, but a complex application that is already running in production can be converted more easily by doing so iteratively. The trick is to keep the application running when it is half traditional IT and half cloud.
Getting Started
The standard way to read most books is straight through following the page numbers and chapter numbers. That will certainly work with this book as well and will help you gain familiarity with the patterns. While that can be the most effective way to read a book, it is not necessarily the most efficient way to use a pattern language.
In a pattern language, rather than only following a fixed chapter order, instead, you can jump in where you need to and follow a path through the patterns that are helpful to you. No two readers may read the same pattern language exactly the same way, and a single reader may not read the same pattern language exactly the same way twice.
The best place to start reading a new pattern language is with the first pattern. That’s true for this book’s pattern language, so a good place to start is with Chapter 1: Cloud Applications and the root pattern for the entire book, Cloud Application. From there, the Related Patterns section at the end of the pattern lists other patterns you might want to read next. And those patterns also have Related Patterns sections. Following these pattern links will eventually lead to most of the book, though often not in page or chapter order.
This pattern language is designed to facilitate learning it at a high level before diving into all of the details. To get a good feel for the pattern language overall, read only the root patterns. They are listed in Figure I-5, and are the first pattern in each of the chapters. Some chapters don’t have root patterns and it’s OK to skip those chapters for this overview, they’re not as key to the pattern language as the chapters with root patterns. The root patterns don’t have to be read in chapter order, but that’s a pretty good way to understand them. In fact, the book presents the chapters in this order because it is a logical progression through the main topics of the book and likewise for the topics’ root patterns. As with any pattern language, this ordering isn’t the only path through and may not always be the best for all audiences, but it is a good way to get started.
Keep in mind the chapter relationships shown in Figure I-5, are also a good order for following the root patterns. The order of the chapters in this book is one suggested ordering, but only one of many useful approaches, and some readers will find other orderings more helpful for focusing on the material most relevant to them.
Once you’re familiar with a pattern language, the best way to apply it is to think about the specific design problem you’re facing currently. Read through the patterns that seem like they may help you solve that problem, pick one that seems promising, apply it, and then see if and how it helps. The Related Patterns section will lead to other patterns you should consider. Once you’ve applied all of the patterns that seem relevant, step back and repeat the process to consider what is now the next problem you’re facing in your design and look for patterns that may apply. In this way, you’ll use the pattern language to create a custom design by applying the patterns in a specific order to create that design.
The act of reading a pattern language customizes it for each reader and each particular application. As you get used to navigating the patterns in this way, you’ll find the chapter order is irrelevant, and you may find that some of the patterns are never relevant to your design and so may never read those pages of the book. That is OK, each reader will use the patterns differently, and how you use the patterns is the right way for your design.
Get Cloud Application Architecture Patterns now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.