Three Essential Design Strategies When Embarking on a Microservice API Adventure
by AB Santiago
Say you’re a Chief Architect in an organization that is embarking on a major Microservices initiative, then here are three design strategies you should prepare for your Agile teams. These are design-related overarching guidelines and disciplines you would want your teams to consistently do and follow. This is the product of our learnings in my consulting company where our core service is project implementation of APIs, microservices and Enterprise Integration. Through the years of dealing with customers who were Microservice virgins, we’ve come to know the pitfalls and essentials on a fundamental level. This article wouldn’t tackle organization’s digital maturity nor the tools required, this will focus on tool agnostic fundamental guidelines in designing fine-grained quality microservices.
Decentralization of teams has become the de-facto theme for microservices which makes sense, loose coupling at team level allows focus, specialization and end-to-end area accountability (DevOps). However as Chief Architect, while teams are on their daily microservices grind, they should follow your sets of guidelines and disciplines.
It’s alarming how Agile is being abused in the design department. There’s a growing gap between document specification and code output. Agile does advocate iteration, refactor and recognizes that there are unknowns. One of Agile’s main anti-pattern is “big design up-front, but there’s no implicit rule in Agile manifesto that discourages good design. Bare minimum or “no upfront design” with the habit of “figuring out specification later” like during build or testing stage (aka defect) – is a recipe for disaster. A form of “decent” design specification should be done before build implementation, these designs are governed by blueprint cookbook guidelines that are versatile enough for different patterns. That when followed properly, conjoins consistency across the microservices produced by multiple teams.
Provide Guidelines to Identify Business Domain Boundaries, and Expand Them Using Well-known Techniques
Designing a reusable service is very difficult. To decrease the burden of such difficulty, it’s wrong to jump straight in scoping the boundaries between microservices. Focus first in coming up with different Domains bounded within the organization’s business structure and processes. Eric Evans’ Domain-Driven Design using Bounded Context (sample diagram below) provides a good technique in iterative collaboration between roles in an agile team – Domain expert, Tech Lead/Architect, Software Developer and business sponsor/end-user.
Bounded Context domain modelling isn’t easy, non-technical roles in the team will have a hard time participating in the modelling discussion. The trick to make DDD collaboration work is to establish a Ubiquitous Language, glossary and jargons within the Domain ensures they have the same meaning. From there everyone can contribute to identify: (a) Entities or Object, Attributes of the entity, and functional Operations within the domain, then (b) Aggregates or sub-modules to define transaction boundaries in the domain. For example, it’s possible to have 3 different operations in a Domain boundary (Bounded Context) each contains customer entity.
The common trap that will be encountered when discussing with Product and Business people in the modelling team is they tend to hijack the modelling discussion in a process step or user story type of discussion, swaying away from the objective of defining Data entities. To address this, Alberto Brandolini created a workshop-style of collaboration that focuses on events – the technique is called Event Storming. From experience in scoping workshops with business and end users, focusing on Events, Reactions and Commands proved to be productive. Event negates the subtle difference of the same object between Domains because it only describes what’s happening within the Bounded Context. Events are distinct actions. An end-user clicking the request button is an example of a command event, partner system processing the transaction is a reaction event. Workshop is similar to defining user stories of Agile except event storming is more organised that has clear objectives. For example, the exercise could flesh out different interpretations of entities involved – this indicates a boundary. Another benefit is that as the exercise drills deep, the team would identify data models and attributes.
Provide Design Governance Policies for a Decomposed Application Architecture
Now that identifying logical boundaries of business domains is performed, the next challenge is the more granular physical design. Consumer channels and vertical systems to integrate with will now come into play. In this step, we will identify the scope of each microservice. There should be policy guidelines that would help Agile teams in crafting Domains further to multi-layered aggregates, to design an ‘independently deployable distributed services’ – also known as microservices.
I often bumped into a microservice implementation where only a few people in the organization understand what a service exactly does. In microservices, the communication between service is via web service HTTP – preferably SOAP or REST APIs. As a generic guideline, it’s important to provide standards that promote Design-First or Spec-Driven Development practice to the teams when specifying API structures and service specification. In order to flesh out inter-dependencies, data structure conflicts, data enrichment & transformation logics – before the build grind. Design-first principle would result in: (a) better collaboration between roles in the team, including cross-team collaborations, and gives a chance to (b) revisit reusability – if the design has achieved that. An increased collaboration would save hundreds of refactor coding hours, and practising Spec-Driven Development would give a real API contract that is discoverable and up-to-date.
Contracts doesn’t have to be final upon first draft of design, but a baseline is essential. Especially for services that has intentions of exposing reusable external communication protocol in the form of an API. Principle of fail fast is the main motivation here, team can potentially realize: to combine two microservices into one, or the reverse of splitting a single microservice to multiple upon seeing the data and transaction impact.
For example, below four business domain further splits to more Bounded Context – Presentation, Business and System/Persistence. This could indicate that the three layers of microservice will be built for some domains.
Here’s the list of common errors in microservice design your team/s should avoid.
- Noisy setup. There is a reason it’s not called ‘Nanoservices’, entity service design is discouraged. I’m seeing more and more implementations of microservices that literally just do one thing – like a single API call. Single-responsibility principle was interpreted literally. Worst, some are functioning as “pass-thru”. If you have layer policy guidelines like separating Presentation, Business and System/Persistence, there’s no shame in creating a rule where single service for all layers is ok if the nature of the requirement is ad-hoc, means the likelihood of reuse is low. It’s a waste of physical resources (network, CPU, memory), not to mention that every microservice has code overheads.
- Fragile service “autonomy”. Data structures and/or functional events in a service that impacts too many relationships and dependencies to other services. It’s a sign that the microservice isn’t optimised enough to stand on its own. You would want to minimise changes to one or few microservices for future requirement change/s.
- Imbalance frequency of use. For better scaling, it’s not healthy to combine frequently used functions with seldom-used ones together in a single microservice. You wouldn’t want to also scale low throughput transaction types along with extremely high ones.
- Tightly coupled to a system brand. When aggregating or abstracting a vertical system’s complexity through microservice API – It’s fine to refactor the data structure format of a microservice that links it to a system (in the case of channel interface or adapter service), like when a system endpoint’s format changes or worst totally replaced. However, the internal data structure format and logic handling should remain functional oriented, or system agnostic.
- Canonical Data Modeling (CDM) is discouraged. Designing a flexible data structure for a service interface is different from CDM. CDM is making a monolithic data structure that will be constant across all services for every entity instance (eg. product), regardless of the entity’s functional context. Standardising is fine, but CDM isn’t compatible to distributed service, they’re horribly unmanageable. Your objective is to establish a minimal set of rules to empower teams to independently work on their microservice modules, which includes data structure independence.
Produce Design Standards that will address Cross-Cutting Concerns
Cross-cutting concerns (CCC) in software development are “generic” technical requirements. These are features your team wouldn’t expect to see in a business nor technical requirements for a specific service feature list. If there’s one area where monolithic applications have an advantage of, it’s likely that code reuse for Cross-Cutting Concerns is present. They can be shared centrally in Monolithic through single instances of libraries or frameworks.
In a distributed service architecture like microservices, these cross cutting concerns would have to either be applied or copied to every applicable microservice – effectively becoming an overhead. I have seen designs where they try to make a standalone service for Cross-Cutting Concerns like logging, being called externally over the network via an API call. The result is unpleasant, crude implementation, latency overhead, and it introduces new forms of limitations. In summary, it’s not recommended to create standalone cross cutting concerns related microservice/s.
There’s a programming paradigm for cross-cutting concerns called Aspect Oriented Programming (AOP). AOP is generic behaviours or aspects that can be executed in the code once declared, it basically intercepts or injects the framework implementation when the behaviour criteria is satisfied. The downside of this is AOP can tend to be intrusive and noisy because custom scenarios to ideally turn them off isn’t an option, aspect change would need to be done in the AOP framework, and it’s not ideal to change custom scenario in a supposedly generic layer.
Now that we know CCC cannot be shared in microservices architecture, it doesn’t mean we will ignore it. A form of cookbook guide should still be created. For disparate development Agile team setup, sets of standards should be followed to avoid different ways of implementations for the same generic concerns. During sprints, cross cutting concerns should be default task cards for every service. Upon beta-version code completion, code reviewers that aren’t abreast to the functionality of the service would tend to focus on sets of standards such as Cross-Cutting Concerns, along with code style and naming conventions. Here are the main CCC list:
- Parameter lookups. These are shared configurations, placeholders and lookups – these have different contexts on various technical angles. Standards need to be set on how this should be implemented. Example concerns for this are file and directory separation for credential values, dynamic values, communication session related parameters, whether to localise a parameter or not.
Logging, notification and alert. Configuration settings of the logging framework, then notification and/or alert logics (eg. send email or SMS) based on log level – WARN, ERROR, FATAL. For investigation purposes, implementation of logging should ideally have conventions to help with tracing and transaction querying.
- Exception handling. Strategies and standards to implement before terminating the session/message in an event of an exception. In native Object Oriented exception handling has different types: checked, unchecked, irrecoverable error.
Monitoring and Performance. Proactive Health Check frameworks to be defined, and in some cases benchmarking performance utilities to provide throughput metrics.
- Audit tracing and correlation. For end to end tracing of transactions with external vertical system as part of the consideration. A strategy is needed to have a traceable form of ‘reference number’, an ID that maps the entire lifecycle of the payload message including external interactions with another microservice.
- Authentication and authorization. Repeatable security related standards that needs to be abstracted due to a number of services will be implementing similar authentication and/or authorization mechanism.