Many organisations are migrating to making use of the Microservice philosophies in developing systems. Generally this makes complete sense and can potentially provide dramatic performance benefits. However, unfortunately many organisations make the same mistakes when it comes to implementing their microservices.
Typically the main objectives organisations and development teams are trying achieve when it comes implementing microservices, is to try and split apart their Monolithic applications. Unfortunately, what this tends to evolve into is that what they end up doing is creating a number of smaller monolithic applications that seem to interoperate with each other. The number one culprit of this type of anti-pattern is the over reliance on the Layered Architecture when designing their services, which inadvertently leading to the implementation of Distributed Monoliths.
I have to admit, I also don't like the use of Layered Architecture pattern when developing REST API's, my view on this is that in most cases REST API's are generally the first step organisations will take on the way too implementing a microservice strategy or philosophy and it is also the first mistake they make.
I'm definitely not a hater of the Layered Architecture, it is still a good pattern, it is just as so many aspects of software development, is great in certain contexts, but it is also a really poor choice in others. This article is primarily aimed at the context of Microservice development.
I have to prefix this article, with that it's not necessarily down to bad practices or bad implementations, primarily because I feel that this could be implied as a slur on the development teams who have implemented this type of pattern in the past, with all the best of intentions. Another caveat I would emphasise here, that I too in the past have made the argument that the short comings of layered architecture pattern in microservices only for them to become evident much later in the cycle. Its easy to the blame the previous implementation or bad practices, but the reality is once you see the light and experience the actual short comings first hand it does become easier to understand.
Working in software development for a number of years, it is also fair to say that belief in the layered architecture has almost reached religious proportions. It's almost as if any questioning or doubting the architecture style could lead to being ostracised from the community. I have to re-iterate that this article is not aimed as dismissing the layered architecture in general, or saying that it is the root of evil in microservices, it's primary purpose is to highlight that when developing microservices developers need to bare in mind that there are a number of alternative software architecture and design patterns that lead to better implementations.
In Monoliths to microservices, Sam Newman, explains the perils of the layered architectural flaw and how they typically emerge in environments where not enough emphasis on concepts like information hiding and cohesion of business functionality leading to highly coupled architectures.
In my experience, this scenario typically emerges when an organisation attempts to develop their own unique framework or templates to develop Microservices. The primary problem I constantly see with the layering approach too is that there is always contention when multiple developers want to change the same piece of code and different teams wanting to push functionality live at different times.
This typically occurs when using Data Transmission Objects (DTOs) which are placed in reusable common library , when it turns out the each team needs to slightly modify the DTO to their needs, i.e. adding additional properties or amalgamating properties etc. Often teams will also try centralise the Mapping of objects to a central layer within application. Thus making object mapping a contentious issue.
Probably the most common mistake I see is Swollen controller pattern in the API layer, which I have previously discussed in Developing API Endpoints . In this post, I'll try dig a little deeper into these issues to try help developers to avoid some fairly common issues I find.
What is Layered Architecture
The layered architecture pattern, is probably the most common and simplest software architecture pattern to implement. It has also become the de facto standard for most Java EE & C# .net applications and therefore is widely known by most architects, designers, and developers.
The primary concept behind the Layered Architecture pattern is that all the components are organised into horizontal layers, each layer performing a specific role within the application. For example a presentation layer would be responsible for handling all user interface and browser communication logic. Any layer should not be concerned about the working of other layers.
Each layer of the application consist of objects specific to a particular concern it represents.
- Presentation layer: This layer consists all the classes responsible for presenting the user interface to the end user or sending the response back to the client.
- Application layer: This layer consists all the logic that an application requires to meet its functional requirements while not being a part of the domain rules. In most systems, the application layer also contains services orchestrating domain objects to fulfil a particular use case scenario.
- Domain layer: This layer represents the underlying domain that mostly contains domain entities and in some specific cases, services. Business rules such as algorithms and invariants should all be a part of the domain layer.
- Infrastructure layer: Also known as a persistence layer, the infrastructure layer includes all the classes responsible for performing the technical actions such as persisting data in the database, like repositories, DAOs (data access objects), etc.
Eric Evans discusses Layered Architecture in his book Domain Driven Design: Tackling complexity in the heart of software and many software teams have taken to implementing this architectural pattern by default. However, the primary problem with this approach is we have to bear in mind context of when this book was initially written and published, back in the early 2000's and for the most part was mostly targeted at what are termed as Monolith applications today.
This is not to say, that the book and the concepts presented in the book are not relevant today, far from it, it remains and will continue so as a very important piece of work and has actually inspired many of the software architecture patterns we know today. Including the CQRS pattern
Layers of Isolation
Each layer in the layered architecture style can either be Open or Closed. A closed layer requires a request to move top down layer to layer and a request cannot skip a layer and must be past to the layer immediately below it to get to the next layer. Open Layers enable requests to bypass layers.
The layers of isolation concept means that changes to made to each individual layer should not affect or impact components in other layers, by providing and ensuring the contracts between the layers remains unchanged. Each layer is independent of the other layers , having little or no knowledge of the inner workings of other layers in the architecture.
To support layers of isolation, layers involved with the major flow of the request necessarily have to be closed. If the Presentation layer can directly access the persistence layer, then changes made to the persistence layer would impact both the Business Layer and the Presentation Layer. Producing a very tightly coupled application with layer inter dependencies between components. Resulting in this type of architecture becoming very brittle, difficult and expensive to change.
An additional concern to consider when implementing the Layered Architecture is the inadvertently implementing Architecture Sinkhole anti pattern, which occurs when requests move from layer to layer as simple pass through processing with no business logic performed within each layer. Every layered architecture will have at least some scenarios that fall into the architecture sinkhole anit pattern.
Domain Objects and Data Transformation Objects (DTO's)
A common mistake I often find in projects is that teams will often try to extract all Domain Objects and DTO's to a common library or centralised libraries within the application, thus tightly coupling every layer in the application these libraries.
Worse still, is that I also find teams that have implemented this also tend to centralise their Mapping Logic, whether it be having a few centralised Automapper classes in a project that also has tighly coupled dependencies to the Domain and DTO projects, that incurring an ever increasing bleeding dependencies thoughout the project, Or by developing customised mapping libraries often based on the misconception that Automapper is the root cause of the mapping bugs. The fact is the root cause of the mapping bugs, based on my experience is the centralisation.
Another common problem I find, and I loosely touched on this concept in C# Records the good, bad and Ugly and the over use and unnecessary implementation of immutability. Immutability is not bad, in fact it can be good. However, in large parts of enterprise software development it can be largely completely unnecessary. In most cases, where I have scene immutable objects being introduced to overcome problems, it has largely been to overcome poor architectural implementations, primarily caused by passing objects through too many layers.
This effort is also doubly wasted because the Layered Architecture style simply isn't suited for Multi-threading, internal messaging and other parallel processing practices and techniques.
Personally, If I find myself trying to break down objects to Domain Objects and Data Transformation Objects within the boundaries of my REST API, then I realise that I have gone too far and I am then falling into the Layered architecture problems within my REST API.
The Problems caused by layered Architecture
In the Fundamentals of software architecture also explains why most software development teams fall into the trap of implementing the Layered Architecture Style for most projects. Primarily because teams fall into the trap of thinking that the layered architecture style is a good choice for simple applications, and thinking that microservices are simple applications. The problem is that microservices are never really simple applications, they can be complex units of functionality in order to address particular problems.
Fundamentals of Software Architecture
An Engineering Approach
provides the first comprehensive overview of software architecture's many aspects. Aspiring and existing architects alike will examine architectural characteristics, architectural patterns, component determination, diagramming and presenting architecture, evolutionary architecture, and many other topics.
The Perils of DRY in microservices
The concept of clean code development teams fall foul of when implementing Microservices is the Don't Repeat Yourself (DRY) principle, which was first introduced in the Pragmatic Programmer first edition, which was primarily written at the time when most software architectures were based on Monolithic Systems. So the concept of DRY made absolute sense. You should not repeat code in a monolithic stack and it makes sense to try re-use code. However, in a microservice based architecture this is not so much of a concern.
DRY and the Perils of Code Reuse in a microservices world , is a concept that Sam Newman raises in Building Microservices, whereby developers try simplify the concept to trying to avoid duplicated code. However, DRY more accurately means we want to avoid duplicating our system behaviour and knowledge. The problem with DRY, is that leads developers to try to develop code that can be reused. Pulling code into abstractions that can be reused everywhere. Which can be deceptively dangerous in a Microservice architecture.
You may stumble across this, as a side effect of trying use C# Records as immutable objects in a Microservice, because developers are concerned about an object being reused and it's values been changed. This is a knee jerk reaction to fix a flaw which is fundamentally an architecture problem.
Don't violate DRY within a microservice but be relaxed about violating DRY across all MicroservicesBuilding Microservices
What has DRY got to do with Layers?
Teams implementing a layered Architecture are typically trying to also develop common client libraries for their Microservices, based on the argument that it is easy to reuse components of your service and avoiding code duplication. The problem, with this approach is that what you end up doing is tightly coupling your services via a common library.
The primary objective of a microservice architecture is too promote loose coupling. I am also of the opinion that even in your microservice, do you never really want or need to perform the same task in more than one location. Each piece of business functionality within your service should be isolated and you should very rarely need to reuse objects across the functionality exposed by your service.
Each Feature of your service should be completely isolated, without ever needing to bleed outside of its area of functionality. The feature in your service should be passed all the data it needs to perform its action in the form of a request and it should provide its own isolated response.
Personally, I prefer the Vertical Slice Architecture when it comes to implementing Features for my microservice.
Lack of Scalability and elasticity
The layered architecture is ideally best suited for Monolithic Applications, primarily due to their monolithic deployment and their lack of architectural modularity. The primary concern here, is that as mentioned earlier, the Layered Architecture pattern simply is not suited for Multi-threading, internal messaging and other parallel processing practices and techniques.
When moving to a microservice architecture Scalability, Elasticity, Multi-Threading, Internal & External Messaging and Parrallel processing are a vital concern.
Layered architectures don't support fault tolerance due to their monolithic deployments and architectural modularity. If one small part of the layered architecture causes an out of memory condition to occur the entire application unit is impacted and crashes. There is also a high Mean-time-to-recovery (MTTR) experienced by most monolithic applications with start up times ranging from 2 - 15 minutes.
Disadvantages of Layered Architecture
- Lack of scalability: The principles of layered architecture hinders the growth of your project as it does not help to scale your project.
- Hidden use cases: It is difficult to determine the use cases of your project by simply checking the code organization. You need to refer the class names and in most cases, implementation.
- No dependency inversion: In a layered architecture, the dependencies are direct and conceptually changes into essential higher layers from a low-level infrastructure layer.
When to use layered architecture
Layered architecture is a great choice for small, simple applications or websites. It is also a great to use as a starting point for applications, due to its simplicity, familiarity and ease of implementation.
The downside to this is when your services grow and the complexity increases characteristics like maintainability, agility, test-ability and deploy-ability, scalability and elasticity are adversely affected. It is for this reason that for Microservices are much better suited for more modular architecture styles.
Despite the Layered Architecture being an intuitive architectural pattern for most developers to learn and understand, unfortunately it is not always the best software architecture pattern to implement and in may instances will actually be the root cause for many problems as the software solution grows.
Initially this pattern is the cheapest to implement and many developers will opt for it at the start of any project as they grapple with trying to understand the domain and the problem they are trying to solve. It is not the best pattern to implement for the long term for your solution.
When it comes to Micro services and even REST based API's the layered architecture pattern should be avoided.