Booklet Microservices For Modern Commerce - Compressed
Booklet Microservices For Modern Commerce - Compressed
m
pl
im
en
ts
of
Microservices for
Modern Commerce
Dramatically Increase Development Velocity
by Applying Microservices to Commerce
Kelly Goetsch
The end of commerce
platforms as you know them
Quickly deliver new features to
market
commerce
platform
Kelly Goetsch
The O’Reilly logo is a registered trademark of O’Reilly Media, Inc. Microservices for
Modern Commerce, the cover image, and related trade dress are trademarks of
O’Reilly Media, Inc.
While the publisher and the author have used good faith efforts to ensure that the
information and instructions contained in this work are accurate, the publisher and
the author disclaim all responsibility for errors or omissions, including without limi‐
tation responsibility for damages resulting from the use of or reliance on this work.
Use of the information and instructions contained in this work is at your own risk. If
any code samples or other technology this work contains or describes is subject to
open source licenses or the intellectual property rights of others, it is your responsi‐
bility to ensure that your use thereof complies with such licenses and/or rights.
978-1-491-97092-8
[LSI]
Table of Contents
Foreword. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vii
2. Introducing Microservices. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Origins of Microservices 11
Introducing Microservices 12
Advantages of Microservices 28
The Disadvantages of Microservices 33
How to Incrementally Adopt Microservices 38
3. Inner Architecture. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
APIs 42
Containers 50
Lightweight Runtimes 51
Circuit Breakers 51
Polyglot 53
Software-Based Infrastructure 53
4. Outer Architecture. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Software-Based Infrastructure 56
Container Orchestration 56
API Gateway 65
Eventing 66
v
Foreword
vii
We now have a large catalog with hundreds of completely reusable
“Lego block”–like commerce APIs that can be used to build innova‐
tive experiences for our customers. We must be able to adapt quickly
to changes in consumer technology. Just 10 years ago, smartphones
barely existed. Now they’re crucial to our everyday lives. Microservi‐
ces allows us to quickly adapt to changes in consumer technology.
We can have a new app running in just a few days.
Microservices has been transformational to our business in many
ways and we will continue to make deep investments as we trans‐
form to be the market leader.
viii | Foreword
CHAPTER 1
A New Commerce Landscape
1
less of how they interact with a brand. Whereas tailoring an
experience to a channel is acceptable, offering a fragmented experi‐
ence is not.
Value-Added Features
A primary driver of online shopping is the additional functionality
that that it offers beyond that of a physical store. These features
include a larger product assortment, ratings/reviews, more in-depth
product descriptions, additional media (enhanced product photos/
videos), related products, tie-ins to social media, and so on.
Convenience
Barely a hundred years ago, physical retail stores were the only way
to make a purchase. Then, catalogs came of age. In the late 1990s,
the Internet began to take off, and consumers could purchase
through a website. Later, smartphones came of age when the iPhone
was released in 2007. In the decade since then, the number of devi‐
ces on the market has exploded, from smart watches to Internet-
enabled TVs. Nearly every Internet-connected consumer electronic
device hitting the market today offers an interface that consumers
can use for shopping. New user interfaces are hitting the market
weekly and successful brands must be able to offer their unique
experience on every one of these new devices.
Organization Structure
Any organization that designs a system (defined broadly) will pro‐
duce a design whose structure is a copy of the organization’s com‐
munication structure.
—Mel Conway (1968)
Each team has its own work location, process for receiving work
(typically a ticketing system of some sort), service level agreements,
process for assigning work to individuals, release cycles, and so on.
This strict separation makes it difficult to make any changes that
span multiple teams. For example, suppose that a Java developer
receives a requirement to begin capturing a customer’s shoe size
during registration. In a typical enterprise, this would be incredibly
difficult even though the work should take about two minutes for
any competent developer to perform. Here’s a non-exhaustive list of
the serial steps required to perform this:
These steps are exhausting even just to read, yet this is how even
minor changes are implemented in enterprises. These steps don’t
even include the UI updates. This bureaucracy, due to horizontally
specialized teams, is what leads to quarterly releases.
With teams so large and isolated, a corrosive culture of distrust
develops. Rather than working together, teams are motivated to
erect more bureaucracy (change requests, architecture review pan‐
els, change control boards, etc.) to cover themselves in the event of a
problem.
Coupling
Today’s enterprises are characterized by extreme coupling, both in
terms of organization and architecture.
Let’s begin with organization.
Enterprises cause extremely tight coupling between horizontal layers
because they build teams of people who have only one focus. For
example, each user interface (point of sale, web, mobile, kiosk, etc.)
has its own team. Those UIs are tightly coupled to one or more
applications, which are each owned by a separate team. Often,
there’s an integration team that glues together the different applica‐
tions. Then, there’s a database on which all teams are completely
dependent. Infrastructure is managed by yet another team. Each
team, no doubt, is good at what they do. But those barriers cause
tight coupling between teams, which introduces communication
overhead and causes delays.
It would be as if an auto repair shop had one person to order tires,
another person to unscrew the lug nuts, another person to remove
the old tire, another person to balance the new tire, another person
to mount it, and one final person to screw on the lug nuts. Sure,
Packaged Applications
Many of today’s enterprise applications are large, monolithic pack‐
aged applications that are bought from a handful of large software
vendors, deployed on-premises, and heavily customized. Many
packaged commerce applications have millions of lines of custom‐
ized code.
These applications are sold to thousands of customers. Those thou‐
sands of customers each write millions of lines of customized code
on top of the application. As the number of customers increases, the
vendors that sell the software are increasingly unable to make
changes because of all the trouble it would create. The more success‐
ful the product, the slower it evolves. It gets frozen in time.
This is why many in the industry have dropped “e” from “eCom‐
merce” to reflect that it’s not something different. Consumers should
be able to buy online and pick up or return in-store, browse in-store
and buy online, have access to the same promotions, and so on. If
channels are offering different data (subset of products, different
prices, etc.) it should be because the experience is being optimized
for each channel or there are opportunities to price discriminate.
Again, the experience per channel can vary, but the variations are
deliberate rather than as a result of IT fragmentation.
Most of today’s enterprise commerce platforms are sidecars on top
of the old in-store retail platforms. There might be additional side‐
cars on top of the commerce platforms for other channels, such as
mobile. Each of these systems acts as its own mini system of record,
with heavy integration back to the old in-store retail platform, as
shown in Figure 1-3.
Each system has its own view of pricing, promotions, products,
inventory, and so on, which might or might not ever be reconciled
with the eventual system of record. For example, many retailers have
web-only pricing, promotions, products, inventory, and so forth.
Origins of Microservices
Although the term “Microservices” first rose to prominence in 2013,
the concepts have been with us for decades.
The software industry has long looked for ways to break up large
monolithic applications into smaller more modular pieces with the
goal of reducing complexity. From Unix pipes to dynamic-link libra‐
ries (DLLs) to object-oriented programming to service-oriented
architecture (SOA), there have been many attempts.
It is only due to advances in computer science theory, organizational
theory, software development methodology, and infrastructure that
11
microservices has emerged as a credible alternative to building
applications.
So what are microservices?
Introducing Microservices
Microservices are individual pieces of business functionality that are
independently developed, deployed, and managed by a small team
of people from different disciplines (see Figure 2-1).
Introducing Microservices | 13
Figure 2-2. Microservices offers less complexity over time
Defining Microservices
Now that we’ve reviewed some of the high-level characteristics of
microservices, let’s look at what the defining characteristics actually
are:
Single purpose
Do one thing and do it well.
Encapsulation
Each microservice owns its own data. Interaction with the
world is through well-defined APIs (often, but not always,
HTTP REST).
Ownership
A single team of 2 to 15 (7, plus or minus 2, is the standard)
people develop, deploy and manage a single microservice
through its lifecycle.
Autonomy
Each team is able to build and deploy its own microservice at
any time for any reason, without having to coordinate with any‐
one else. Each team also has a lot of freedom in making its own
implementation decisions.
Multiple versions
Multiple versions of each microservice can exist in the same
environment at the same time.
Single purpose
A large monolithic application can have tens of millions of lines of
code and perform hundreds of individual business functions. For
example, one application might contain code to handle products,
inventory, prices, promotions, shopping carts, orders, profiles, and
so on. Microservices, on the other hand, each perform exactly one
business function. Going back to the founding principles of Unix,
Write programs that do one thing and do it well, is a defining charac‐
teristic of microservices. Doing one thing well allows the teams to
stay focused and for the complexity to stay at a minimum.
It is only natural that applications accrue more and more functional‐
ity over time. What begins as a 2,000-line microservice can evolve to
one comprising more than 10,000 lines as a team builds competency
and the business evolves. The size of the codebase isn’t as important
as the size of the team responsible for that microservice. Because
many of the benefits of microservices come from working together
as a small tight-knit team (2 to 15 people). If the number of people
working on a microservice exceeds 15, that microservice is probably
trying to do too many things and should be broken up.
Introducing Microservices | 15
In addition to team size, another quick test is to look at the name of
a microservice. The name of a microservice should precisely
describe what it does. A microservice should be named “Pricing” or
“Inventory”—not “PricingAndInventory.”
Encapsulation
Each microservice should exclusively own its data. Every microser‐
vice should have its own datastore, cache store, storage volume, and
so forth (see Figure 2-3). No other microservice or system should
ever bypass a microservice’s API and write directly to a datastore,
cache layer, file system, or anything else. A microservices’ only inter‐
action with the world should be through a clearly defined API.
Ownership
A typical enterprise-level commerce application has hundreds of
staff who work on it. For example, it would not be uncommon to
have 100 backend developers building a large monolithic applica‐
tion. The problem with this model is that staff members don’t feel
like they own anything. A single developer will be contributing just
one percent of the codebase. This makes it difficult for any single
developer to feel a sense of ownership.
In economic terms, lack of ownership is known as a Tragedy of the
Commons problem. Individuals acting in their own self-interest
(e.g., farmers grazing their cattle) almost inevitably end up making
the common resource (e.g., public lands) less better off (by over-
grazing). It’s the exact same problem in a large monolithic applica‐
tion—hundreds of staff acting in their own self-interest end up
making the monolithic application more complicated and add more
technical debt. Everyone must deal with complexity and technical
debt, not just the individual who created it.
Microservices works in large part due to ownership. A small team of
between 2 to 15 people develop, deploy and manage a single micro‐
service through its entire lifecycle. This team truly owns the micro‐
service. Ownership brings an entirely different mentality. Owners
care because they have a long-term vested interest in making their
microservice succeed. The same cannot be said about large mono‐
Introducing Microservices | 17
lithic applications with which hundreds of people are involved. Sup‐
pose that a team has five members—three developers, one ops
person, and one business analyst. In this case, any given developer
contributes 33% of the code. Every person on that team is making a
substantial contribution and that contribution can be easily recog‐
nized. If a microservice is up 100 percent of the time and works per‐
fectly, that team is able to take credit. Similarly, if a microservice is
not successful, it’s easy to assign responsibility.
On an individual level, microservices brings out the best in people
because they can’t hide in a larger team. The performance of indi‐
viduals in any team take the shape of a standard bell curve. The top
performers like microservices because they can have an outsized
influence over a microservice. Microservices attracts high perform‐
ers because it allows them to have more responsibility.
A team responsible for a microservice should be composed of 2 or
more people but no more than 15. The best team size is seven, plus
or minus two people. There’s some interesting research and anec‐
dotes that impact team size.
2 people
Insufficient skills to draw from. Two people isn’t quite a “team.”
It’s more like a marriage—which can bring its own challenges.
3 people
Unstable grouping because one person is often left out, or one
person controls the other two.
4 people
Devolves into two pairs rather than a cohesive team of four
people.
5 to 9 people
Feeling of a “team” really begins. Enough skills to draw from,
large enough to avoid the instability of smaller teams, small
enough that everyone is heard.
10 to 15 people
Team becomes too large. Not everybody can be heard, smaller
groups might splinter off.
Each team should have a wide mix of skills, including development,
operations, datastore administration, security, project management,
requirements analysis, and so on. Often, teams are composed of one
Introducing Microservices | 19
Autonomy
Ownership cannot exist without autonomy. Autonomy can mean
many things in the context of microservices.
Each team should be able to select the technology best suited to
solve a particular business problem—programming language, run‐
time, datastore, and other such considerations. The tech stack for a
microservice that handles product image resizing is going to look
very different from a tech stack for a shopping cart microservice.
Each team should be able to select the tech stack that works best for
its particular needs and be held accountable for that decision. Each
microservice should expose only an API to the world; thus the
implementation details shouldn’t matter all that much. That being
said, it makes sense for enterprises to require each team to select
from a menu of options. For example, the programming language
can be Java, Scala, or Node.js. Datastore options could be MongoDB,
Cassandra or MariaDB. What matters is that each team is able to
select the type (Relational, Document, Key/Value, etc.) of product
that works best, but not necessarily the product (MongoDB, Cassan‐
dra, MariaDB) itself. Teams should be required to standardize on
outer implementation details, like API protocol/format, messaging,
logging, alerting, and so on. But the technology used internally
should be largely up to each team.
Along with technology selection, each team should be able to make
architecture and implementation decisions so long as those deci‐
sions aren’t visible outside of the microservice. There should never
be an enterprise-wide architecture review board that approves the
architecture of each microservice. Code reviews should be per‐
formed by developers within the team—not by someone from the
outside. For better or worse, the implementation decisions belong to
the team that owns and is responsible for the microservice. If a
microservice is not performing to expectation, engage a new team to
build it. Remember, each microservice should be fairly small
because it is solving a very granular business problem.
Each team should be able to build and run its own microservice in
complete isolation, without depending on another team. One team
shouldn’t build a library that another team consumes. One micro‐
service shouldn’t need to call out to another microservice as it starts
up or runs. Each microservice is an independent application that is
built, deployed and managed in isolation.
Multiple versions
Another defining characteristic of microservices is the ability (but
not obligation) to deploy more than one version of a microservice to
the same environment at the same time. For example, versions 2.2,
2.3, 2.4, and 3.0 of the pricing microservice may be live in produc‐
tion all at the same time. All versions can be serving traffic. Clients
(e.g., point of sale, web, mobile, and kiosk) and other microservices
can request a specific version of a microservice when making an
HTTP request. This is often done through a URL (e.g., /inventory/2/
or /inventory?version=2). This is great for releasing minimum viable
products (MVPs). Get the first release out to market as v1 and get
people to use it. Later, release v2 as your more complete product.
You’re not “locked in” in perpetuity, as you often are with monoliths.
Monolithic applications, on the other hand, don’t have this level of
flexibility. Often, only one version of a monolithic application is live
in an environment at any given time. Version 2.2 of a monolithic
application is completely stopped and then version 2.3 is deployed
and traffic is turned back on.
The ability to support multiple clients is a strength of microservices
and is an enabler of real omnichannel commerce. Clients and other
microservices can code to a specific major API version. Suppose that
the ecosystem codes to a major version of 1. The team responsible
for that microservice can then deploy versions 1.1, 1.2, 1.3, and
beyond over time to fix bugs and implement new features that don’t
break the published API. Later, that team can publish version 2,
which breaks API compatibility with version 1, as shown in
Figure 2-6.
Introducing Microservices | 21
Clients and other microservices can be notified when there’s a new
version available but they don’t have to use it. Versions 1 and 2 coex‐
ist. A year after the publication of version 2 when nobody is using
version 1 any longer, version 1 can be removed entirely.
Monolithic applications suffer from the problem of forcing all cli‐
ents to use the same version of the application. Any time there’s an
API change that breaks compatibility, all clients must be updated.
When the only client used to be a website, this was just fine. When it
was mobile and web, it became more difficult. But in today’s omni‐
channel world, there could be dozens of clients, each with its own
release cycle. It is not possible to get dozens of clients to push new
versions live at the same time. Each client must code to its own ver‐
sion, with migrations to later versions happening as each client goes
through its own release cycles. This approach allows each microser‐
vice to innovate quickly, without regard to clients. This lack of cou‐
pling is part of what makes microservices so fast.
The major challenge with supporting multiple versions of a micro‐
service concurrently is that all versions of a microservice in an envi‐
ronment must read/write to the same backing datastore, cache store,
storage volume, or other storage schema. A microservice’s data
remains consistent, even though the code might change. For exam‐
ple, version 1.2 of the product microservice might write a new prod‐
uct to its backing database. A second later, version 2.2 of the same
microservice might retrieve that same product, and vice versa. It
can’t break. Similarly, an incoming message might be serviced by
any version of a microservice.
Evolvable APIs
It is possible to offer a single evolvable API rather than multiple ver‐
sions of one API. Having one API that evolves solves the problem
mentioned in the previous paragraph, but comes at the cost of
reducing the level of changes you can make. For example, you can’t
just rewrite your pricing API, because you might have a dozen cli‐
ents that depend on the original implementation. If you don’t need
to radically rewrite your APIs, having a single evolvable API is pref‐
erable to having multiple versions of the same API.
Support for more than one version adds a new dimension to devel‐
opment and deployment. That’s why microservices is fundamentally
Choreography
Centralization is a defining characteristic of traditional monolithic
applications. There is one centralized monolithic commerce applica‐
tion. Often, there is a top-down “orchestrator” system of some sort
that coordinates business processes across multiple applications. For
example, a product recall is issued, which triggers a product recall
workflow. Figure 2-7 presents a very simple overview of what that
workflow would look like.
This model introduces tight coupling. The workflow must call APIs
across many different applications, with the output of one being fed
into the next. This introduces tight coupling between applications.
To change the warehouse’s APIs now requires an update and retest‐
ing of the entire workflow. To properly test the workflow, all other
applications called by that workflow need to be available. Even sim‐
ple API changes can require retesting an enterprise’s entire backend.
This leads to quarterly releases to production because it’s just not
possible to perform this testing more frequently than that. This cen‐
tralized orchestrator is also a single point of failure.
Microservices favors choreography rather than orchestration. Rather
than each application being told what to do by a centralized coordi‐
nator, each application has enough intelligence to know what to do
on its own.
Introducing Microservices | 23
Microservices is often referred to as having
smart endpoints and dumb pipes. Endpoints are
the individual REST APIs that are used to inter‐
face with individual microservices. Pipes are just
plain HTTP. Requests from system to system are
rarely if ever modified while en route.
SOA is often referred to as having dumb end‐
points and smart pipes. Endpoints are exposed
methods in large monolithic applications that
cannot be called independently. Pipes are often
routed through Enterprise Service Bus–type lay‐
ers, which often apply vast amounts of business
logic in order to glue together disparate mono‐
lithic applications.
Eventual Consistency
Monolithic applications are strongly consistent, in that any data
written to a datastore is immediately visible to the entire application
because there’s only one application and one datastore.
Microservices are distributed by their very nature. Rather than one
large application, there might be dozens, hundreds or thousands of
individual microservices, each with its own datastore. Across the
entire system there might be dozens of copies of common objects,
like a product. For example, the product, product catalog, and
search microservices might each have a copy of a given product. In a
monolithic application, there would be exactly one copy of each
product. This isn’t cause to panic. Instead, consider what this dupli‐
cation buys you—business agility.
Adopting microservices requires embracing eventual consistency.
Not all data will be completely up to date. It doesn’t need to be. For
example, most of the world’s ATMs are not strongly consistent. Most
ATMs will still dispense money if they lose connectivity back to the
centralized bank. Banks favor availability over consistency for busi‐
ness reasons—an ATM that’s available makes money. Much of the
Introducing Microservices | 25
world favors availability over consistency. That most data in com‐
merce systems is consistent is more a byproduct of the prevailing
monolithic architecture and centralized databases rather than a
deliberate choice.
Suppose that an end client needs to render a category page with 10
products. In a monolithic architecture, it would look something like
Figure 2-10.
The category method calls the product function 10 times. The prod‐
uct function calls the inventory and pricing functions. All of this
code runs inside a single process. The application is within a few
milliseconds of the database.
Now, let’s take a look at Figure 2-11 to see how this would look if
you directly mapped it back to a microservices-style architecture.
With tens of milliseconds separating each microservice, this clearly
wouldn’t work. Synchronous calls between microservices should be
exceptionally rare, if entirely non-existent.
Instead, the proper way to do this with microservices is to build a
product catalog microservice that is the only microservice accessed
to retrieve product data. The category, product, inventory, pricing,
and all other related microservices each act as the system of record
for their own individual pieces of data, but they publish that data
internally in the form of events. The product catalog microservice
will pull down those events and update its own datastore, as demon‐
strated in Figure 2-12.
Yes, this results in duplication. There are now many copies of each
object. But consider this: how much does storage cost today? It is
minuscule compared to the benefits of faster innovation. The over‐
Introducing Microservices | 27
Sometimes data needs to be strongly consistent. For example, the
shopping cart microservice should probably query the inventory
microservice to ensure that there’s inventory available for each prod‐
uct in the shopping cart. It would be a terrible experience if a
customer made it to the final stage of checkout before being told a
product was unavailable. There are two basic approaches to strong
consistency:
There are very few instances for which data must be truly consistent.
Think very hard about introducing this as a requirement, because
the consequences (coupling, performance, availability) are so dam‐
aging. Don’t fall into the trap of implementing microservices as a
monolith.
Advantages of Microservices
Microservices offers many advantages. Let’s explore some of them.
Advantages of Microservices | 29
The responsibility for these centralized functions is pushed down to
each small team, which has the skills required to be self-sufficient.
Each team simply cares about its inputs (typically events and API
calls) and its outputs (typically events and APIs). Eliminating
dependencies is what dramatically increases development velocity.
Accountability
Each small team owns a single microservice from its inception to
retirement. That team has great freedom to make architecture,
implementation, and technology decisions. All of that freedom
means each team and each member of that team is accountable. If a
team picks a hot new open source project but it fizzles out in six
Advantages of Microservices | 31
business function. For example, the team building the inventory
microservice are able to develop extensive expertise with distributed
locking.
Easier Outsourcing
It’s difficult to outsource roles such as development, operations, and
QA with traditional monolithic applications because everything is so
tightly coupled. For example, it might take only a few days for a
third-party system integrator to build a new payment module, but it
will take weeks to set up development environments, learn the pro‐
cess to make changes to the shared database, learn the process for
deploying code, and so on.
Microservices makes it very easy to outsource. Instead of a payment
module, a system integrator could be tasked with creating a payment
microservice, which exposes a clean API. That system integrator
could then build the payment microservice and host it themselves as
a service or hand it over for hosting alongside the other microservi‐
ces. By outsourcing in this manner, dozens or even hundreds of
microservices could be developed in parallel by different vendors,
without much interaction between the different teams.
Rather than hiring a system integrator, APIs can be bought from
third-party software vendors. For example, there are vendors that
offer payment APIs as a service. Or product recommendations as a
service. These vendors can build highly differentiated features as a
service that can simply be consumed as a service.
Security
Security is most often an afterthought with large monolithic applica‐
tions. Inevitably, the application is developed and secured later,
often just in the UI. One application thread, if compromised, can
call any function in the entire application. Even if proper security is
implemented, there’s no real way to enforce that developers use it.
Many developers don’t implement it because the code is complicated
and messy.
Microservices is very different. Each microservice exposes a small
granular bit of business functionality as an API (or two). It’s easy to
see exactly who or what is calling each API. Rather than dealing
with security at the application layer, you can instead use an API
gateway or API load balancer. These products allow you to define
Organizational Maturity
Organizations must have a strong structure, culture, and technical
competency. Let’s explore each.
As we’ve already discussed, an organization’s structure dictates how
it produces software. Centralized organizations oriented around lay‐
ers produce layered monolithic applications. Even simple changes
require extensive coordination across those layers, which adds time.
The proper structure for an organization that wants to create micro‐
services is to reorganize around products. Rather than a VP of oper‐
ations, there should instead be a VP of product catalog, with all of
the product catalog–related microservices rolling up to her. It’s a
fundamental change in thinking, but one that will ultimately pro‐
duce better software. DevOps, which we’ll discuss shortly, is one step
toward tearing down the organizational boundaries between devel‐
opment and operations. If that transition went well, the blurring of
team boundaries won’t be too foreign.
Duplication
A goal of microservices is not to reduce costs. Even though that can
happen over time, cost savings through consolidation and standard‐
ization is not a benefit of microservice. In a traditional enterprise,
the CIO buys one database, one application server, and so on, and
forces everyone to use it. Many will go a step farther and build one
large database, one large cluster of application servers, and so forth.
This makes a lot of sense if the goal is to reduce cost. But it doesn’t
work with microservices, because it introduces coupling, which
greatly harms speed.
Each microservice team is autonomous and should be able to pick
the best product for each layer. As discussed earlier, each team
should be given a menu of supported products to choose from. For
example, the programming language can be Java, Scala, or Node.js.
The team can purchase formal product support for all of these
options. Rather than one product there are now potentially a hand‐
ful. A lot of cost-focused managers would say “Just use Java,” but
Eventual Consistency
One of the biggest issues that enterprises have with microservices is
the fact that not all data is strongly consistent. A product can exist in
20 different microservices, each having a copy of the product from a
different point in time. Enterprises are used to having one single
database for all commerce data, which is strongly consistent—one
copy of the product that is always up to date. This is not the micro‐
services model because it introduces coupling, which harms devel‐
opment velocity.
Within a commerce platform, enterprises have historically expected
that data is strongly consistent. But data has never been consistent
across an entire enterprise. For example, CRM, ERP, and commerce
applications each have their own representation of a customer, with
updates to data being propagated between applications asynchro‐
nously.
Within a microservices-based commerce system, there is always at
least one microservice that has the most up-to-date data. For exam‐
ple, the product microservice owns all product data. But the product
catalog and search microservices might have a cached copy of that
data. Consider using a wiki or some other internal place to docu‐
ment which microservice owns which bit of data.
Testing
Microservices makes testing both easier and more difficult at the
same time. Let’s begin from the developers’ workstation and work
up to production.
Monitoring
Like testing, monitoring becomes both easier and more difficult at
the same time.
The health of each microservice is very easy to assess. It’s the health
of the overall system that’s trickier to assess. If the Order Status
microservice is unavailable and the application is able to gracefully
handle the failure, there’s no problem. But what happens if the
Order microservice is down? Where is the line drawn between
healthy and unhealthy? That line matters because at some point you
need to cut off traffic and fix the problem(s). A monolithic applica‐
tion, on the other hand, is fairly easy to monitor. It’s typically work‐
ing or it’s not.
Another issue that microservices introduces is end-to-end transac‐
tion tracing. Out of the dozens, hundreds or thousands of microser‐
Net New
If you’re building a large new commerce platform from scratch, with
hundreds of developers, it makes sense to start using microservices.
In this model, each team is responsible for one microservice
(Figure 2-14). All microservices are on the same level.
Figure 2-14. How you would structure your microservices if you were
starting from scratch
It does not make sense to start with this approach if you’re building
a smaller application or you don’t know whether the complexity will
justify the initial overhead of microservices. Microservices is best
for when you’ll be writing millions of lines of code with hundreds or
even thousands of developers. Do not prematurely optimize.
For example, say you need to overhaul pricing in your monolith due
to new business requirements. Rather than doing it in the monolith,
use this as an opportunity to rewrite pricing in a microservice. The
monolith will then call back to the pricing microservice to retrieve
prices.
Over time, more and more functionality is pulled out of the mono‐
lith until it eventually disappears.
This model is the standard operating procedure for most commerce
platforms today. Few do payments, tax calculation, shipping, or rat‐
ings and reviews in-house. It’s all outsourced to a third-party ven‐
dor. Breaking out functionality into separate microservices is the
same model, except that the team behind the API you’re calling hap‐
pens to be part of your company.
The hard part about doing this is that the microservices will all be
written to code to the same database, since there was probably only
one database in the monolithic application.
Again, this is hard to do but well worth it if you can make it work.
Summary
Speed is the currency in today’s fast-paced market. The number of
hours it takes to get a new feature or user interface to market mat‐
ters very much. Microservices is the absolute best architecture for
getting new features and user interfaces to market quickly. There are
many advantages and, yes, a few disadvantages to this architecture,
but overall you’ll find none better for large-scale commerce.
Now that we’ve covered theory, let’s discuss the actual technology
you’ll need to put microservices into practice.
41
runtime, and so on. In an organization built around minimizing
costs, this strategy makes sense. The procurement department can
squeeze the best prices from the vendors and you can build teams
with very specialized product expertise. But this is completely con‐
trary to the principles of microservices because it forces organiza‐
tions to be horizontally tiered, with each tier focused on a specific
layer. This introduces coupling between teams, which dramatically
slows down development velocity.
Whereas most technology procurement today is centralized, tech‐
nology procurement in microservices is distinctly decentralized. As
discussed in Chapter 2, each team should have enough autonomy to
select its own stack. That freedom comes with responsibility to run
and maintain it, so teams are incentivized to make wise decisions.
Experimenting with some new version 0.02 framework might sound
like fun until it’s your turn to fix a problem at 3 AM on a Sunday
morning. In traditional enterprises, the people who select technol‐
ogy aren’t the ones who actually must support it. They have no per‐
sonal incentive to make conservative decisions.
APIs
Each microservice team exposes one or more APIs out to the world,
so it’s important that those APIs are well designed. However, this
chapter focuses on inner architecture, so we’re going to save the dis‐
cussion on API gateways and API load balancers for Chapter 4, in
which we discuss the outer architecture.
APIs are often HTTP REST, but it is not a requirement. It just hap‐
pens to be a de facto standard because it’s the lowest common
denominator and it’s easy to read.
Level 0: RPC
This is how most begin using REST. HTTP is simply used as a trans‐
port mechanism to shuttle data between two methods—one local,
one remote. This is most often used for monolithic applications.
Let’s take inventory reservation as an example to illustrate each level
of maturity.
To query for inventory, you’d make an HTTP POST to the mono‐
lithic application. In this example, let’s assume that it’s available
behind /App. The application itself is then responsible for parsing
the XML, identifying that the requester is trying to find inventory.
Already, this is problematic because you need a lot of business logic
to determine where to forward the request within the application. In
this example, queryInventory has to be mapped back to a function
that will return inventory levels:
HTTP POST to /App
<queryInventory productId="product12345" />
APIs | 43
This would be the response you get back:
200 OK
<inventory level="84" />
To reserve inventory, you’d do something like this:
HTTP POST to /App
<reserveInventory>
<inventory productId="product12345" />
</reserveInventory>
Notice how the only HTTP verb used is POST and you’re only inter‐
acting with the application. There’s no notion of an individual ser‐
vice (/Inventory) or a service plus function (/Inventory/
reserveInventory).
Level 1: resources
Rather than interact with an entire application, level 1 calls for inter‐
acting with specific resources (/Inventory) and objects within those
resources (product12345). The resources map back neatly to micro‐
services.
Here’s how you would query for inventory with level 1:
HTTP POST to /Inventory/product12345
<queryInventory />
APIs | 45
Level 3: HATEOAS (Hypertext As The Engine Of Application State)
Level 3 is the highest form of maturity when dealing with REST
interfaces. Level 3 makes full use of HTTP verbs, identifies objects
by URI, and offers guidance on how to programmatically interact
with those objects. The APIs become self-documenting, allowing the
caller of the API to very easily interact with the API and not know
very much about it. This form of self-documentation allows the
server to change URIs without breaking clients.
From the YAML definition shown in Figure 3-2, you can generate
server and client-side stubs.
APIs | 47
Figure 3-2. Swagger server stubs
Versioning
As we discussed in Chapter 2, a defining characteristic of microser‐
vices is supporting multiple versions of a microservice in the same
environment at the same time. A microservice can have one or up to
potentially dozens of versions deployed, with clients requesting the
major and sometimes the minor version of an instance of the micro‐
service they’d like to retrieve. This introduces complexities.
The first complexity is in development itself. You need to support
multiple major and minor code branches, with each of those
branches potentially having the standard branches you would expect
when writing software. This requires full use of a feature-rich source
control management system (SCM). Developers must be proficient
at switching between versions and branches within the version.
Even though there’s more complexity, microservices makes this
somewhat easier because the codebase for each microservice is so
small and there’s a lot less coordination within a team because team
sizes are so small.
When you’ve built your microservice, you then need to deploy it to
an environment. Your deployment mechanism should be aware of
the multiple versions of the code you’re running and be able to
quickly pull out a version if it’s not working well. Public cloud ven‐
Alternate Formats
To this point, we’ve assumed that the underlying protocol is HTTP
and the format is XML or JSON. The combination of HTTP and
XML or JSON works well because it’s human-readable, easily under‐
stood, and can be used natively by established products like proxies
and API gateways.
In theory, you should rarely if ever make synchronous HTTP calls
between microservices, especially if the client is waiting on the
response. In practice, however, it’s all too common.
For the absolute best performance, you can use binary-level trans‐
port systems. Examples include Apache Thrift, Apache Avro, and
Google Protocol Buffers.
For example, Protocol Buffers from Google are 3 to 10 times smaller
and 20 to 100 times faster than XML. Rather than human readabil‐
ity, these implementations are optimized for performance.
APIs | 49
Containers
Containers are an important part of most microservices deploy‐
ments. Containers are not required for microservices but they’ve
both come of age at about the same time and they’re extremely
complementary.
This section will be about containers for a single microservice. In
the next section, in which we cover outer complexity, we’ll discuss
how you deploy and orchestrate containers, which is perhaps their
biggest value.
The value for each microservice team is that they can neatly package
up their microservices into one or more containers. They can then
promote those containers through environments as atomic, immut‐
able, units of code/configuration/runtime/system libraries/operating
system/start-and-stop hooks. A container deployed locally will run
the exact same way in a production environment.
In addition to application packaging, containers are also extremely
lightweight, often just being a few hundred megabytes in size versus
virtual machine (VM) images, which often are multiple gigabytes.
When a container is instantiated, a thin writable layer is created over
the top of the base container image. Because of their small size and
the innovative approach to instantiation, a new container can be
provisioned and running in a few milliseconds as opposed to the
many minutes it takes to instantiate a VM. This makes it easier for
developers to run microservices locally and for individual microser‐
vices to rapidly scale up and down in near real-time response to
traffic. Many containers live for just a few hours. Google launches
two billion containers a week, with more than 3,000 started per sec‐
ond, not including long-running containers. Many containers live
for just a few seconds. There’s such little overhead to provision and
later kill a container.
In the case of Docker, you build containers declaratively by using
Dockerfiles. Dockerfiles are are simple YAML (YAML Ain’t Markup
Language) scripts that define the base Docker image, along with any
commands required to install/configure/run your software and its
dependencies. Here’s a very simple example:
FROM centos:centos6
EXPOSE 8080
CMD ["node", "/Inventory/index.js"]
Lightweight Runtimes
Remember that each microservice is fairly small and uncomplicated.
The runtime required to run a 5,000 line application is very different
than would be required for a 10,000,000 line application. The large
application has 2,000 times more code, which could be using hun‐
dreds of advanced features like distributed database transactions.
Because of this reduced complexity, you can easily use smaller,
lighter, faster runtimes. Many of these runtimes are a few megabytes
in size and can be started in a few milliseconds.
Circuit Breakers
Calls from one microservice to another should always be routed
through a circuit breaker such as Hystrix from Netflix. Circuit break‐
ers are a form of bulkheading in that they isolate failures.
If Microservice A synchronously calls Microservice B without going
through a circuit breaker, and Microservice B fails, Microservice A
is likely to fail as well. Failure is likely because Microservice A’s
Lightweight Runtimes | 51
request-handling threads end up getting stuck waiting on a response
from Microservice B. This is easily solved through the use of a cir‐
cuit breaker.
A circuit breaker uses active, passive, or active plus passive monitor‐
ing to keep tabs on the health of the microservice you’re calling.
Active monitoring can probe the health of a remote microservice on
a scheduled basis, whereas passive monitoring can monitor how
requests to a remote microservice are performing. If a microservice
you’re calling is having trouble, the circuit breaker will stop making
calls to it. Calling an endpoint that is having trouble only exacer‐
bates its problems and ties up valuable request-handling threads.
To further protect callers from downstream issues, circuit breakers
often have their own threadpool. The request-handling thread
makes a request to connect to the remote microservice. Upon
approval, the circuit breaker itself, using its own thread from its own
pool, makes the call. If the call is unsuccessful, the circuit breaker
thread ends up being blocked and the request-handling thread is
able to gracefully fail. Figure 3-3 presents an overview of how a cir‐
cuit breaker functions.
Software-Based Infrastructure
Operating in a cloud is a requirement for microservices because it
allows each team to consume its own infrastructure, platform, and
software, all as a service. Remember, each microservice team needs
to own its entire stack and not be dependent on any other teams.
Before the cloud, centralized IT teams were required to support
each layer—compute, network, storage, and so on. Due to Conway’s
Law, this naturally led to large monolithic applications. What used
to take months of work by a specialized team can now be performed
by a single API call with less specialized individuals on a microser‐
vice team.
Each team should treat their infrastructure as essentially “disposa‐
ble.” There’s an old “Cattle versus Pets” meme coined by Randy Bias
that’s illustrative of this principle. If you have 1,000 cattle in a herd
and one of them breaks its leg, you euthanize it. It doesn’t have a
Polyglot | 53
name, it doesn’t sleep in your bed, your kids don’t wish it a happy
birthday. It’s a cow that’s indistinguishable from its 999 peers in the
same herd. A pet, on the other hand, is different. People will run up
massive veterinarian bills to fix a pet’s broken leg. It’s a pet—it has a
name, it spends time with your family, your kids celebrate its birth‐
day. This isn’t a perfect analogy, because cattle are worth thousands
of dollars, but the larger point Bias is trying to make about the cloud
is spot-on—stop treating your servers like pets. Containers deployed
to the cloud have IPs and ports that are not known, and when they
have problems, they are killed. They lead short anonymous lives.
What matters is the aggregate throughput—not the fate of any single
instance.
As part of achieving disposable infrastructure, HTTP session state
(login status, cart, pages visited, etc.) should be persisted to a third-
party system, like a cache grid. None of it should be persisted to a
container, because a container might live for only for a few seconds.
Remember that each microservice must exclusively own its data.
Multiple microservices can share the same store, but each must have
its own partition. One microservice cannot access the state of
another microservice.
A microservice’s configuration could be packed into the container or
it can be externalized and pulled by the microservice as required. It’s
best to place the configuration inside the container so that the con‐
tainer itself runs exactly the same regardless of its environment. It’s
difficult to ensure repeatability if the configuration can be modified
at runtime.
Sometimes microservices have singletons. These singletons can be
used for distributing locks, synchronously processing data (e.g.,
orders), generating sequential numbers, and so on. In the days of
static infrastructure, this was easy—you’d have a singleton that was
behind a static IP address that never changed. But in the cloud,
where containers might live for only for seconds, it’s impossible to
have named long-lived singletons that are always available. Instead,
it’s best to employ some form of leader election, in which singletons
are named at runtime.
55
widely established. After all, an individual microservice is just a
small application.
Software-Based Infrastructure
Although each microservice team can choose its own programming
language, runtime, datastore, and other upper-stack elements, all
teams should be constrained to using the same cloud provider. The
major cloud platforms are all “good enough” these days, both in
terms of technical and commercial features.
The first major benefit is that multiple teams can access the same
shared resources, like messaging, API gateways, and service discov‐
ery. Even though each team has the ability to make some choices
independently, you generally want to standardize on some things
across teams; for example, the messaging system. You wouldn’t want
100 different teams each using its own messaging stack—it just
wouldn’t work. Instead, a centralized team must choose one imple‐
mentation and have all of the microservices teams use it. It doesn’t
make sense to fragment at that level and it’s difficult to implement.
An added advantage of everyone using the same implementations is
that latency tends to be near zero.
By standardizing on a cloud vendor, your organization has the abil‐
ity to build competency with one of them. Public clouds are not
standardized and specialized knowledge is required to fully use each
cloud. Individuals can build competency, which is applicable regard‐
less of the team to which they’re assigned. Teams can also publish
blueprints and best practices that others can use to get started.
Working across clouds adds unnecessary complexity and you should
avoid doing it.
Container Orchestration
Containers are used by individual teams to build and deploy their
own microservices, as part of inner architecture. Container orches‐
tration is the outer architecture of microservices.
Simply put, container orchestration is the system that runs individ‐
ual containers on physical or virtual hosts. Managing a handful of
containers from the command line is fairly straightforward. You
SSH into the server, install Docker, run your container image, and
Container Orchestration | 57
lized. If utilization of a host reaches near 100 percent, containers can
be killed and restarted on another, less-loaded host.
Container orchestration, like the cloud, is a system that every team
should be forced to use. These systems are extremely difficult to set
up, deploy, and manage. After it is set up, the marginal cost to add a
new microservice is basically zero. Each microservice team is able to
easily use the system.
Let’s explore some of the many roles that container orchestration
systems can play.
Releasing Code
Each team must continually release new versions of its code. Each
team is responsible for building and deploying its own containers,
but they do so using the container orchestration system. Every team
should release code using the same process. The artifacts should be
containers that, like microservices, do only one thing. For example,
your application should be in one container and your datastore
should be in another. Container orchestration systems are all built
around the assumption of a container running just one thing.
To begin, you need to build a container image, inclusive of code/
configuration/runtime/system libraries/operating system/start-and-
stop hooks. As we previously discussed, this is best done through a
Dockerfile YAML that is checked in to source control and managed
like source code. The Dockerfile should be tested on a regular basis.
It should be upgraded. The start/stop hook scripts should be tested,
too.
After you’ve built your image, you need to define success/failure cri‐
teria. For example, what automated tests can you run to verify that
the deployment was successful? It’s best to throughly test out every
API, both through the API and through any calling clients. What
constitutes a failure? If an inconsequential function isn’t working,
should the entire release be pulled?
Then, you need to define your rollout strategy. Are you replacing an
existing implementation of a microservice that’s backward compati‐
ble with the existing version? Or, are you rolling out a new major
version that is not backward compatible? Should the deployment
proceed in one operation (often called “blue/green”), or should it be
phased in gradually, at say 10 percent per hour (often called “can‐
Service Registry
When the container orchestration system places a container on a
host, clients need to be able to call it. But there are a few complicat‐
ing factors:
Container Orchestration | 59
be more rich in how they query for an endpoint. The query could be
a formal JSON document stating version and other quality-of-
service preferences, depending on the sophistication of your service
registry.
The major drawback of this approach is that the client must “learn”
how to query each microservice, which is a form of coupling. It’s not
transparent. Each microservice will have its own semantics about
how it needs to be queried because each microservice implementa‐
tion will differ. Another issue is that the client will need to requery
for an endpoint if the one it’s communicating with directly fails.
Although the client-side approach can be helpful, the server-side
method is often preferable due to its simplicity and extensive use in
the world today. This approach is to basically use a load balancer.
When the container orchestration places a container, it registers the
endpoint with the load balancer. The client can make some requests
about the endpoint by specifying HTTP headers or similar.
Unlike client-side load balancing, the client doesn’t need to know
how to query for an endpoint. The load balancer just picks the best
endpoint. It’s very simple.
Load Balancing
If you use a server-side service registry, you’ll need a load balancer.
Every time a container is placed, the load balancer needs to be upda‐
ted with the IP, port and other metadata of the newly-created
endpoint.
There are two levels of load balancing within a container orchestra‐
tion system: local and remote.
Local load balancing is load balancing within a single host. A host
can be a physical server or it can be virtualized. A host runs one or
more containers. Your container orchestration system might be
capable of deploying instances of microservices that regularly com‐
municate to the same physical host. Figure 4-1 presents an overview.
Networking
Placed on a physical host, the container needs to join a network.
One of the benefits of the modern cloud is that everything is just
software, including networks. Creating a network is now as simple
as invoking it, as shown here:
$ docker network create pricing_microservice_network
With this new “pricing_microservice_network” network created,
you can run a container and hook up its network to the container:
Container Orchestration | 61
$ docker run -itd corp/pricing_microservice
--network=pricing_microservice_network
--name Pricing Microservice
Of course, container orchestration does this all at scale, which is part
of the value. Your networking can become quite advanced depend‐
ing on the container orchestration you use. What matters is that you
define separate, isolated networks for each tier of each microservice,
as demonstrated in Figure 4-2.
Figure 4-2. Software-based overlay networks are required for full isola‐
tion and proper bulkheading
Autoscaling
Commerce is unique in the spikiness of traffic. Figure 4-3 shows the
number of page views per second over the course of the month of
November for a leading US retailer:
Container Orchestration | 63
Containers help because they can be provisioned in just a few milli‐
seconds. The hosts on which the containers run are preprovisioned
already. The container orchestration system just needs to instantiate
some containers.
Note that autoscaling needs to be version-aware. For example, ver‐
sions 2.23 and 3.1 of your pricing microservice need to be individu‐
ally autoscaled.
Storage
Like networking, storage is all software-defined, as well.
Containers themselves are mostly immutable. You shouldn’t write
any files to the local container. Anything persistent should be writ‐
ten to a remote volume that is redundant, highly available, backed
up, and so on. Those remote volumes are often cloud-based storage
services.
Defining volumes for each microservice and then attaching the right
volumes to the right containers at the right place is a tricky problem.
Security
Network-level security is absolutely necessary. But you need an
additional layer of security on top of that.
There are three levels: identification, authentication, and authoriza‐
tion. Identification forces every user to identify itself. Users can be
humans, user interfaces, API gateways, or other microservices. Iden‐
tification is often through a user name or public key. After a user has
identified itself, the user must then be authenticated. Authentication
verifies that the user is who it claims it is. Authentication often
occurs through a password or a private key. After it has been identi‐
fied and authenticated, a user must have authorization to perform
an action.
Every caller of a microservice must be properly identified, authenti‐
cated, and authorized, even “within” the network. One microservice
can be compromised. You don’t want someone launching an attack
from the compromised microservice.
The client makes the call to the API gateway and the API gateway
makes concurrent requests to each of the microservices required to
build a single response. The client gets back one tailored representa‐
tion of the data. API gateways are often called “Backends for your
frontend.”
When you call APIs, you need to query only for what you want. A
product record might have 100 properties. Some of those properties
are only relevant to the warehouse. Some are only relevant to physi‐
cal stores. Remember, microservices are meant to be omnichannel.
When you want to display a product description on an Apple
Watch, you don’t want the client to retrieve all 100 properties. You
don’t even want the API gateway to retrieve those 100 properties
from the Product microservice because of the performance hit.
Instead, each layer should be making API calls (client → API gate‐
way, API gateway → each microservice) that specify which proper‐
ties to return. This too creates coupling because the layer above now
needs to know more details about your service. But it’s probably
worth it.
API Gateway | 65
The issue with API gateways is that they become tightly coupled
monoliths because they need to know how to interact with every cli‐
ent (dozens) and every microservice (dozens, hundreds or even
thousands). The very problem you sought to remedy with microser‐
vices can reappear if you’re not careful.
Eventing
We’ve mostly discussed API calls into microservices. Clients, API
gateways, and other microservices might synchronously call into a
microservice and ask for the current inventory level for a product,
or for a customer’s order history, for example.
But behind the synchronous API calls, there’s an entire ecosystem of
data that’s being passed around asynchronously. Every time a cus‐
tomer’s order is updated in the Order microservice, a copy should
go out as an event. Refunds should be thrown up as events. Eventing
is far better than synchronous API calls because it can buffer mes‐
sages until the microservice is able to process them. It prevents out‐
ages by reducing tight coupling.
In addition to actual data belonging to microservices, system events
are also represented as microservices. Log messages are streamed
out as events—the container orchestration system should send out
an event every time a container is launched; every time a health‐
check fails, an event should go out. Everything is an event in a
microservices ecosystem.
Summary
Microservices is revolutionizing how commerce platforms are built
by allowing dozens, hundreds, or even thousands of teams to seam‐
lessly work in parallel. New features can be released in hours rather
than months.
As with any technology, there are drawbacks. Microservices does
add complexity, specifically outer complexity. The enabling technol‐
ogy surrounding microservices is rapidly maturing and will become
easier over time.
Overall, microservices is probably worth it if your application is suf‐
ficiently complex and you have the organizational maturity. Give it a
try. It’s a new world out there.
Summary | 67
About the Author
Kelly Goetsch is chief product officer at commercetools, where he
oversees product management, development, and delivery. He came
to commercetools from Oracle, where he led product management
for its microservices initiatives. Kelly held senior-level business and
go-to-market responsibilities for key Oracle cloud products repre‐
senting nine-plus figures of revenue for Oracle.
Prior to Oracle, he was a senior architect at ATG (acquired by Ora‐
cle), where he was instrumental to 31 large-scale ATG implementa‐
tions. In his last years at ATG, he oversaw all of Walmart’s
implementations of ATG around the world. He holds a bachelor’s
degree in entrepreneurship and a master’s degree in management
information systems, both from the University of Illinois at Chicago.
He holds three patents, including one key to distributed computing.
Kelly has expertise in commerce, microservices, and distributed
computing, and speaks and publishes extensively on these topics. He
is also the author of the book on the intersection of commerce and
cloud, eCommerce in the Cloud: Bringing Elasticity to eCommerce
(O’Reilly, 2014).