Maximizing Microservices Architecture Part 3: Standards

Wahome
12 min readJul 30, 2019

--

Architecting & designing micro-services

Teddy Roosevelt, the 26th President of the United States, in a private letter to Henry L. Sprague dated January 26, 1900 while still Governor of New York, in a bout of happiness after forcing New York’s Republican committee to pull support away from a corrupt financial adviser, remarked:

“I have always been fond of the West African proverb: Speak softly and carry a big stick; you will go far.”

As his political career and fame blossomed, rising from the Governor of New York to the Vice President, to then becoming President following the assassination of William McKinley, Roosevelt evolved this proverb into a political philosophy known as the big stick ideology, big stick diplomacy, or big stick policy. He described his style of foreign policy as:

“the exercise of intelligent forethought and of decisive action sufficiently far in advance of any likely crisis.”

You must be bewildered and probably wondering what Roosevelt and his ideologies has to do with microservices.

Keep calm and carry on, and please bear with me as I attempt to marry political ideology with software engineering; which is no mean feat whatsoever.

Principles to Standards

image from opensourceforu.com

In part 2 of the series, principles, an influential and enduring collection of empirical microservices architecture and design best practices which have incrementally grown out of experiences and encounters with the architectural style were discussed. In that post, a distinction between principles and standards as well as their relationship was made. Ideally standards arise from principles.

These principles should speak softly but carry big sticks.

Standards are the metaphorical big sticks that principles should carry. They mandate what ought to be done and how to go about doing it. Principles focus on the ‘why’.

In software development, standards are one element in a structured set of ideas that collectively define and guide an organization from values through to actions and results. They consist of certain terms, concepts, data formats, document styles and techniques arrived at through consensus across an organization. In essence, a standard is an agreed way of doing something that is derived from the collectively distilled wisdom and expertise of people.

At its core, software, both throughout various industries and as an industry in itself, relies on standardization. From the very foundation of standardized hardware specifications and interfaces, up through programming languages and interoperability, as well as the simplicity of using software for the purpose and use case that it was intended for, software development and use is heavily driven by standardization.

Adapting Teddy’s big stick diplomacy to software engineering, standards can be described as an exercise of intelligent forethought and of decisive action sufficiently far in advance of any likely crisis. They truly are a careful and measured application of good judgement backed by decisive action (what ought to be done and how to go about doing it), sufficiently far in advance of any potential crisis. Indeed:

“Good judgment comes from experience, and experience from bad judgment.”

Microservice Standards

Microservice architecture approaches a large, complex application as a suite of loosely coupled, independent and autonomous single-function component services. To solve the business problem at hand, the true value of software, these services work and communicate with each other through a standardized API; Standardized API Mechanism with a Published Contract is one of the key principles in microservices.

In the crucible of microservice standards, APIs take center stage because they’re the sinews that bring together a fleet of independently deployed services as one application. While it’s one thing to slice and dice a monolith into correctly sized, highly decomposed, independently deployable units, it’s an entirely different thing to have them cohesively working with each other to deliver value. Standards are the party piece to this intricate choreography.

disparately exposed service APIs

If no standardization takes place, as is the case in the example above, completely disparate services will be exposed; to both internal and external consumers. Each developed service may differ with respect to its behaviour, message structure and interaction patterns resulting in:

  • Longer development cycles
  • Limited or no code reuse
  • Difficult to consume services
  • Wasted development effort

Standardizing APIs is primarily motivated by the need to drive conformity to microservice principles with respect to:

  • Service Behaviours: expected characteristics.
  • Service Location: how to expose a service.
  • Interaction patterns: how to communicate.
  • Versioning: what and when to version.
  • Message structure: how to structure message data.
  • Contracts: how to define a contract.

1. Service Behaviours

While building microservices, certain key principles and best practices should be considered and applied to achieve desired characteristics of services. Some of the ideal service characteristics arising from these principles include:

a) Statelessness

Ideally, services should not maintain any state and each invocation of an operation should not be dependent on any previous invocations. Any required state should be accessible to all instances of a service so that sticky sessions are not necessary.

b) Bounded Context:

Each service should be responsible for a single bounded context around a business capability. Any required information or action that is outside of the context should be executed by making use of a published, standardized API.

direct data access anti-pattern

In the example above, if the order service requires some customer information that it does not hold in its context, it should only access it through a published service interface and not have direct access to the customer service data store.

c) Idempotency

idempotency and retries

Idempotence is the property of certain operations in mathematics and computer science that allows them to be applied multiple times without changing the result beyond the initial application.

“Idempotency is doing the same thing over and over again and expecting the same results.”

In idempotent services, if duplicate invocations of an operation were to occur, the service should give the same result or at the very least not perform a harmful operation. It is only really a requirement on services that change state or perform certain critical operations e.g. financial transactions. Data lookup operations are already idempotent by nature.

Idempotency can be easily achieved using two methods:

  • Returning the original response when a duplicate request is detected
  • Alternatively, rejecting and notifying a consumer when a duplicate request is detected

Both approaches are dependent on identifying duplicate requests through the use of the message trace IDs that should be passed along in the API message structure; see message structure section.

With idempotency, retrying failed messages is no longer dangerous and is a capability that goes a long way in enhancing system stability. However, on the flip side, there is need for a shared message store used by all instances of a deployed service to persist trace IDs.

2. Service Location

location transparency

The Location Transparency principle requires that services are not exposed directly to consumers and service consumers are never to be made aware or dependent on the exact address of the service.

point-to-point location transparency anti-pattern

Instead, some form of indirection should be used when locating or invoking services.

There are various ways of implementing location transparency to ensure that consumers are never directly coupled to service addresses. Use of a DNS pointed load balancer, or proxy, that receives a request and in turn forwards it to an available service node is one common example.

location transparency: indirection with a load balancer

Consistency in service endpoints naming is essential and should be defined as a convention. Failing to do so would result in the external facing APIs being highly inconsistent and looking unprofessional.

A naming convention should include:

  • the base URL
  • a version number
  • service/context/domain
  • operation specific paths, information or requirements
Example Convention:https://www.<base_url>/api/<service>/<version>/<operation_details>Usage:https://www.myorderservice.com/api/orders/v1/account/1/order

3. Interaction Patterns

Services, on their own, provide no meaningful value. To derive benefits, services need to be employed as elements of a larger process — a process that provides value to an organization.

Service interaction is considered a first-class citizen in various industry standards. The importance of interaction increases as monolithic systems are broken down into smaller services, becoming more service oriented, and as processes increasingly cross organizational boundaries.

Interaction patterns detail how two or more systems communicate with one another. There are various interaction patterns that can be implemented; each with its own strengths and shortcomings. While determining what patterns fits where and when, a pragmatic comparison must be undertaken. Such a comparison should be based on:

  • Process length: Is the pattern ideal for executing short or long running processes?
  • Adoptability: Can the pattern be easily implemented by services communicating internally or externally?
  • Scalability: How easy is it to horizontally and vertically scale services using the pattern?

The image below shows an analysis of different interaction patterns against the laid out criteria:

interaction patterns compared

Below is a decision tree that is quite useful in determining where and when to use each of the interaction patterns:

interaction patterns decision tree

4. Message Structure

Standardizing what is communicated over interfaces is equally as important as standardizing how to go about the communication and the API mechanism itself.

SOAP message structure

Designing a standard message structure is aimed at balancing ease of use for consumers with commitment of stability from a service provider; by eliminating uncertainty and ambiguity.

Certainly, the purposes of communicating over APIs can only be realized if messages between a service provider and consumer are easily and correctly generated and parsed. Besides, a consistent message structures is as much for the benefit of humans, when human intervention in systems in required, as it is for machines. Just imagine the chaos that would ensue if a service’s API was to respond in one format today and in another a day later.

Standardized messages also have a lot to do with the nuances and peculiarities of the world we live in, which incidentally is what we build systems for; internationalization and localization. Without any shred of standardization, amounts (money and currencies), and dates and times easily become a big headache.

There is quite a number of documented best practices and rules, by different organizations, on how to go about designing message structures that can be adopted on a standardized API. Most revolve around JSON, a lightweight data-interchange format, that has become the de facto format on the web.

Google’s JSON Style Guide, is an example of such guidelines and recommendations. Below are some of its key highlights:

i) Property names must use double quotes

ii) Property names must be camelCased

iii) Property names must all be singular except for arrays & collections

iv) Property values must be booleans, numbers, strings, objects, arrays, or null

v) Property values that are “null” should be removed from the message

vi) Property values that are ENUMS should be represented with strings

vii) Properties that are dates or dates with time should be represented in ISO8601 format and should be in UTC

If you have a simple date field, use the format: YYYY-MM-DD

If you have a date-time field, use the format: YYYY-MM-DDThh:mm:ssZ

The Z in the format indicates that the date and time is in UTC.

viii) Properties that represent money must use an amount data object that contains ISO 4217 currency and precision information and be in the minimum monetary unit.

An API message, whether a request or a response, should include tracing metadata (such as message IDs, group IDs, batch IDs and sequence numbers) and a timestamp enclosed in a structure with an appropriately named field; header is a preferred name, within the messages body as well as in transport/protocol headers if any.

Example in a JSON body:

{
"header": {
"messageId": "99a21a54-d694-4748-9758-7ddd1f204e39",
"timestamp": "2019–30–07T06:43:01.232Z"
}
}

Message trace IDs are important in achieving idempotency as a characteristic of a service, as well as in facilitating distributed tracing. It’s highly recommended and a good practice to bounce back, in the response, message trace IDs received in a request as it enhances cardinality and ease of tracing.

It’s worthwhile to mention that an API response, in both success or failure cases should communicate the status of the interaction unambiguously. As a consumer, when I get a response from an API, the first thing to do is check the status value. Is it a success status? Great, carry on as expected. Is it a failure status? I’ll need to handle it gracefully.

To communicate interaction status, well documented codes such as SUCCESS, REJECTED, FAILURE, UNKNOWN that semantically convey meaning that is known and advertised before hand should be considered. Operational status codes should be distinguished from transport/protocol status codes. There’s always a temptation to ride on protocol status codes e.g. HTTP status codes to communicate interaction state.

5. Versioning

The Interface Version Dependency principle requires that systems are dependent on the version of the interface they are consuming and completely unaware of the version of the application that exposes it. From a consumer’s point of view, the only artifact they are dependent on is an operation’s API contract and location.

service API versioning per operation example

a) What to version

To encourage looser coupling between service providers and consumers, each operation in a service should be versioned independently and a contract published for each versioned operation.

b) How to version

Creating a new version of an operation and running it simultaneously with the previous version is one of the better methods of versioning. It has the advantage of not forcing unprecedented work onto the service consumers.

6. Contracts

Publishing contracts (with help of tools such as Swagger) that document an API and communicate its specification and intent should be considered as one of the foremost steps in the development cycle; contract first development.

With a contract availed, consumers of an interface can start to develop clients against it without necessarily having to wait for the service providers to implement the actual endpoints which promotes looser coupling between the service providers and consumers development cycles. Besides, concerns/issues can be addressed at this design phase with little disruption since development work has not kicked off; contract negotiation.

Moreover, it is documentation that’s still needed at the end of the development cycle. More often than not, documenting after the fact is done begrudgingly and dispassionately.

Below are some of the rules and guidelines, in line with other standards, to consider while writing API contracts:

  • Require a single file per operation
  • Files should be versioned with the API
  • Operation request and response should follow the same naming convention: eg a PlaceOrder operation should have a PlaceOrderRequest and a PlaceOrderResponse
  • An API should not return multiple different objects depending on input; only return a single type of response.
  • Define the request and response object in shared definitions.

Summary

Standards can be described as an exercise of intelligent forethought and of decisive action sufficiently far in advance of any likely crisis. They are a crucial aspect in realizing the potential of microservices and ideally arise from principles.

When talking about microservice standards, APIs take center stage because they’re the mechanism that cohesively brings together independently deployed services as one application.

--

--

No responses yet