By now, most developers have heard all about the benefits of microservices. Yet, when it actually comes time to "move your monolith" by converting existing apps to a microservices architecture, you may well find it daunting to design an effective microservices architecture. The developer community has spent much more time discussing the why of microservices architecture than it has the how.
To help provide some balance, let's take a look at best practices for designing a successful microservices architecture. We won't get into developing or deploying microservices themselves (that's fodder for another day's article), but we'll discuss common mistakes to avoid when planning a microservices architecture.
1. Obsessing over One-Service-per-Function
Ask most developers to describe an effective microservices architecture, and they'll tell you that each area of functionality in an app should be powered by a different microservice. For example, with a payment app, authentication should be handled by one microservice, while another microservice does payment processing, another runs the front end, another stores and retrieves data, and so on.
In general, aiming to delegate each major application function to a different microservice is a good idea. However, it can be easy to take this principle too far--to the point that it gets in the way of designing an effective microservices architecture.
Sometimes, the line separating one function from another is blurry. For example, should you treat user registration as a separate function from user authentication, and therefore create individual microservices for each? If your application stores data in more than one location, should each location get its own microservice? Or should you have just one data service that handles all locations?
The answer to these questions is that it probably doesn't matter much. Do what seems to make the most sense when it comes to figuring out how many microservices the app will have and which functionality each one will handle. If you spend too much time figuring out how to split up different tasks within the app, you won't be very productive.
2. Making Microservices Too Small
Along similar lines, it's a common mistake when designing a microservices architecture to make each microservice so small that you need many dozens of microservices to compose your entire app.
Developers run into this pitfall because they think that smaller is better when it comes to microservices. To a point, that's true--breaking a large app into smaller, discrete units is one way to make the app more scalable and more reliable.
Yet, when microservices get too small, you end up with too much overhead down the line when it comes time to develop and deploy them. Each microservice will require its own development and deployment pipeline (not to mention separate monitoring, logging and security operations).
So, while you do want microservices to be small, you don't want them to be too small--and you don't want your app to have too many microservices. As a very general guideline, if you have more than a dozen microservices comprising your app, each one is likely too small and you should have designed your architecture differently by merging some of them together.
3. Requiring a Specific Deployment Solution
But will this be true a few years down the line? Maybe, or maybe not. Deployment technologies change rapidly, and it's hard to know which deployment solutions will make the most sense for your microservices app at a future date.
For that reason, it's a mistake to design your microservices architecture in such a way that it requires a specific type of deployment technology. You don't want to make yourself dependent on Kubernetes--or even containers in general--to be able to deploy your app. Instead, design an architecture that can work on any type of infrastructure--and, if possible, on any operating system.
4. Requiring That All Microservices Be Updated at the Same Time
A mistake you sometimes see in a microservices architecture is the requirement that if one microservice within an app is updated, the others have to be updated (or at least restarted), too.
This is a natural way to think if you come from a monolithic point of view. But, with microservices, this approach means you are shooting yourself in the foot. Part of the point of microservices is to allow some parts of your application to be updated, or to scale or be restarted, without impacting other parts.
Thus, if a change to the state of one microservice means that others have to be changed, too, you lose a lot of the flexibility that microservices are supposed to provide. You also make it harder to do continuous delivery because you can't push out an update to one service without impacting others.
Another way of making this point is to say that your microservices should not be coupled too tightly. To the extent possible, avoid designing your architecture in such a way that one microservice can't run unless another one that it depends on is up.
5. Leaving out Logging
A final pitfall to avoid when designing a microservices architecture is to leave logging out of the picture.
This is an easy mistake to make. It's tempting to assume that you can figure out logging later, when you are actually writing the code for each microservice--or that you'll simply be able to drop a logging agent into your microservices environment and have it collect all the data you need.
It’s far better to include logging in your microservices architecture from the very start. In many cases, that will mean creating a microservice just for collecting log data from the other microservices. In others, however, each microservice might do its own logging and redirect the data to a central location.
Whichever strategy you adopt, the goal should be to ensure that your microservices architecture makes it easy to collect log data from across the application and get it into a centralized location for analysis and retention.
There are no hard-and-fast rules when it comes to designing microservices. Still, as general guidelines, the principles described above will help you plan a microservices architecture that successfully delivers all of the benefits that microservices are supposed to provide, without the hassles that a poorly designed microservices architecture can impose on developers and IT teams.