Breaking into small teams for large scaling power
Monoliths vs. Microservices
The best way to understand the need for microservices is to compare them to their counterpart, monoliths. Smaller applications that don’t have much concern for scaling might be content with a monolithic system as they have a single codebase, process, database, and tech stack. This makes it simple and easy for anyone to jump from working on one part of the project to another.
While there’s nothing wrong with this approach, there are some disadvantages. For starters, deployment gets risky as one bug in your program could cause the whole thing to break. Since monoliths share one database, added features could require changes in many areas. You are also married to the technology that the application was built on which might leave you stuck with legacy code. If these things concern you, consider the microservices architecture.
By contrast, microservices help us to scale applications seamlessly and allow for more flexibility. Instead of having a single shared service, each feature is treated as a service with its own source code and database. A microservice can even choose to use different technologies than other microservices within the same application. This is a huge advantage because it allows us to utilize the best tools for a specific job.
To relieve the stress of features that are tightly coupled and co-dependent, microservices offer an autonomous approach. Each service’s team gets full responsibility for the business logic, API, presentation, and data storage for a specific part of the application. This agile-friendly model allows for rapid adaptability.
Once a service has been updated, they can deploy their changes without worrying about affecting other areas of the application. This eliminates downtime during deployment and reduces the anxiety of integration issues.
It’s crucial to note that individual services are meant to be independent and should never reach into another service’s database. However, sometimes we may need some shared information. A common example is when preparing a shopping cart. We would likely need to know the current user from an authentication service and the products added to the cart from the service handling product information. Luckily, there a few ways that we can access the information needed without violating the autonomous structure.
Communication Between Services
Sync communication is a when a direct request is made from one service to another. Instead of reaching into another service’s database, Service A can make a request to Service B. Assuming that Service B is set up to subscribe to such requests, it responds by retrieving the data needed from its database and returning it back to Service A. This method is simple and easy to understand. It also means that Services A may not even need a database at all if it only needs information from Service B’s database.
A less simplistic but potentially more effective way for services to speak to each other is through events. This is known as async communication. It involves something referred to as an event bus that is responsible for sending and receiving requests. You can think of it as a mediator. Service A asks the event bus to get some information from Service B. The event bus asks Service B for the information. Service B searches for that information in its data base, then gives it back to the event bus. Finally, the event bus gives the data to Service A. The downside is that the event bus can become a single point of failure. If something goes awry during this back and forth conversation, it might be harder to pin-point where things went wrong.
Both of these methods are dependent on another service and its database. They are also only capable of being as fast as the slowest request and could lead to a web of requests. If Service C requests some data from Service A and Service A needs to get some data from Service D to complete that request and Service D needs some data from Service B to complete that request… you can see how this can get messy quickly.
Async Communication Revisited
Another way to use async communication is to introduce a database that contains tables for the information needed from other services.
Going back to the shopping cart example, you could create a database for the shopping cart service that contains a user and product table. Each table would only need the information that the shopping cart service needs to know about. Maybe we only need the user’s id and the product’s id. We can set up some actions in the user service to send the user’s id to the event bus to tell the shopping cart to update its database when new user is authenticated. We can have the product service do the same when a user adds a product to their shopping cart.
With this implementation, the microservices are no longer dependent on each other. In addition to eliminating co-dependency, our shopping cart service would run much faster this way. Keep in mind that this would cause data duplication and would take a little more programming. However, it’s a great option for maximum autonomy and quick features.
Although there are some challenges to be aware of when incorporating microservices, it’s worth looking into if you are building applications that you need to scale. Working within this archetiture allows the development team of big applications to break off into smaller more focused teams that have more flexibility and the capability to scale as much as necessary.
As always, I encourage you to dive deeper into this idea and try it out for yourself. There are plenty of resources out there. Check out some of the links I’ve listed below to get started!