During the past few years, designing applications as a collection of Microservices are becoming a common architectural pattern. It is important to understand Microservices is not a silver bullet and meant to solve all the software problems out there. However, there are many success stories out there of adopting Microservices by many big players in the software industry which fuels the uprising trend.
Similarly, there are many failure stories as well and the numbers could be even higher since many of them doesn’t go out to the public. One of the main reasons I have seen for failures is the lack of understanding what we are dealing with when designing Microservice architectures.
One of the important lessons I learned designing Microservices is that it demands a deep understanding of distributed systems and without this knowledge, it is likely to end up in doing the wrong abstractions and communication between Microservices.
So what could go Wrong?
I have seen many occurrences where developers claim to say that they have adopted Microservices architectural style but when going into the details it is not the case. Let us look at few common mistakes people do when designing Microservices.
- Using containers (e.g; Docker) doesn’t mean you are adopting Microservices. It is just a tool which can be used to implement it.
- If different services, share code directly within the project (Without externalizing the dependencies and accessing them via a repository with versioning), then you are doing it wrong.
- Depending on too many Microservices to perform a single task, could potentially lead to dependency and performance issues. For example, I have seen single page web applications that call many Microservices to perform a single transaction.
- There is a common misconception using middleware (API Gateway’s, Messaging Queues & etc.) Microservices is a bad practice. It is important to understand that, the problem is not using middleware, rather how it is being used for Microservices. For example, one can argue, that setting up Middleware services could be too expensive for a Microservices. There is a truth behind it also but if we take a look at Cloud-based middleware, for example, AWS API Gateway, its possible to integrate it for each of the Microservice API endpoints, for consumption based cost model (e.g; $3.50 per million API calls and 0.09 per GB of data transferred) which overruns the costs over the benefits.
- For certain applications, it is not mandatory for Microservices to communicate via REST APIs. The concept of APIs as a contract could be implemented in different levels. For example, if several Microservices needs to share files between each other its possible to use shared network file system such as Amazon EFS or object storage like Amazon S3 to implement a contract with governance using a well-versioned library to interface the access along with policies for backward compatible modifications to the library.
- It is also important to understand that when designing Microservices, some of the best practices we used to follow in developing Monoliths could be challenged. If we take the concept using a single database for consistency without duplicating data, this will become an anti-pattern in Microservices context, if they access the same database ultimately making these Microservices highly coupled with data dependencies.
- Dividing Microservices based on technical layers also considered as a bad practice. I have seen some have separated the Data Access layer as a separate service which caused major performance issues.
When designing Microservices, there are several key points, I validate over and over again in the design phase.
- How many Microservices should I divide the business capabilities into thinking in terms of domain driven design.
- Partitioning the data into different services with their own database solution with the understanding of which segment of data needs to be kept redundant locally for each of these services.
- How data is synchronized between Microservices for redundant data.
- Consistency requirements of data. (Whether its transactional requiring strong consistency vs possibility of implementing eventual consistency).
- Selection of right interfaces, channels, and middleware for communications and interaction between services.
- Required level of Security, Reliability, Performance, and Efficiency thinking the costs in mind.
- Tools and technologies to be used for individual Microservices. (Serverless, Containers, Web Technologies & etc.)
- How to version and share code (If required) in the form of libraries using repositories like NPM.
- How the teams and processes (Development, DevOps, Change Management & etc.) are structured around Microservices.
On the other hand, trying to do a big design upfront dividing the Business Domain for a large number of Microservices is not practical either. It is always challenging to find the golden number of Microservices to start with but its better to start small and divide later.
You can also consider areas such as Security, Privacy of Data (Especially with GDPR requirements), Domain Driven Design Practices and most importantly the composition of the development teams and sub-teams to fuel your thoughts.
One of the clear benefits I have seen with Microservices is that, although the overall architecture is complex considering overall services and interactions which requires expert design skills, implementing individual services becomes simpler with their own lifecycle, improving developer productivity in long-term.
If you are designing Microservices in Cloud, Serverless technologies and managed services could potentially solve most of the challenges without needing to write code or use third-party middleware. For instance, I’m often using AWS SNS with Lambda (governed by SNS policies for message format contract) as the pub-sub messaging solution to synchronize data between the databases in Microservices. For Microservices that needs to be deployed in an on-premise data center, there is Open Source messaging middleware like Apache Kafka which could potentially replace AWS SNS.
Using Identity Server (e.g; Amazon Cognito UserPools) for Authentication and Authentication Federation could be also a good choice to integrate rather implementing it as a Microservice from scratch. It is also useful to use stateless tokens like JWT for both for authentication and authorization (using claims), which could be validated by different Microservices without explicit dependencies.
I have been often using OpenID Connect in multiple projects to implement authentication across Microservices (using the standard authorization flow with OAuth2.0) which proved to be much convenient in the long run.
In a Microservices implementation, you can consider each of the services as an individual project and organize the code artifacts and CI/CD setup around each service. However, it is also important to identify the common interaction channels between services (e.g; RestAPIs, Pub-sub topics, Queues & etc.) and treat them separately having their own change management process. This plays a crucial role in making sure the services interact with each other without causing a change in interfaces to break the applications.