Microservices architecture
Copyright 2014-19 Graham Berrisford. One of more than 300 papers
at http://avancier.website. Last updated 31/01/2021 12:34
THE MAIN POINTS OF THIS ARTICLE HAVE BEEN MOVED INTO A
NEWER ARTICLE ON LINKEDIN
https://www.linkedin.com/pulse/monolithic-apps-v-microservices-graham-berrisford
WHAT
REMAINS HERE IS SUPPPLEMTARY TO THAT ARTICLE
This paper was initially written to support the solution architect training courses advertised on our web site.
It has been extended to answer questions posted in Linkedin discussions and is now downloaded more than 10,000 times a year.
It draws from discussions with software architects about difficulties they are having with microservices.
And from discussions with EA teams about the need govern “agile” enterprise application development.
To quote from comments and discussion in Linkedin
·
“At last, a post on architecture with some real content.”
·
“This is worth reading again and again and sticking in the
diary to read again.”
·
“Watching a presentation on Microservice Architecture I was
astounded by the lack of perception [of the points in this paper].
·
“Your analysis of loose coupling is a poetry that everybody
getting into system integration should read several times until it is really
understood.”
Contents
A
brief aside on terminology torture
Microservices
as server-side application components.
Hierarchically-layered
client-server architecture
Dividing
the server-side application
Issues
raised by microservices
Particular
conclusions and remarks
General
conclusions and remarks
This article was written to:
· make sense of the term “microservice”
· steer people away from badly-designed, hard-to-maintain and poorly-performing microservices
· encourage enterprise and solution architects to take responsibility for governing software architects.
An architect recently complained his CIO uses the term "microservices" as meaninglessly as "SOA” has long been used.
On this topic (like so much in IT) some nonsense is talked.
And the use of a design pattern in marketing by technology vendors may be read with scepticism.
What's new? The trend is towards more distribution and flexible deployment of code.
Application layer modules can be deployed in "containers", shifted between servers and managed separately.
Yes, but why? And is migrating applications to the cloud consolidating inefficient software design patterns?
Does size matter? Small systems are usually easier to build and maintain than large ones.
But naturally, given one system to design, reducing the size of its modules increases the frequency and complexity of messaging between modules.
Given those modules must be joined up to make one application, you still have the same requirements to meet, the same use cases/transactions to support, same business rules to code, and the same persistent data to maintain.
All is trade offs.
THE MAIN POINTS OF THIS ARTICLE HAVE BEEN MOVED INTO A
NEWER ARTICLE ON LINKEDIN
https://www.linkedin.com/pulse/monolithic-apps-v-microservices-graham-berrisford/?published=t
Five motivations discussed in the article
· To maintain data stores already distributed and separately managed
· To separate what must be coded using different technologies
· To enable scalability or very high throughput
· To migrate a large legacy application for operation in "the cloud"
· To facilitate agile development
Four trade offs discussed in the article
· Agile development trade offs
· Sociological trade offs
· Modularity trade offs
· Data management trade offs (the 4Ds)
IT IS RECOMMENDED YOU READ THE LINKEDIN ARTICLE FIRST
THE REMAINDER OF THIS ARTICLE ARE WHAT REMAINS OF THE
SOURCE FROM WHICH THE LINKEDIN ARTICLE WAS DEVELOPED
In short, microservices is a design pattern for the modular design of enterprise applications.
Modular design before
microservices
Abstract away from the technologies and enterprise application design is a story of modular design.
In the 1960s, Larry Constantine promoted strong cohesion within a module and low coupling between modules.
By the 1970s, several modular design principles were widely recognised.
Design
principles |
Meaning
that |
Cohesion |
Modules encapsulate cohesive data structures and
algorithms |
Loose
coupling |
Modules are encapsulated by and communicate via
interfaces |
Composability |
Modules can be invoked and orchestrated by higher
processes |
Reusability |
Modules are designed for use by different clients |
Maintainability |
Modules are maintained in several versions to
enable incremental change |
Since 1970s, there have been many modular design fashions and techniques.
Read SOA and the Bezos mandate for notes on:
· RPC: Distributed programming
· DO: Distributed Objects
· CBD: Component-Based Design
· SOA: Service-Oriented Architecture
· REST: Representational State Transfer
· SOAP versus REST
· The Bezos mandate
Microservices
Briefly, a more entity-oriented microservice is much as Fowler defined in 2014:
· a micro application, encapsulated behind an API
· a discretely deployable subdivision of what could be a monolithic application (a macroservice?)
· it encapsulates one partition of what could be a monolithic data structure, and so “decentralises data management”.
Is everything that
realises an API a microservice?
No. Every microservice should have an API, but not every API encapsulates a microservice
To use the term for every module
behind an API is to make the term meaningless and valueless.
A small application is just a small application – nothing new or special about that.
A silo application is just a silo application – nothing new or special about that.
Small and silo applications are readily developed using agile principles – because they are naturally easier to write and to change.
And there is nothing new of special about wrapping up a small or silo application behind an API.
How do microservices
relate to Bounded Contexts in Domain-Driven Design?
A bounded context has its own ubiquitous language and its own architecture.
However, two bounded contexts can share concepts (e.g. customer, or product).
That describes the typical enterprise application portfolio since the 1970s.
The EA nightmare has been silo apps (separately developed and maintained) that store overlapping data structures.
As I see them, microservices are smaller than bounded contexts; they further decentralise data management.
They apply Conway’s law (in reverse) to a large bounded context.
What makes a microservice "micro" is division of one data structure into agile development-sized chunks
A microservice maintains a subset (perhaps as small as an aggregate entity) of one coherent and cohesive persistent data structure.
If there is no dependence between microservices, then they are discrete silo applications.
Should microservices
should be isolated and autonomous?
This ranges from questionable to impossible where data
integrity is needed.
One writer says a microservice
is "a self-contained and independent unit with a well-defined scope and
responsibility".
But this definition can fit
everything from a single Java class to an entire CRM system.
Moreover, you could define a good API
the same way.
And the notion that microservices are
independent of each other is misleading.
Will dividing a
monolithic application into microservices affect the services offered to
clients?
It can do if microservices are decoupled too far.
Are microservices are
“gatekeepers” to data?
Yes in that all updates should be applied by the microservice.
However, the data can be accessed by other software for analysis and reporting.
In the babble that enterprise and software architecture discussion has become, terms are used loosely and often abused.
Some use the terms system, component, process and service interchangeably.
Some use them with different (and sometimes contradictory) meanings.
|
In
EA standards from The Open Group |
In Fowler’s writings for programmers |
Component |
A structural building block that offers multiple services (a service portfolio) to its
clients |
A software unit (component) that is independently replaceable and upgradeable. |
Process |
A behavior composed of steps in a sequence that leads to a result. |
An instance of a component
executed on a computer. It contains executable code and current state
data. (Some OS allow it to contain multiple concurrent
threads of execution.) |
Service |
The external or declarative view of behaviour that encapsulates one or more processes. |
An out-of-process component, typically reached by a web service request or
RPC. |
Remember Martin Fowler’s process and service correspond to application components in TOGAF and ArchiMate.
And TOGAF’s Function is a logical component that groups behaviors by some cohesion criterion other than in sequence (which is a process).
It is commonly said that “enterprise architecture views the enterprise as a system, or system of systems”.
But there are profound misunderstandings of what this means.
To call every problem, situation or business “a system” is unhelpful.
It is important to distinguish:
· a social network in which people communicate
· a social system in which people realise role and rules.
An enterprise is one social network that realises many social and technological systems.
Those systems may overlap, duplicate or even be in conflict.
Enterprise architecture is about applying change control to large-scale, generational, system change.
In short, beware the terminology clashes that are the curse of every writer in this field.
Using EA terminology, a microservices architecture would be better called a microcomponent architecture.
The services offered by the wider monolithic application should remain unaffected by division into microservices, but they might be affected.
It is important to recognise design principles and patterns are not mandatory goals; they are only means to some ends.
Since the 1960s, software designers have debated now best to scope a module, separate modules and integrate modules.
Always, one has to consider what trade offs have to be made between requirements.
For example, there are trade offs
between flexibility and simplicity, scalability and integrity.
Of course, it is quicker to design and build a small subsystem than a large system.
But the smaller and simpler each sub system, the less useful it is, and the more complex the integration between subsystems.
This table lists my universal modularisation trade offs.
Agile
developers’ dream |
Enterprise
architects’ nightmare |
Smaller, simpler modules |
Larger, more complex module dependency &
integration |
Module isolation/autonomy |
Duplication between modules and/or difficulties
integrating them |
Data store isolation |
Data disintegrity and difficulties
analysing/reporting data |
Loose-coupling is often promoted as though it is always a good thing, but this is not true.
Designing systems or components to be “isolated” and/or “autonomous” can lead to disintegration of data and processes.
How does
decentralisation affect EA?
EA was a response to silo system proliferation.
Themes in EA literature include data and process quality, data and process sharing, data and process integration.
EAs are wary of excessive decoupling of software components across a network and/or via middleware.
Since it can create needless disintegrities and complexities, not to mention needless costs and performance issues.
Excessive decoupling can hinder how customers or employees perform business processes.
A microservices architecture divides an application into modules or components - confusingly called micro services.
It might be seen as an application of modular design, OO design, CBD or SOA principles.
Microservices might be distributed and integrated over a network, but this is not essential.
Martin Fowler (2014) defined microservices in terms of nine characteristics, most of which amount to principles.
The first six about are how best to scope a module, separate modules and integrate modules.
· Componentization (discussed in this
paper)
· Decentralized data management (discussed in this paper)
· Decentralized governance (click on the link to read more)
· Organisation around “Business Capabilities” (click on the link to read more)
· Smart endpoints and dumb pipes (click on the link to read more)
· Design for failure and "You build, you run it" (click on the link to read more)
Fowler’s last
three principles are drawn from the wider agile development movement, not
discussed here.
· Products not Projects
· Infrastructure Automation
· Evolutionary Design
c1980, an EA vision was a central database, supporting one application that served all the enterprise’s business functions.
That vision became unachievable as enterprises deployed ever more applications, which were increasingly distributed.
The vision evolved: EA divided the enterprise into “segments”; and the centralised data vision was recast as “single-version-of –the-truth”.
EA now involves “master data management” within each segment of the enterprise, and sometimes across segments.
EA expects each enterprise segment to be supported and enabled by many applications; some in-prem and some in-cloud.
Much as an enterprise may be divided into segments, an enterprise application may be divided into application components.
A typical client-server enterprise application processes successive transactions and is divided into three layers.
Microservices divide the server-side of what could be one large application into components that are supposedly autonomous.
By “decentralising data management” Fowler implies dividing what could be one persistent data structure into parts.
The idea is that each microservice encapsulates the data it needs; it does not directly access the data maintained by any other microservice.
Each is an application component that can perform one or many transactions or partial transactions.
Size matters!
You might see microservices as a scaling down of the “Bezos mandate” and/or Service-Oriented Architecture.
In other words, as extending the principle that components are distributed across a network and communicate via APIs.
However, microservices do not have to be distributed across a network.
Other modularisation strategies are no less service-oriented; and SOA does not say how to divide an application into modules.
You might better view microservices as a scaling up of OO design principles.
In other words, as a response to complaints that the granularity of “objects” is too small for architecture-level design.
An enterprise application enables or supports one or more
business roles and processes.
It encapsulates some business rules, business knowledge or business capability.
A few enterprise applications are purely algorithmic, or need very little stored data.
But the majority maintain, or at least need access to, a substantial business data store.
Dividing an application into three layers dates back to the 1970s - the days of COBOL and pre-relational databases.
Fowler says an enterprise application is typically composed of three layers, as shown below.
Client-side
user interface |
Typically HTML
pages and javascript running in a browser on the
user's machine |
Server-side
application |
Controllers: handle requests/commands from the
client Views: populate views/pages sent to the client. Models: retrieve and update data, applying
business rules |
Data
store |
A persistent data structure maintained using a
database management system. Typically a relational
DBMS, but there are many data store varieties. |
The layers form a client-server hierarchy, meaning each layer requests services from the layer below.
The
requirements for most enterprise applications may be seen from the client side
and the server side.
Client side
requirements
On the
client side, the users require a user interface, usually divided into views.
The views
are designed to what the user want to do in different use cases/sessions.
And within
each use/case session, the user enters queries and commands
the server-side application must respond to.
Server side
requirements
On the
server side, the back-end application must respond to the queries and commands.
In short
term memory, it must remember what the user is currently doing in one session.
In long
term memory, it must remember more persistent data – e.g. about products,
customers, and all the orders they have placed.
Despite the OOPers mantra that business rules belong in the middle layer, some regard the data layer as the best place for data-centric business rules.
Middleware may be used to connect layers, but Fowler says "smart end points, dumb pipes", meaning don't put business rules in middleware.
Fowler says the server-side application is typically a single executable.
One large application serves many requests/commands from clients.
Moreover, it is seen as a single software development unit.
So changes to requests/commands involve building and deploying a new version of the whole server-side application.
Microservices are subdivisions of the server-side application layer.
Each microservice receives a subset of the service requests that clients make of the whole application.
The ideal is that each of these application components can be built and tested in parallel.
Client-side
user interface |
Screens
/ Pages |
|
Server-side
application |
Customers,
Agents, Sales |
Reservations,
Rooms, Hotels |
How to divide one application into microservices? Fowler suggests:
· “Organisation around Business Capabilities” according to Domain-Driven Design (see footnote 1).
· “Decentralized data management”, which is discussed in this paper.
“At a first approximation, we can observe that microservices map to runtime processes, but that is only a first approximation.” Fowler
Client components (of any kind) do not have to be invoked a microservice via a message broker.
Microservices can be invoked using web service requests, the OData protocol, or whatever suits.
The closer microservices are adjoined in location and time, the more likely synchronous request-reply invocation will be appropriate.
Dividing the data store
Microservices imply not only dividing the application layer processing of what could be one cohesive data structure.
But also partitioning that data structure to match the division of the application layer into microservices.
“A microservice may consist of multiple processes that will always be developed and deployed together, such as an application process and a database.” Fowler
In other words, an application layer microservice cannot work without its data store.
To keep thing simple, let us minimise the need for a data abstraction layer between application and data layers.
In other words, the class diagram of the application layer and the data model of the data layer are variants of one logical domain model, and divided the same way.
Client-side
user interface |
Screens
/ Pages |
|
Server-side
application |
Customers,
Agents, Sales |
Reservations,
Rooms, Hotels |
Data
store |
Customers,
Agents, Sales |
Reservations,
Rooms, Hotels |
If you decouple the microservices’ data structures, and store them separately, then you need a cross-reference between them.
At least one entity type is duplicated in adjacent microservices' data stores, with a common primary key, but different, attributes and relationships.
That is an old and generally applicable idea; it can be applied however you choose to divide work between horizontal layers.
On division of work
between horizontal layers
Regardless of division into vertical partitions, an idea that Fowler seems to presume is that all business logic is in the application layer.
However, a data model captures business-specific terms, concepts, data types, data derivation and data integrity rules.
And coding data-centric rules next to the data is a rational design strategy.
So it is common to find data-centric business rules in the data layer.
Especially basic consistency/integrity rules such as: “no row can be inserted into the Sale table unless there is a corresponding row in the Customer table”.
And perhaps: no row can be removed from the Customer table if that Customer has Sales with future-dated Reservations.
Macro applications might be seen as the enemy here, but a macro application is always modularised one way or another.
As Fowler says, most business applications handle a series of discrete business transactions (event-triggered processes).
And they maintain a cohesive data resource – describable as data entities related to each other in a network structure.
The database of a microservice is a discrete division of what would be a wider database.
That is, the database of an application not divided into microservices (aka application components).
This table illustrates the mapping of:
· event-triggered business processes against
· entities in the data model of that wider database.
Entities |
Customer |
Sale |
Sale
item |
Product
type |
Events |
||||
Register
customer |
Create |
|
|
|
Place
order |
Read |
Create |
Create |
Update |
Complete
sale |
|
Update |
|
|
Launch
product |
|
|
Create |
|
Recall
product |
Read |
Read |
Update |
Update |
Event-oriented or
entity-oriented programming?
In short, each microservice may be coded either as:
· a set of event-triggered transaction scripts, or else as
· an aggregate entity in which the root entity manages all the transactions.
The concept of the aggregate entity probably implies the use of Fowler’s Rich Domain pattern and/or Evan’s Domain-Driven Design.
There seems no clear reasons to insist microservices should use those patterns.
In 2002, Martin Fowler observed that
Domain-Driven Design is difficult to learn and best reserved for complex
systems with “rich domain models”.
And in January 2017, Wikipedia said: “Microsoft recommends that [domain-driven design] be applied only to complex domains.” (where I guess rich implies substantial use of inheritance).
A transaction script is an event-triggered procedure.
At a lower level, transactions can share/reuse shorter subroutines, which may be entity or object-oriented.
At a higher level, a business transaction that crosses
between microservices is coded as a longer procedure (aka saga) that
coordinates micro-service-level transactions.
Fowler suggested transaction scripts are simpler for 90% of business applications.
So, further discussion below starts with the transaction script pattern, and moves on to the aggregate entity pattern.
You can modularise an application into one module for each event-triggered process to be completed.
That was the expectation in using structured analysis and design methods in the 1980s
Duplication is removed by factoring out common sub-routines (done well, this gives the leanest design).
Procedural microservice (handles 1 whole transaction, has access to the whole data structure)
In his book on enterprise application design patterns, Martin Fowler wrote thus.
“Most business applications can be thought of as a series of [client-server] transactions.
[Some are] as simple as displaying information in the database.
[Others] involve many steps of validations and calculations.
A Transaction Script organizes all this logic primarily as a single procedure, making calls directly to the database or through a thin database wrapper.
Each transaction will have its own Transaction Script, although common subtasks can be broken into subprocedures.” Fowler
A suggestion for agile development, divide the data model into aggregates, each with a kernel entity (as several examples discussed in our architects course)
Give each programmer the transactions that start in one aggregate - at the kernel/root entity or other entity in the aggregate.
Where few transactions span more than one aggregate, the programmers mostly work autonomously – their code and tests do not overlap.
Where many transactions span aggregates, dividing the code and data structure as per entity-oriented microservices (below) is problematic.
As I recall from decades ago, the common subtasks tend to be subroutines that access one entity, or a few entities.
You can draw a simple hierarchical dependency diagram to track inter-dependencies between Transaction Scripts and shared subroutines.
Perhaps start with Transaction scripts, and refactor into a Domain-Driven Design only when complexity of the domain demands it?
Fowler proposes the design of microservices should decentralise data management and governance.
This means dividing a wider data model into narrower data structures.
Fine-grained entities
You could modularise an application into one module for each normalised data entity.
The coordinated these modules to complete an event-triggered process by
· Choreography: entity modules call each other to complete the process
· Orchestration: the process calls the requisite entity modules
Or a mix of both the above, using the GRASP pattern.
However modules that encapsulate normalised data entities are too small to be deployed separately as microservices.
Partly because entity-level objects are so small that coordinating them in higher level processes is inefficient.
Partly because it leads to issues addressed by Fowler’s first rule of distributed objects “Don’t distribute your objects!”
Aggregate entities
A microservice usually encapsulates a data structure considerably larger than a normalised data entity.
It is reasonably here to employ the idea of “aggregate entities” that appears in Evans’ Domain-Driven Design
There are
recognised techniques for dividing an OO domain model or data model.
E.g. you may identify aggregate entities by applying cluster analysis (the north-west corner method) to the entity-event table above.
Having divided an application into application components thus, you must define each aggregate entity separately.
This involves defining the several transactions that access the data in that aggregate entity.
A more entity-oriented microservice handles a cluster of aggregate-entity level transactions.
It encapsulates a discrete group of data entities - an aggregate entity or more.
Ideally, the data structure is sufficiently wide that a microservice can complete many (most?) client-server requests on its own.
Ideally, the
data server and app server layers of the application are partitioned in the
same way.
Otherwise,
the data abstraction layer between them will be complex, and undermine the
hoped-for benefits of microservices.
What if one
business transaction needs to access the data encapsulated by more than one
microservice?
The business transaction can be completed by coordinating microservices – whether by orchestration or choreography.
Or else, you can duplicate some data between data stores, so
that each microservices has all
the data it needs.
This leads to the complexity of adding processes to align data stores after a transaction – asynchronously - as best they can.
A more procedural microservice handles a cluster of whole transactions that have access to the wider data structure.
Again: a suggestion for agile development, divide the data model into aggregates, each with a kernel entity.
Give each programmer the transactions that start in one aggregate - at the kernel/root entity or other entity in the aggregate.
Where few transactions span more than one aggregate, the programmers mostly work autonomously – their code and tests do not overlap.
Where many transactions span aggregates, dividing the code and data structure as per entity-oriented microservices (below) is problematic.
A more procedural microservice packages transactions as suggested above.
The package resembles a more object-oriented microservice in scope, but does not completely encapsulate the data.
It can be managed as a separately deployable application layer component, accepting that data management remains centralised.
Where transactions span more than one aggregate entity, design options include:
· allow more procedural microservices to compete for the same data (limits scalability).
· duplicate (by caching) data needed by several microservices (increases disintegrity and complexity).
Decoupling is an important tool of design; but it means many things, and can be overdone.
There are many ways to decouple application components: physical decoupling using network and/or messaging technologies is not logical decoupling.
There are many ways to integrate application components: exposing APIs over an IP network is only one way.
Read SOA and the Bezos mandate for discussion of component granularity, network use and middleware use.
For some, loose-coupling via messaging has become a mantra, but there are always trade offs, as this table shows.
Couple
via synchronous request/reply for |
Decouple via asynchronous messages/events for |
Speed Simplicity Consistency |
Scale (very high volume and/or throughput) Adaptability/Manageability (of smaller
distributed components) Availability (of smaller
units of work) and Partition
Tolerance |
Things to think about include:
· Physical decoupling makes logical coupling more complex.
· Naïve architecture guidance - can mandate decoupling, asynchronicity and scalability where not needed
· Response time – where one transaction requires microservices that communicate via network and/or messaging.
· Availability – where synchronous access is needed to partitioned/distributed data stores.
· Scope/complexity creep – where microservices are increasingly hooked together.
· Business data and process integrity – where BASE replaces ACID.
· Application maintenance – where multiple design patterns and technologies are used.
· Best practice use of CQRS/Event Sourcing.
We are talking about the server-side of an enterprise
application.
A remote client sends a service request to a point of entry on the server-side.
That service request is a business transaction, a command or query, definable
in a declarative service contract.
The service contract defines I/O data with reference to one or more persistent
data entities.
Suppose we have modularised a macro application into several more entity-oriented microservices.
Ideally, most business transactions don’t stray beyond one microservice.
But what about those business transactions that do involve two or more distributed microservices?
CAP theorem
The famous CAP theorem says you can’t have all three of Consistency (integrity), Availability and Partition (broken connection) tolerance.
It doesn’t mention a fourth thing you want from a design – Simplicity.
The disintegrity that results from dividing a cohesive data model tends to increase the complexity of a system.
Allowing data to become inconsistent, then restoring integrity by compensating transactions, is more complex both for programmers and for a business.
ACID versus BASE
This table contrasts two ways to implement a business transaction.
ACID
when you |
BASE when you |
Want every business
transaction to succeed Want to simplify
design and development Want consistent data |
Want to get a business transaction started, even
if it fails later Don’t mind the complexity of compensating
transactions Can live with some
inconsistency for a while (perhaps forever) |
ACID transactions for
consistency/integrity
Often, data integrity is important to business operations
A client wants a business transaction to succeed or fail completely before the server-side application replies.
A customer does not want to reserve a hotel room (using the sales agent microservice) that later turns out not be available (in the hotel administration microservice).
ACID means a transaction is atomic, consistent, isolated and durable.
Here, it means microservices are coordinated synchronously
on the server-side.
So, clients’ service requests are not affected by how the server-side
modularised.
Aside: For scalability, microservices can be replicated on parallel server-side nodes.
Sticky sessions or state replication enable a load balancer to choose which node responds.
To keep things simple, it is best if all the required data can be stored in one place.
Then, the database management system can be used to roll back a transaction when a business rule is violated.
It can also save some programming effort by maintaining referential integrity.
And support analysis of data for management information purposes.
If data is distributed, it may be necessary to hand-code the two-phase commit units a transaction manager could handle.
BASE for very high
throughput by partitioning
Naturally, it is easier and quicker to develop a module that has minimal interaction with others.
For agility, developers want their microservice to work on its own, and reply to a client immediately with minimal dependency microservices.
For scalability, they want to isolate modules physically as well, deploying them on differ server-side nodes.
BASE means an application is basically available, scalable and eventually consistent.
Here, it means that one microservice may reply to a client before that client’s desired business transaction is completed.
Other microservices are coordinated later, asynchronously, and compensating transactions are performed if need be.
The result is that clients become aware of, and are affected
by, modularisation into microservices.
Basically available
The idea is that a microservice can serve its clients well enough without immediate reference to other microservices.
A microservice can complete some business transactions immediately, and set other business transactions in motion.
The presumption is that a client will be happy to see a business transaction started, with no promise it will be completed.
E.g. a customer reserves a hotel room using the sales microservice, without the promise that the room will be available.
Scalable
Today, an ordinary relational database can handle many thousands of transactions per second.
Very high transaction volumes can be handled by beefing up the data server and using solid state drives.
To handle
extraordinary transaction volumes,
the data store can be partitioned in one of two ways.
·
Sharding: this means partitioning the data store into separate data
populations (perhaps by geography or region?).
·
Functional scaling:
this means partitioning the data structure by subject matter (e.g. customers
and products).
Microservices can enable functional scaling, but this is not a general motivation.
FAANG is an acronym for five large IT-centric businesses, namely Facebook, Apple, Amazon, Netflix and Alphabet’s Google.
To which you might add eBay and Spotify.
“Hard
cases make bad law” is
legal maxim.
In other
words, a general law is better drafted for average or common
circumstances.
Not many businesses are like the seven mentioned above.
Whatever those internet giants do to handle extreme business transactions volumes is not necessarily optimal for your business.
Eventual consistency
Doing business using inconsistent data is a business issue
before it is a technical issue.
Any
business transaction that spans two or more partitions needs special attention.
The BASE strategy is to divide a business transaction into smaller transactions, each performed by a different microservice.
And to allow cases were a whole business transaction cannot be completed in one go.
You must analyse possibility that data becomes inconsistent, the impact that has on business operations, and when and how to restore data integrity.
E.g. what to do if a customer reserves hotel room (using the sales agent microservice) that turns out not be available (in the hotel administration microservice)?
You have to analyse and design additional compensating transactions to correct or undo the unwanted effects of business transactions that start, but cannot be completed.
Microservices can be seen as a renewal of the Component-Based Design fashion in the 1990s.
CBD partitioned a cohesive data model into chunks, each an aggregate entity maintained by a “business component”.
How did developers maintain data integrity?
Some worked to the presumption that few transactions stray beyond the bounds of an aggregate entity.
Some (as in domain-driven design) gave the root entity in each aggregate responsibility for transaction management.
I suspect these developers may have ignored some data integrity tests that could be made.
Still, what to do about those business transactions that do require access to data in more than one aggregate / business component?
Maintaining all the aggregate entities in
one single data store
This facilitates
queries, data analysis and reports.
The optional update
patterns are
· Synchronous
ACID transactions maintain data integrity: Complete a two phase commit unit, integrate
components via RPC or middleware; OR use the DBMS transaction management to
maintain cross-component data integrity.
· Asynchronous
BASE pattern, allow data integrity temporarily: Reply before completing the transaction,
then integrate components via using RPC or middleware to complete it,
performing compensating transactions as need be.
Maintaining the aggregate entities in
distinct data stores
This hinders
queries, data analysis and reports.
The optional update
patterns are:
· Synchronous
ACID transaction patterns maintain data integrity: Complete a two phase
commit unit, integrate components via RPC or middleware; OR use a
distributed/federated transaction manager.
· Asynchronous
BASE pattern, allow data integrity temporarily: Reply before completing the transaction,
then integrate components via RPC or middleware to complete it, performing
compensating transactions as need be.
Some options couple components more obviously that others; but all options couple components in some way.
Publishing "decoupling" as a general principle can accidentally encourage excessive decoupling of software components across a network and/or via middleware.
EAs ought be concerned about creating needless disintegrities and complexities, not to mention needless costs and performance issues.
The issues, principles and tradeoffs are largely vendor and technology neutral.
If EAs can't directly govern Technical and Software Architects, then they need to be socialised with Solution Architects who can do this on their behalf.
Assisting agile development is a plus for any modular design strategy.
A clean division into business components/microservices can suit the division of work between small teams.
However, remember Berrisford’s universal trade offs.
Agile
developers’ dream |
Enterprise
architects’ nightmare |
Smaller, simpler modules |
Larger, more complex module dependency &
integration |
Module isolation/autonomy |
Duplication between modules and/or difficulties
integrating them |
Data store isolation |
Data disintegrity and
difficulties analysing/reporting data |
Be cautious about partitioning what could be one cohesive data structure.
Beware naïve principles, look at realistic requirements and assess trade offs.
· Flexibility? Beware this usually requires a more complex design.
· Scalability? Be realistic about the need, and assess the cost of allowing and restoring integrity.
· Decoupling? Beware that decoupling related components physically does not decouple them logically.
· Reuse? Beware that dividing an application does not usually create components readily reusable in other contexts.
· Keep it simple? Beware BASE can be considerably more complex than ACID.
· Agile development? Grouping transaction scripts into “more procedural microservices” is a rational way to divide work between individuals or teams.
In short, there is no silver bullet, you can’t have everything, there are always trade offs.
Read SOA as per the Bezos mandate for some SOA history.
Read Domain-Driven Design, Transaction Script and Smart UI for discussion of the simple v complex trade off.
Read Smart endpoints and dumb pipes for
discussion of the synchronous v asynchronous trade off.
Programmers are intelligent people, and know useful stuff.
Having a programming background helps people become good architects.
It gives them insights that are valuable at the architecture level.
Nevertheless, there are difficulties out there.
· Technology vendors encourage programmers to use technologies they don’t need.
· Industry analysts repeat what technology vendors tell them.
· Reading software guru books can encourage programmers to use patterns designed for problems they don’t have.
· Some programmers are under-educated in the very wide variety of ways to code software – and trade offs between them.
· Some programmers like extend their CV with the latest buzz words.
Advances in hardware can always be negated by questionable software engineering practices.
The ability of software to consume ever more electricity is endless given presumptions such as:
· Following latest design fashion without question.
· Copying what Google or Amazon do.
· All components should be loosely coupled.
· All inter-component messages must be sent via middleware.
· All components should be open to extension but closed to amendment.
· Integrity is best achieved by eventual consistency.
· New data store types are better than relational databases.
· Complex data abstraction layers are needed.
· Microservices are the answer.
The result is that programmers can easily generate solutions that are more complex than is justifiable.
Complexity can appear in excess code, data abstraction layers, message passing, compensating transactions and middleware dependency.
Vendors, industry analysts and commentators encourage programmers to decouple application components.
Concerns about this include:
· there are a dozen ways to be coupled or decoupled, so the meaning of the direction is unclear.
· physical decoupling is not logical decoupling.
· decoupling tends to increase complexity, slow down and disintegrate business processes.
· the optimal degree of coupling varies with the granularity and cohesiveness of the specific components at hand.
· following the direction has led to overuse of middleware and consumes excessive server-side resources.
As Craig Larman wrote: the problem is not high coupling per se, it is high coupling between components that are unstable in some way.
Rather than directing decoupling throughout, architects should be balancing trade offs and providing more nuanced guidance.
How to save the enterprise from the cost, risks and issues of following software design fashions?
EA needs solution architects to shape and steer programming efforts in an EA-friendly direction.
They have a role to play in abstracting and repeating lessons learned from experience.
Don’t be over eager to use the latest design pattern/method or technology.
Beware that ungoverned agile development can lead to duplication, disintegrity and complexity.
Keeping things simple, minimising unnecessary code and use of middleware, is a reasonable principle.
This can sometimes mean constraining programmers’ enthusiasm for “new” ways of doing things.
As the first slide in our enterprise and solution architect training says.
Think about the
business context. Don't forget the
numbers. There are many
ways to design and build something. You have to
balance trade offs: · between qualities (e.g. flexibility or
simplicity) · between what is best for the local system
and what is best for a global system. You are
responsible for: · knowing there are many possible answers · ensuring trade offs
are addressed and · recommending one or more options. |
In 2002, Martin Fowler observed that Domain-Driven
Design is difficult to learn and best reserved for complex systems with “rich
domain models”.
And in January 2017, Wikipedia said: “Microsoft recommends that [domain-driven design] be
applied only to complex domains.”
(Where I guess rich implies
substantial use of inheritance.)
Read Dividing
an application into microservices and Domain Driven Design, Transaction
Script and Smart UI
for practices related to microservices.
Domain-Driven
Design, CQRS and Event Sourcing are sometimes related to each other.
But Transaction Scripts can equally well publish Events (for others to consume) and log Events (for subsequent query and replay).
And you don’t
need Domain-Driven Design or CQRS to design microservices, separate database
update transactions from queries, publish Events, or use Event Sourcing.
Design by Contract (DBC) means a server will fall over if a client (any client) does not guarantee its preconditions.
Defensive Design means a server tests (or likely retests) its preconditions are met, and responds gracefully if not.
Distribution of microservices (or any software) between nodes tends to increases the need for Defensive Design.
You might see microservices as a scaling down of the “Bezos mandate” and/or Service-Oriented Architecture.
In other words, as extending the principle that components are distributed across a network and communicate via APIs.
However, microservices do not have to be distributed across a network.
Other modularisation strategies are no less service-oriented; and SOA does not say how to divide an application into modules.
(For more on this read SOA and the Bezos mandate).
Marc Bastien wrote
At last, a post on architecture with some real content.
Thanks Graham for elevating a bit the level of discussion about IT architecture above vendor sales pitches.
And for those that have a hard time understanding your post, that are lost in the terms, and find this subject too "esoteric", here's a friendly suggestion:
Don't rely too much on programmers that are good at doing the "real" thing.
Because most of them just don't have a clue about the impacts of what they are doing besides and above the code they deploy.
Not because they're not bright enough, but simply because they're busy doing their things.
Martin Cotter wrote:
This is worth reading again and again and sticking in the
diary to read again.
The comment by Marc about coders who are brilliant but busy rings especially
true.
Rob Dean wrote:
Watching a presentation on Microservice Architecture I was astounded by the lack of perception [of the points in this paper].
You cannot provide every quality attribute in any architecture.
Trade offs are the nature of engineering, regardless of the discipline.
Each service will contain some redundant code to enable it's isolation from the other services to allow for the horizontal scaling.
While removing one type of dependency you introduce others, such as state.
The only way to introduce reuse in these services is composing them from a configuration definition.
Also, while attempting to scale small units of functionality you introduce dependency scaling.
If you decouple Orders from Inventory as in the example, you will always need enough inventory services to support the orders.
The dependency remains intact even though the monolith gone.
Edin Nuhic wrote:
Graham, your writing on the subject is highly relevant.
TOGAF can often confuse people who misunderstand it.
However, you seem to be the one who analyzes things deeply and then tries to summarize what it all comes down to.
Your analysis of loose coupling is a poetry that everybody getting into system integration should read several times until it is really understood.
As you point out - there are two definitions of microservices
· the one from Martin Fowler that addresses the organisational and the deployment aspects in the first hand
· the other that addresses the performance.
This second advocates asynchronicity as a means of preventing idle slots waiting for a synchronous service to return.
[This bring something new to the table] and deserves a separate discussion.
It is strange that very serious people can mean so different things and call them the same name.
-1- What makes a service a micro?
It is micro compared with the macro application it is a component of.
-2- What is micro about “more procedural-microservices”?
They are similar in size to more entity-oriented microservices.
Both process a subset of service/transaction requests from the client layer.
But more procedural microservices don’t wholly encapsulate the data they access.
-3- How does a microservice differ from a usual SOA style service?
What is a usual SOA style service?
-4- How important is independence of deployment - the devops aspect?
That suggests a distinct application, not a subdivision of one.
Unless it means deliberately dismembering a business process.
-5- How important are performance gains through asynchronicity and granularity.
Speed – could make things worse.
Throughput – important in some (probably rare) cases
Availability – it depends what the business wants to be available.
All free-to-read materials at http://avancier.website are paid for
out of income from Avancier’s training courses and
methods licences.
If you find the web site helpful, please spread the word and
link to avancier.website in whichever social media you
use.