Antipaterns to avoid for building microservices-based systems
We are proponents of the idea that if you want a scalable software system, there’s no other path, but the microservices one. The idea behind microservices is simple - instead of building a monolithic application, break down your logic into small services that communicate with one another through APIs.
That’s the approach companies like Amazon, Uber, Netflix and other bahamuts use to scale their systems. Sounds simple, but it’s easy to get lost on way there so in this post, I'll outline couple dont’s to consider.
Don’t reason about decoupling from technological perspective
At first, it may be appealing to design your services around what you consider technically viable and logical, but such designs result in unnecessary layers of physical abstractions and lack of encapsulation.
Low cohesion and scattered logic will make it difficult and cost-inefficient to make changes to the system since you’ll have to change multiple services instead of a single one.
A microservice should rather be designed around a piece of business logic that can be modularized in the form of a single, reusable unit. This would allow the develpment team to execute changes more efficiently since all logic of the specific domain will be within a single service.
Don’t share databases between services
Your data is one of the the biggest constraints when it comes to building M/SOA applications and that’s especially true if you are refactoring a monolith and not starting a new project from scratch. It’s not uncommon for developer to cut corners by having multiple services share the same database for the sake of accessing data easily. After all, fetching records directly through the ORM is far easier than building APIs, but this technical debt beast will come back bloodthirsty sooner or later.
A shared database can quickly become a massive dependency, making it extremely difficult to coordinate schema changes and migrations.
Instead of sharing a database, each service should have its own so that business logic, data and communication interfaces are all encapsulated together. This will enforce the development team to stick to the good practice of creating APIs for communication between the services and also allow them to pick the most suitable database for the job.
Don’t use synchronous communication unless needed
Another common pitfall I’ve seen (and did) is using the HTTP protocol for handling ALL communication between services. Service A, awaiting for response from service B automatically means that B is a dependency of service A. The microservices paradigm is all about having independent systems working together so dependencies like that are no bueno. Some people even go further by stating that microservice-based applications that use HTTP are just monoliths.
One needs to carefully decide whether synchronous communication is really needed. To put things in perspective, consider these two scenarios:
1. A user authentication service;
2. An images processing engine for file transformation.
Clearly, with 1, you’d need a response and you’ll need it right away so HTTP is indeed the best way to go, while 2 would work beautifully with a message queue of some sort. A message queue approach would allow your services to work asynchronously and also make sure that no messages are lost.
Here’s a real world example we’ve been using successfully in an e-commerce project:
A team member approves a newly created order for shipping. In order to pass that info to the interested parties, the admin service pushes a message to AWS SNS. SNS replicates that message and passes it to 3 AWS SQS queues, each of which is respectively linked to a Packaging service, Invoice service and an email service.
Another great thing about using queues is that they handle request spikes very well. If a specific service gets under a heavy load, the incoming messages will gracefully wait for the older ones to get processed first.
To be continued...