In 2014, Martin Fowler and James Lewis put together a blog called "Microservices" after having concerns about the loose use of the term. I was working with ThoughtWorks at the time and, being a massive fan of Martin Fowler, I would always read his work. Although a decade has passed since then, and despite the clarity provided by his work and the community, the term 'Microservices' is still used loosely. There are definitely firms pioneering this architectural style, but many more are just following the trend for the sake of the name.
Ever since the term SOA was coined in 1996, there has been a lot of ambiguity surrounding it. In this blog, I am attempting to take a step back and explain what service-oriented architectures are and where microservices fit in. We will start by exploring SOAs themselves. Then I will move on to discuss the architectural styles sliced by sizes and finally examine the various implementation approaches based on functionalities. None of what I propose is new knowledge, it is an attempt to bring it all on a single page with the intent to form a reference for technical decision-making in organisations and teams.
Before we dive in, I would like to make it clear that this is not a sales pitch advocating for the adoption of service-oriented architectures. Each organisation or firm is unique, and while a service-oriented approach may work for some, others might be better off sticking with a monolithic approach. This is akin to the first law of thermodynamics, which states that 'energy cannot be created or destroyed.' The same holds true for complexity in architectures - we do not create or destroy complexity; we merely shift it from one form to another.
Looking closely at the definition above, it speaks to how this architectural style is a composition of "services" and what a "service" is. The definition of SOA above focuses on the composition. Whereas the definition of "a service" in the statement is much broader. A definition this wide can invite different and potentially incompatible ideas to be grouped together. These ideas as Martin Fowler says in one of his posts need to be described and named properly. While I tend to agree partially with Martin Fowler, I also believe, since his blog first came out in 2005, many of the ideas have been named and found definitions (some by him as well). Looking closely at the definition above, it speaks to how this architectural style is a composition of "services" and what a "service" is. The definition of SOA above focuses on composition, whereas the definition of "a service" in the statement is much broader. A definition this wide can invite different and potentially incompatible ideas to be grouped together. As Martin Fowler says in one of his posts, some of the concrete ideas under the umbrella need to be described and named properly. While I tend to agree partially with Martin Fowler, I also believe that since his blog first came out in 2005, many of these ideas have been named and defined (some by him as well). We shall explore the ideas which did find concrete footing in the sections below.
Example 1: A Monolithic Architecture
I worked at a startup. We had a team size of 10-15 developers, and we worked on a monolithic Ruby on Rails application. The codebase was highly modular and followed an n-tiered pattern with a UI layer, business logic layer, and database layer. Since the domain was not overly complex, as developers, we could easily navigate the codebase and contribute to any part of the application. This architecture was well-suited to the size of the organisation and supported a significant amount of traffic on the website.
Example 2: Service-Oriented Architecture with Monolithic services and Microservices
In a different example, I worked at a large organisation with both in-store and digital presence. This organisation had many teams with varied capabilities to support their customers. These teams built and ran their own applications; some were monoliths (of which some were built in-house and others where off-the-shelf products such as an e-commerce platform, CRM, etc.), while others comprised one or many microservices crafted with domain boundaries. These diverse applications and services integrated seamlessly to support substantial in-store foot traffic and digital presence.
The definition from microservice.io is a fine-grained set of the characteristics as compared to the service definition from the SOA. The additional characteristics of microservices include being 'loosely coupled,' having a clear ownership aspect, and being organised around business capabilities. These extras are what makes the definition of microservices a strict subset of the definition of service in SOA. The loosely coupled nature of microservices lays the foundation around communications between services i.e. communicating with lightweight mechanisms, often an HTTP resource API, or a message broker. The ownership aspect with a single team talk to autonomy and the ability to move fast. The organisation around business capability also speaks to organisation structure and the data ownership. And finally the word "micro" in microservices talks to the size of the service (How big is a microservice?). Let's dive into examples:
Example 1: A Banking Ecosystem
For a banking application, a few microservices could be - Account Management, Transaction Management, Customer Management, etc. A team may own a single or a few of the services. There would be a light-weight central governance but the individual teams would have autonomy.The governance would be around the set of tech stacks to choose from, communication protocols, etc.
Example 2: The Pioneers
REA Group were pioneers of this architectural style in Australia. They started blogging about this back in 2014. Furthermore, they leverage these services to expose their public partner platform and APIs allowing customers and other businesses to integrate. There are many others doing this right such as Netflix, Amazon, Uber, etc.
In November 2016, ThoughtWorks technology radar listed debuted the term “Micro-frontends” as a technique in "Assess" state. Since then, it has moved to "Adopt" state on the radar. The definition on the radar states:
Put simply, you get all the benefits of delivering incrementally and independently, you have your independent codebase, and you can deploy independently in the frontend layer. You also inherit the complexity of the distributed system (as stated earlier, you make educated depictions based on factors such as domain complexity, firm size, wtc to go or not go with distributed approach).
For example: When you land on a search result page of an e-commerce website, the product tiles would be getting served from a product micro-frontend, the add to cart functionality from a cart micro-frontend, and the recommendations from a recommendation micro-frontend. Each of these micro-frontends are owned by a single team and can be developed, tested, and deployed independently.
Following the frontend we have the BFF layer. According to Sam Newman, this architectural patterns creates a backend for the frontend where a frontend might be a dedicated channel for a consumer (e.g. mobile, website, etc.) or might be a domain aligned independently deliverable frontend applications aka micro-frontends. Here the BFF service is:
The team owning the frontends owns this service and operates it autonomously. It comes with benefits such as reducing waste over the network due to over-fetching or under-fetching. It also is great at encapsulating functionality which are more specific to a specific channel.
Example 1: BFF by customer facing channel - Diving into an example of a digital retailer who has a mobile app and a website. The mobile app has a BFF service which is optimized for mobile devices and the website has a BFF service catering to the web.
Example 2: BFF by domain aligned frontend - In an accounting platform for small businesses, the teams are aligned by the domains. Each team has a micro-frontend, an optional BFF (based on the complexity of aggregations) and a domain service.
The knowledge base on experience APIs over the internet has exponentially increased but there were 2 very specific things which popped out explaining the need of this pattern:
Both the above clicked in my head and matched perfectly with the problem solved with services exposing experience APIs. Here the Experience API exposes a boundary around a customer journey. Let’s understand this with an example: Let’s say we have a Payment Experience API which orchestrates the payments from consumer of both the channels - mobile and website. This experience service is responsible for an experience where customer wants to leverage split payments. The customer keys in 3 gift-cards and a credit-card for payment of their online order. The Experience API then does a pre-authorisation for all the supplied cards by calling a Payment API (Core API used by both digital and in-store channels). In case there is a failure to pre-authorisation the experience layer is able to release the money held on the previous cards by calling the same Core API for the payment domain. It may also offer other capabilities such as fraud detection.
Let’s go back to the 2 points we referred to:
These services have many benefits. As mentioned above, this brings in re-usability where the experience orchestration can be extracted from the frontend layer and/or the BFFs. Teams closer to the user experience and channels own this layer. They have a deep understanding of user journeys and can leverage this layer to achieve fine-grained control for the journeys. The layer can leverage asynchronous communication with downstream domain APIs, uplifting the efficiency and performance of the customer facing channels.
In Domain-Driven Design, bounded contexts set boundaries that expose business capabilities while protecting the internal models (a black-box approach). Each bounded context presents an opportunity to build at least one domain service, which can be exposed via a communication protocol. Aggregates and bounded contexts are the building blocks upon which microservices can be based. This is why, when considering a bounded context, there is always an opportunity to build at least one domain service. In some scenarios, it may be necessary depending on the complexity to build multiple microservices within the boundaries of a single bounded context.
For example, a Retail Consumer API might be a single microservice that exposes APIs to create, update, retrieve, and delete customer details. In contrast, an "E-commerce Order Management" bounded context could include multiple microservices, such as an ordering service, payment service, inventory service, and shipping service, each exposing different business capabilities. In this latter case, while an order might be placed synchronously, the shipping service could be triggered asynchronously by an event.
In the context of Domain-Driven Design, domains can be categorised as Core, Supporting, and Generic. Based on which domain we are working under the context of an in-house solution vs an off-the shelf solution can be leveraged. Core domains are the "bread-and-butter" of the firm and should be developed at a slow and thoughtful pace, whereas generic domains should be ones where off-the-shelf software can be very beneficial. This is a topic which I will explore in a blog later in more details.