Service Oriented Architecture - Slice and Dice

Posted on 2024-06-10
architecture microservices service-oriented-architectures

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.

What are Service Oriented Architectures?
Service-Oriented Architecture (SOA) is an architectural style that supports service orientation. Service orientation is a way of thinking in terms of services and service-based development and the outcomes of services. A service:
  • Is a logical representation of a repeatable business activity that has a specified outcome (e.g. check customer credit; provide weather data, consolidate drilling reports)
  • Is self-contained
  • May be composed of other services
  • Is a “black box” to consumers of the service

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.

Slicing and Dicing by Size?
To begin with - let's talk about the sizes of services. When it comes to building a service there are only two widely accepted sizes - Microservices and Monoliths. However, a new(-ish) term "macro-services" has been tossed around, but it is not a widely accepted one.
Monoliths
Monolithic architecture refers to an architectural style where a single, unified application is built in one large codebase and deployed as a single unit. A small to medium-sized team can successfully use a monolithic approach without their code becoming a legacy system, provided they adhere to clean code practices and maintain a modular codebase. While a single monolithic codebase represents monolithic architecture, these monolithic applications can also be part of an ecosystem that embraces service-oriented architecture. Here are two examples to illustrate these scenarios:

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.

Monolithic Architecture Example 1 Diagram

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.

Monolithic Architecture Example 2 Diagram

Microservices
Microservices - also known as the microservice architecture - is an architectural style that structures an application as a collection of services that are:
  • Independently deployable
  • Loosely coupled
Services are typically organized around business capabilities. Each service is often owned by a single, small team.

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.

Microservice Architecture Example 1 Diagram

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.

Microservice Architecture Example 2 Diagram

Macro-services
Since the term has been tossed around a few times, let’s examine what it could mean. My research led me in with 2 sets of subtly different definitions - first where Macroservices and Monoliths are synonymous and the latter which described it purely by size sitting between the size of a monolith and a microservice. From my experience of working in the industry and seeing a handful of these types of services being used I would state that it reflects few characteristics of Monoliths and few of macro-services. My proposed definition: “Macroservices refer to an architectural style where parts of business capabilities are built in a service, where these capabilities may not belong to the same domain. The service is independently deployable. May be owned by one or more teams”. Looking at examples where this pattern has been successful, I can duly state that this is NOT an end state implementation pattern i.e. it could be a stepping stone when moving from a monolith to microservices. It may allow strangling the monolith into smaller services. But it MUST be a stepping stone. This style comes with the disadvantages of tightly coupling (despite the smaller size as compared to monoliths) and also incurs the overhead of managing infrastructure and deployments without really achieving autonomy. Furthermore, rather than being a transient/temporal state this can become a thing eventually scaling back to the size of a monolith. Hence, this should truly not be a promoted style.
  • Example: The stepping stone - We were working with a company which bloomed from a startup to an enterprise in a very short period. To achieve the quick scale up they acquired a few other products (from startups). One of the product they acquired was an n-tiered monolith itself which extremely coupled, and cost of change was increasing exponentially. Some domains in this monolith had business logic for which no one knew an exhaustive set of test cases. To be able to break this monolith we proposed a few steps. First step was to identify the low-hanging fruits i.e. the more isolated and low risk domains and create microservices around them. Second was to identify highly coupled areas and lift and shift into macroservices which can be refactored and tested. This would allow moving at desired speed but also opening tighter knots in the monolith. The idea was the microservice itself would slowly be refactored and stripped down to a single domain.
Slicing and Dicing by Functionality?
Next we move from exploring the size of the service to the functionality it encapsulates. Let’s work backwards from the consumer facing layer. We have digital channels such as Mobile, Websites, Self-service kiosks, etc. Each of these channels surface a frontend for the consumer to interact with. These are then supported by a Backend for Frontend (commonly referred as BFFs) which are tightly coupled with the user channel/interface. Then comes the services exposing Experience APIs which form a bounded context around customer journeys. Finally, comes the core domain services exposing domain APIs. Let’s dive into each of these:
Micro-frontends

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:

In this approach, a web application is broken up by its pages and features, with each feature being owned end-to-end by a single team. Multiple techniques exist to bring the application features—some old and some new—together as a cohesive user experience, but the goal remains to allow each feature to be developed, tested and deployed independently of others.

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.

Backend for Frontends aka BFFs

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:

  • self-contained and independently deployable
  • Tightly coupled to the frontend it is created for

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.

Services exposing Experience APIs

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:

  • In the very first episode of “The Engineering Room with Dave Farely”, Dave Farely refers to his conversation with Eric Evans on microservices in which Eric states an observation that “the protocol of exchange of information between services is a distinct bounded-context distinct from the bounded context of the service”
  • The other, where Sam Newman in his blog “Pattern: Backend for Frontend” under the section "Reuse and BFFs" expanded on the concern around duplication between BFFs themselves which leads to the dilemma of merging or not merging BFFs.

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:

  • We are exploring a distinct bounded context from the point of view of a customer/user journey
  • It removes the dilemma of re-usability stated by Sam in his blog.

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.

Services exposing Domain APIs

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.

Final Thoughts
The terms service oriented architecture, microservices, core services, all often get used interchangeably, but they mean very different things. Each architectural pattern addresses different problems and offers unique advantages. This was an attempt to provide a structure and talk about all these concepts on a single page. These patterns while connected solve different problems, and an ill-informed or conflicted interpretation leads to making decisions which neither last long nor sustainably solve the problem. It's crucial to assess the specific needs and context of your organisation to determine the most suitable approach, whether it's a monolithic, or microservice architecture. By understanding these differences, we can make informed decisions that effectively address the complexities and demands of modern software development.
References