Chapter 1. Towards a Microservices Architecture

The Microservices style of building software has recently exploded in popularity. In the early 2010s, a few practitioners were using the term Microservices as a way of describing how an application could be composed of small, independent component. In March 2014, James Lewis and Martin Fowler documented this way of building software with an article titled “Microservices: a definition of this new architectural term” and kicked off a surge of interest, curiosity and experimentation.

Since then, adoption rates for Microservices have skyrocketed. Startups, Enterprise companies and everyone in between have learned and implemented Microservices-style architecture. Proof that adoption has taken hold can be found in the growing number of vendors hawking tools, services and solutions to cater to the Microservices practitioners. At the time of this writing, Allied Market Research predicted the global market for Microservices architecture to grow from $2.07 billion in USD to $8.07 billion USD in 2020. That kind of market estimate indicates a lot of interest, a lot of adoption and lots and lots of Microservices.

But, building software in the Microservices way has turned out to be a challenge for many adopters. The truth is, implementing a Microservices system can be a real pain. That’s because breaking an application into a complex network of independent components comes with a high cost. Each of the services that comprise a Microservices architecture add to the management, maintenance, support and testing load for the overall system. If you aren’t careful, the initial gains you get when implementing Microservices can be dwarfed by the on-going pain of having to manage services at scale.

The key to succeeding with a Microservices initiative is to start with a solid architectural base that will evolve as your system does. That means, making good decisions early about the things that will be difficult to change later on. It also means designing an architecture that makes some changes easier, so that you can learn and improve. Unfortunately, it’s really difficult to figure out what the right decisions should be unless you’ve already made mistakes and learned from them.

In theory, you could build the best architecture by building lots of Microservices systems and learning from your mistakes as you go. In fact, most of the organisations that were early adopters of the Microservices style of architecture didn’t have a goal of building Microservices-based software. Instead, they improved their architecture iteratively over time and ended up with a software system that we now recognize as a Microservices architecture.

But, it’s difficult to get the time and freedom to experiment with a software architecture. Today’s technology teams are under incredible pressure to deliver systems that achieve agility and release speed - without compromising on quality. Software architects are tasked with the dilemma of building complex, modern systems within a finite and limited time. The fundamental challenge in this scenario is how to accelerate the design and decision making effort without unknowingly introducing “technical debt” that is difficult to resolve later.

The goal of this book is to accelerate the process of building that foundation. These pages catalogue an opinionated and prescriptive guide to building a Microservices system. The architectural decisions that are documented here are based on the guidance of practitioners who have succeeded and the lessons learned from those who have paid the cost for early mistakes. By following the directions and implementing the decisions that we’ve catalogued, you’ll have a strong base of knowledge and confidence for building an architecture that works for you.

By the end of this book you’ll have built a Microservice architecture. But, before we can dive into the details of what that entails we’ll need to address the question of what we mean by Microservices. More importantly, we’ll need to understand the benefits and trade-offs of the Microservices style, so that we can understand when it’s a good idea to build software in this particular way. We’ll get started by doing a little bit of definition wrangling.

What are Microservices?

There isn’t a single, canonical definition for the Microservices style. But, a good starting place is James Lewis’ and Martin Fowler’s foundational article on Microservices. Lewis and Fowler describe Microservices as:

“an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms. […] built around business capabilities and independently deployable by fully automated deployment machinery.”

They go on to list nine characteristics that comprise a Microservices style:

  • Componentization via Services

  • Organized Around Business Capabilities

  • Products not Projects

  • Smart Endpoints and Dumb Pipes

  • Decentralized Governance

  • Decentralized Data management

  • Infrastructure Automation

  • Design for Failure

  • Evolutionary Design

That’s a lot of scope. It includes architecture, infrastructure, engineering, operationalization, governance, team structure and culture. A clear takeaway is that the microservices style is more than just building smaller services.

The book, Microservice Architecture (which the authors of this book co-wrote), takes another approach and provides two distinct definitions. One for the Microservices themselves one for a Microservices Architecture.

Names and labels are useful in architecture because they give us a way of describing complex concepts in a simple way. But, constructing an air-tight, rigorous definition for an architectural style can be a challenge. For example, in the world of building design, a Victorian style of building or Art Deco building are categorised by the general design characteristics they exhibit - rather than a strict code of angles, measurements or materials, or discrete checklist of properties that the design must have.

It’s the same for the Microservices style. Not every Microservices implementation will exhibit every characteristic that James Lewis has identified. Not every Microservices architecture will strictly meet the definition we’ve provided. But, you should still be able to know a Microservice architecture when you see it. All Microservice based applications universally share three design traits:

  1. They are composed of “services” or mini applications that are available on a network through a “machine” interface (for example, an API)

  2. The size (or boundaries) of these services are an important design consideration (for example, small or micro services)

  3. There are system-level optimisation goals that have influenced all of the design decisions (for example, improving the changeability of the system)

This is a fairly coarse-grained set of traits and covers a pretty broad set of applications. For example, there isn’t any indication of which specific tools or platforms should be used. There aren’t any formal patterns or practices defined. Most importantly, no one will define exactly what “micro” means. Instead, these guiding definitions and traits should give you enough of a sense of things to be able to know a Microservices architecture when you “see one”. We think that’s OK, because the far more important consideration are the goals, benefits and trade-offs of building an architecture with these traits. Why do people build these things in the first place?

Why Microservices?

To really understand Microservices, we need to address the question of why anyone would want to build one in the first place. It’s an important point to understand and it’s worth considering if a Microserivces system is a good fit for your particular problem space, domain and context. In a lot of cases, where we’ve seen a lack of success, you could trace the problems all the way to the early days of adoption hindered by unclear motivations for jumping in.

One thing is for certain: declaring “microservices” as a universal architectural style – “the future” and an inherently superior one, without clearly articulating tangible benefits for the adopting organization – almost never works. Choosing to develop systems in this style should not be motivated by a desire to follow a hype. Instead, it should be rooted in a clear understanding of the benefits and drawbacks of costly technical, organizational and cultural transformations that adopting Microservices will require.

So, what are the benefits? We believe that the main benefit of adopting Microservice Architecture is the ability to increase speed without compromising safety of a system. It should be pointed out that there are many ways to increase speed of software engineering, microservices are certainly not the only way. The most straightforward, and alas common, way is by accumulating technical debt or cutting corners on safety of the system. However, such inexpensive ways of increasing speed, while popular in startups, are not always an option in other circumstances. Obviously systems developed for financial, healthcare, and government sectors, among others, would not be allowed to compromise on safety for the sake of speed. And yet, competitive and market forces demand higher speed from these industries just like any other. This is where Microservices can shine, since it is one of the very few architectural approaches that fundamentally allow increase of the speed without compromising safety. Let us explain how and why it is made possible in this particular approach.

The Microservices Way: Speed, Safety & Scale

For the longest time there has been a belief in software engineering that you have to compromise between speed and safety: either you go fast or you build with high quality. You cannot have both. It makes sense intuitively. As humans, at the most basic physiological, instinctive level we are subjected to feel that high speed is always “dangerous”. It has everything to do with centuries of self-preservation, but of course isn’t always true. Generally speaking, airplanes are more safe than any other type of transportation, despite being the fastest, and modern high-speed trains are far safer than the steam-propelled ones from the 19th century. But putting our primal instincts aside, there are also more tangible reasons for why we believe that safe software cannot be built quickly, at least not complex one. Big part of the reasoning is that complex systems are built by many teams.

Building complex software is hard work. In films and TV, a brilliant engineer can heroically engineer a world-changing product over the course of a sleepless weekend. In real life it takes lots of people and a whole lot of time to produce a quality result. Multiple teams working on a complex project are typically implementing different parts of said system, following independent roadmaps, at independent pace. Periodically, these parts need to be integrated to resolve dependencies, at which point mostly autonomous teams need to coordinate their work (See Figure 1-1).

ch towards a microservices architecture coordination timeline
Figure 1-1. Sample timeline of a complex project, with coordination touch-points

Imagine team lead Jane in charge of the Accounting work-stream. Her team just finished a sprint and has a dependency on a component that is being developed by team in charge of the Shipment module, lead by Tyrone. Since road-maps are independent, it could be that Tyrone’s team is actually not done with their implementation of the needed component, in the Shipment work-stream. At this point Jane has one of two choices: she can either wait for the component to be delivered (prioritizing safety, but sacrificing speed, by putting her team on halt) and do a proper integration test, or she can rely on agreed interface contract between her team and Tyrone’s team, assuming that his team will deliver the component exactly as planned. In the latter case Jane would proceed without interruption, increasing her team’s speed, but potentially compromising the overall safety of the system since integration testing didn’t occur at the earliest possible stage and a “happy path” assumption was made.

Any team lead, in a complex, multi-team environment, regularly faces this choice between ignoring coordination costs and keeping momentum vs. acknowledging need for coordination and slowing down. Typically we choose one or the other using our intuition on risk vs. benefits, but overall, in a sufficiently complex system, when these choices occur frequently enough you would observe very pronounced tension between speed and safety. It is real, it is not related to our primal instincts and there is a way to fix it. Since coordination costs are what is causing the tension, what if we had a system specifically designed in a way to minimize the very occurrence of coordination needs? What if instead of choosing one way or the other, teams were not even facing the choice most of the time? You can have such design, emphasizing the minimization of coordination, if you have autonomous teams working on small batches of isolated work. And that is exactly what Microservice Architecture is all about, in its essence.

The Essence of Microservice Architecture

Microservice Architecture is primarily about minimization of coordination costs, in a complex, multi-team environment, to achieve harmony between speed and safety, at scale 1

Indeed, if we compare this fundamental benefit with a more expanded list of benefits of Microservice architecture from Sam Newman’s book, we can see that every benefit Sam listed can be represented as a minimization of some kind of coordination requirement:

Technology Heterogeneity

removes need to coordinate programming languages, frameworks, libraries and technology platforms across multiple teams.

Partitioned Scalability and Independent Deployments

basically removes the need to coordinate deployments and operation of various parts of the system across teams.

Organizational Alignment

is all about the creation of autonomous teams, so direct response to avoiding cost of coordination

Composability and Replaceability

removes the need to coordinate life-cycle management of parts of the larger system, across various teams.

Understanding that the fundamental force of building successful microservice architectures is aiming for the minimization of coordination is extremely useful. It gives us a universal litmus test. Building complex, distributed systems, such as microservice architecture, isn’t easy and when in doubt we should always ask ourselves “is this decision I am facing going to reduce coordination costs for my teams or not?” The right answer will be much more obvious when we view decisions from the perspective of coordination costs.

Warning

One of the dangers inherent in the Microservices style are the unintended consequences and unexpected costs that can emerge from the seemingly logical optimisation decisions we make. This can be a problem when we focus on making our decisions based on local factors, but don’t (or can’t) evaluate the overall system impact. For example, some practitioners run into problems when they create very small microservices to reduce the local team costs of development and maintenance, but run into problems later when the co-ordination costs of making these services work together creates a new bottleneck.

So, why do people build Microservices? Because they help businesses succeed. Modern organizations are under incredible pressure to adapt, change and improve more often and more quickly. Investing in a technology architecture that is purposefully designed for change speed and change safety at the scale of a large organization makes a lot of sense. The Microservices style enables companies operating in complex domains to have the agility of a simpler, smaller company while continuing to harness the power and reach of their actual size. It’s incredibly appealing and the growth in adoption proves that - but, the benefits don’t come for free. It takes a lot of up-front work, focus and decision making to build a Microserivces architecture that can unlock that value.

Building A Microservices Architecture

In the previous sections we’ve described the “what” and “why” of Microservices. With that out of the way, it’s time to introduce the real focus of this book - the “how” of Microservices. Armed with the principles, characteristics and properties that we’ve described so far we have enough of a foundation to move onto the architecture itself and what it takes to bring all of these ideals to life. It turns out that bringing all of that theory into practice takes a lot of work.

One of the biggest hurdles that first-time adopters face is dealing the enormous scope and breadth of a Microservices system. You might start by making smaller services, but pretty soon you’ll find yourself having to consider the infrastructure, co-ordination services, security, data, transactionality and the team, organization, process and culture. It’s a lot to cover and dealing with all of that scope can lead to some unique challenges. Here are three design problems that architects and engineers face:

“Long Feedback Loops”

A challenge with a Microservices system is that the decisions you make today may not impact you until much further down the road. Design decisions that make sense given the type of work or number of services you need to build today may not make sense a few months or a few years down the road. For example, when you start out you might decide to use a shared communication library to make it easier for your services to talk to each other at runtime. But, over time it may turn out that keeping that library up to date across all of your microservices and teams turns out to be a huge problem. The crux of the problem here is that it’s difficult to understand the impact of a decision you are making until problems arise - that makes it difficult to evaluate options and choose between them.

“Too Many Moving Parts”

A Microservices system is a complex adaptive system. That means that each part has an impact on all the other parts that is difficult to predict. When all those parts come together an emergent system behaviour is produced. If you’ve ever introduced a new tool or new process into an organization you’ve probably seen this first-hand. Some teams take to new stimuli and changes immediately, others need help and support to adapt, but no matter what you almost always end up with consequences to the way people work and the decisions that are made. For example, technology teams who introduce Docker containerization tooling inevitably end up adapting their development and release lifecycle as a consequence of the container deployment model. Sometimes these consequences are planned, but often times we need to deal with the unintended consequences of the changes that are introduced. This complexity is what makes Microservices system design difficult. It’s difficult to predict the specific impacts of the changes that are introduced, leading to a risk that we’ll do more harm than good with a new architecture model.

“Analysis Paralysis”

When we compound the problem of long feedback loops for our decision with the complex system we need to design, it’s easy to see why Microservices architecture is a challenge. The decisions you need to make are both highly impactful and difficult to measure. This can lead to endless speculation, discussion and evaluation of architectural decisions because of the fear of making the wrong kind of system. Instead building a system that can achieve business outcomes, we end up in a state of in-decision, trying to model the endless permutations of our choices. This is a condition that is commonly known as “analysis paralysis”. It doesn’t help that the web is full of horror-stories, “bumper sticker” advice and contradictory best-practices for building a Microserivce architecture.

Ultimately, the real challenge of building a Microservices architecture is the challenge of dealing with a big, complicated system that spans a huge scope. The good news is that this is not a unique problem to solve. In this book, we’ll be bringing together and using a set of practices and patterns that have evolved for this type of domain. We’ll also be introducing and implementing tools that embody these ways of working and make the work that happens in a Microserivces system easier, safer, cheaper and faster. All of that sits on a set of general principles that should apply to any Microserivces build.

Microservices Guiding Principles

A system’s principles communicate the most important beliefs of the team that has designed it. They represent the foundational characteristics and properties that the system should exhibit. A good set of principles describes the ideals that are pivotal to producing the system outcomes that the team desires.

Principles are useful because they shape our decisions. They provide the design and build team with a way to measure all of the architectural, design and build decisions that are made in the system. As the system moves from architecture to design and build, the principles help keep the team focused on the overarching goals of the architecture. Principles aren’t laws, but when a design decision breaks a principle it should trigger a healthy debate.

So far in this chapter we’ve described the characteristics, properties and optimization goals that define a Microservices system. But, beneath all of that are a set of general principles that drive the decision making of most Microservices practitioners. Teams that adopt these three principles stand a better chance of succeeding with the Microservices style:

Modularity

At the root of any service oriented architecture lies the principle of modularity and Microservices is no exception. As an industry, we’ve been programming and building applications in modules for a long time. The principle of modularity has been identified, applied and used by some of the most influential thinkers in computer science, including David Parnas, Edsger Dijkstra and Larry Constantine. That’s because breaking a big complex problem space into smaller, bounded pieces is the key ingredient to building a manageable solution. Embracing this principle means that we’ll build Microservices that encapsulate a part of our domain behind a service interface.

Disposability

Disposability is a design quality used to describe products that are cheap to produce, cheap to consume and designed to be thrown away after they are used. This turns out to be a key principle for the microservices in a Microservices architecture. Applying this principle to the microservices that we design and build will help us to form services that are easier to manage and maintain.

That’s because a highly disposable service is one that is easy to reproduce. At run-time, that means our disposable services can easily be instantiated, destroyed and cloned - lessening the burden on our operations teams. At design-time, disposable services can be easily thrown away and re-written - lessening the cost of making changes to existing services. If we design our system with this principle in mind, we’ll make it easier to change our decisions in the future. That’s a huge benefit for us and can reduce some of the analysis paralysis we talked about earlier. If we build a system with parts that are designed to be replaced, we stand a better chance of introducing improvements. But, we’ll need to make purposeful design decisions to bring this quality out.

Continuous Improvement

How we manage changes and improvements to our architecture is the most important consideration of all. Microservices systems that really work well almost always embody the principle of continuous improvement and incremental change. That means that the solution is designed to support a continuous cycle of experimentation, measurement, learning and improvement. There are a lot of different applications of this idea ranging from Edward Deming’s pioneering Total Quality Management to Lean methodologies and Agile principles. But, the underlying goals are similar - we want to build a system that is designed to be changed and improved throughout its lifetime. This is an essential principle for us to adopt. Continuous improvement gives us the freedom to build a system for today’s problems that can adapt and grow as our circumstances and understanding changes. The trade-off being that we need to build an architecture that is optimized for both changeability and observability so that our improvement cycles are fruitful.

These principles are important and understanding them will help us make better decisions for our Microservices architecture. They aren’t the only principles that matter for a software architecture, but these three general guiding principles are the ones that will give us the best chance of success. They don’t give us any specific guidance in terms of how to build our Microservice application (we’ll define a set of more concrete principles for our build in the next chapter). Instead, they give us an overarching direction and goal for the type of system we need to build. We’ll need to consistently consider modularity, disposability and continuous improvement as we design and implement our system. With these principles in hand, we can move on to the architectural decisions that they will influence.

Introducing the “Up and Running” Microservices Model

So far, we’ve talked about Microservices in a pretty generic way. We’ve highlighted a set of general characteristics, traits and principles that gives practitioners a lot of flexibility in how they might implement their system. We’ve also highlighted that the scope and complexity of a Microservices architecture can lead to some difficult decision making for aspiring adopters. It’s difficult to imagine how general guidance and advice will help you when you are faced with the complexities of building your first Microservices implementation. If you had enough time, you could continuously improve your system, changing the architecture as you go and end up with a solution that works. But, chances are you don’t have that luxury.

Instead, we’re going to accelerate that process. We’ll stay true to the guiding principles that we’ve outlined above, but we’ll address the analysis paralysis and the early stage challenges of Microservices adoption by introducing a model that will get you “up and running” faster. Our goal will be to establish a foundational Microservices architecture as a first step on your journey. We won’t be able to cover every aspect of a Microservices system, but we’ll focus on the parts the are the most difficult and most important for your future success. As we go through our build, we’ll implement these key elements of the architecture:

[[The Microservices Up & Running Model]] .The Microservices Up & Running Model image::images/arch-map.png["Description to come"]

Architectural Principles

We’ve covered some general Microservices principles in this opening chapter, but we’ll need to establish principles that are much more specific for our Up & Running build. In Chapter 2, we’ll document five principles that will shape our architecture and the decisions we’ll be making.

Operating Model

With our design principles defined, the first thing we’ll tackle is how the work will get done. In Chapter 3, we’ll dive into the details of our teams and how they will co-ordinate with each other to build and maintain the system.

Cloud Foundation

Our Up & Running architecture will be implemented in the cloud. In Chapters [TK & TK], we’ll design and implement a cloud-based infrastructure for our Microservices system. That will include a runtime network architecture along with some tools and patterns for continuously improving it.

Microservices Platform

Before we can start engineering our individual Microservices, we’ll need to establish some supporting infrastructure to help them communicate and operate. In Chapter [TK], we’ll implement a services architecture based on Kubernetes and Istio that can operate on our cloud foundation. Later, in Chapter [TK], we’ll show you how to implement a local version of this platform to support our engineering work.

Design Process

Once we have a platform and infrastructure in place, we’ll define a design process that will improve the way we create new microservices. In Chapter [TK], we’ll introduce a Microservices design process we’ve called “SEEDS” for designing jobs-based services and interfaces. In Chapter [TK], we’ll incorporate Alberto Brandolini’s “Event Storming” method to help us establish better boundaries and find the right size for our Microservices.

Data Model

In Chapter [TK], we’ll address the challenges of dealing with data - one of the most difficult aspects of building a Microservices architecture. We’ll outline a few approaches ranging from embedded data implementations to a more complex Event Sourcing approach.

Engineering

Finally, we’ll introduce and implement a set of practices for developing, testing and releasing Microservices in chapters TK and TK.

The Up & Running Microservices model covers a lot of ground and is prescriptive and opinionated by design. That makes it a great solution to the problem of “analysis paralysis” that is a problem for so many practitioners. But, it’s not meant to be the last Microservices architecture you’ll ever implement - in fact, it’s just the opposite. The Up & Running model is a great first step for you to take in your journey towards a Microservices system. It will allow you to quickly go through the steps of implementing a realistic sample Microservices system. The hope is that this will accelerate your learning process and give you the understanding and perspective you’ll need to build a Microservices architecture that is the right fit for your own unique constraints and context.

Learning by Doing - The Dreyfus Model of Skill Acquisition

Learning how to achieve a skill-oriented task by starting out with a highly prescriptive guidance is a tried and true method. This is explored particularly well by Stuart and Hubert Dreyfus in their Five-Stage Model of Adult Skill Acquisition, which describes how learning benefit from trying a new skill before they understand the context and rules that comes with mastery.

Summary

In this chapter, we introduced a few important foundational concepts.. We introduced definitions for both individual Microservices and for a Microservices architecture, along with three traits that will help you identify the Microservices style. We also dove into the fundamental nature of Microservices: improving speed and safety of changes at scale by reducing co-ordination costs. Finally, we introduced the Up & Running Microservices architecture model that we’ll be using in this book and the parts of the stack that we’ll be covering.

Throughout the rest of the book we will be demonstrating steps of building a microservice architecture, and our guidelines for how to approach each step, while going through a sample project: an airline’s online reservation system. Obviously, due to the complexity of implementing such system in reality, the needs in the sample system will be greatly over-simplified and implemented at the most basic level sufficient for demonstrating the principles discussed in this book. For full disclosure: neither of the authors has any experience in airline industry and we take this opportunity to apologize to anybody who has actually implemented such systems for the level of our subject-level ignorance you may observe. That said, booking a flight is an activity a lot of readers will probably be very familiar with, so we believed it was an intuitive example to employ, for demonstration needs of this book.

In the next chapter, we’ll tackle the first part of our model: the five architectural principles that will shape our build.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.43.17