A typical monolithic server contains all of the codes implemented for all features for our application inside one codebase , and this server interact with a single database.
A microservice contains the codes needed for just one feature of our application.
Each service is self-contained and standalone, it interacts with its own database.
If one service crashes for some reason, other services are not impacted and still up and running, so a portion of our application is still working fine.
The Golden Rule : Database-per-Service
- Each service has its own database
- One service never reach into other service’s database
By applying the golden rule of Database-per-Service, the microservices architecture gains the advantages such as:
- Each service is running independently of other services, so we can significantly increase the up time of our application.
- It’s a lot easier to scale a database dedicated to certain service which need extra capacity or throughput, instead of scaling a big common database served all services, which could be a challenging task.
- We can choose different type of database, e.g. SQL or noSQL, to best serve for different service’s functionality.
Communication between services
All sounds good, but here comes the question : how are we going to share data between services ? After all, our application is supposed to being pieced together by each microservice.
Take the example of a very simple library application:
- Service A: Implement feature to add a user
- Service B: Implement feature to add a book
- Service C: Implement feature for a user to borrow a book
As you can imagine, in order for service C to work, we need Users and Books data coming from Database A and Database B respectively. However, per Golden Rule #2: One service never reach into other service’s database, so we are going to introduce two strategies to communicate between services.
Sync communications — direct request
In sync mode, services communicate with each other by direct request.
So in our little library application, when Service C receive a request from a user to borrow a book, what Service C can do is :
- Send queryUser request to Service A, get result and store userId in its own database.
- Send queryBook request to Service B, get result and store bookId in its own database.
So now Service C is able to map user and book data to display the borrowing history for a given userId.
However, there are some downsides of sync mode :
- It introduces a dependency between services, which is the thing we always try to avoid in microservice architecture.
- Th entire request performance depends upon each inter-services communication.
- We may step into a complicated web of inter-services requests. For example, when Service C calls Service A, behind the scene Service A may calls Service K and then Service K may calls another Service, and so on.
Async communications — event broker
This is also called event-based communication.
In this mode, there is an event broker in between to manage inter-services communication :
- When Service A add a user into its database, simultaneously it emit an userCreated event to the broker. The broker will then forward the event to Service C, for it to insert the userId into its own database.
- The same flow applies to Service B.
- So the database C will have a portion of replication data from Database A and B with zero inter-services dependency.
- And Service C’s performance can be best optimized since it doesn’t need to wait for the response from other services in order to complete its functionality.