In this guide we give you a complete Microservices Architecture Example. Microservices are a great framework to create complex applications in a way that is easy to scale and maintain. The concept is simple, develop many small services rather than one big monolith that does everything. However, you may be stuck in how to apply this concept to your app. In this guide, we have some microservices architecture example to help you understand how a big app can be broken down into microservices.
Like with any architectural framework, there is no absolute right way of defining a microservices architecture. Yet, there are some good practices to follow. To put it another way, what parts of your app are grouped together into a single microservices – as opposed as parts divided across multiple microservices – is a design choice. You need to develop enough critical thinking to make good choices.
For this microservices architecture example, we will see how to design a real-time chat application for instant messaging. In order to get the most out of this article, some basic knowledge of Docker is reccomended, but not required.
Microservices Architecture Example: How to Design
User Journey as a Starting Point
Before we start dividing our large app into many microservices, it is important that we understand how the app works from the perspective of the user. This is important, not only for the microservices architecture example, but also for your general software development skills.
You may do this defining user stories or features, but all in all you need to define what the users expects to do in the application. For a chat application, we might think about the following features:
- Send messages to contacts, and messages are delivered instantly
- View when a message has been sent, received, and read
- Send and receive multimedia messages like images and videos, they get delivered even if the recipient is temporarily offline (when he connects back)
- Group chat, with the same features as the peer-to-peer chats
Once you have defined features you can prioritize them and classify them. The most used classification, which we use also in this microservices architecture example, is core and non-core features. Core features are the ones that are key to the user experience in the app, they make the app itself worth using. All the other are non-core features.
In our microservices architecture example for a chat, all listed features are core except the “view when a message has been sent, received, and read”. Without this, the user will be annoyed for sure, but in its core the app will continue to work.
Why do we need to consider core and non-core features? Because, when designing, we may need to think how the app can handle large loads and which features may be sacrificed.
Consider Where Data Is
When designing a microservice application, consider where the data is or where you want it to be. In fact, this is what we will do in the course of this microservices architecture example.
This means thinking about where and how you want to archive information, such as messages, videos, images, metadata, and more – and segment your application into microservices based on this. You want the same piece of data, such as the same table or same database, to be accessed by a single microservice. If other microservices needs to modify that data, they should engage the microservice owning that piece of data.
The importance of this concept comes from the fact that it ensures consistency in the app, avoiding data gets manipulated. However, this is not the only benefit. The other key advantage of this approach is that it benefits speed greatly, because you design your app in a way that forces microservice to work with data that is close to them only.
Components of our Microservices Architecture Example: A Chat Application
Gateway Microservices Architecture Example
The first design concept in microservices architecture is the idea of gateway. Your application should host many users, but since it will have many separate microservices, where will the users connect? On the gateway.
The gateway is a microservice specifically designed to host connections from the users and relay them back to the inner layers of the application, without doing much processing or intelligence. The advantage of having this component is that we have something that stays between the user and our application. Because of this, we can use different communication protocols on the user-gateway side and on the gateway-app side. This allows us to use more performing protocols on the inside, as we are not constrained by protocols that need to run over the Internet while also being secured.
Additionally, this removes the complexity of talking with the users from other services. This is particularly important in a chat application, where you will have an always-on connection to the user so that you can send messages immediately. Having that connection means leaving a TCP socket open, which consumes RAM memory. Even if a single socket does not consume much memory, having many users means you will have to dedicate a lot of memory to this. With the gateway, you can have a component specifically dedicated for this task.
When talking with the user, you generally use either HTTP or Web Sockets (WSS). HTTP is a transactional communication, initiated by the user and expecting an immediate response, after which the connection is closed. This is not ideal for a chat app, so instead we prefer WSS, where a channel between the user and the server is always on, so that both can send messages as soon as they have them (ideal when receiving messages, that is, when the server sends messages to the user).
Router, to Scale the App
The key to a microservices architecture example is that you don’t have just one instance of each microservice. You might have more. In fact, you should have more so that you can be more resilient in case of fault and so that you can better serve users across different geographies – having servers close to the user.
In our microservices architecture example of the app, this translates into the fact that not all users will be connected to the same gateway instance. Instead, we will have multiple gateways, and each gateway will host only a portion of the users, but not all. This introduces a problem: how do we know on which gateway a user is? To solve this, we need another microservice.
We call this microservice the “Router” because it routes request to the proper gateway. Of course, you might call it whatever you want. In a nutshell, it contains a mapping between user and gateway. This can be done with a simple key-value pair, where the user ID is the key and the value is the gateway. When any other service needs to communicate with a user, it can just ask the router which gateway to engage.
Even in peer-to-peer communication, a gateway may need to engage another gateway to send a message from a user to another. It does that by engaging the Router and identifying the destination gateway.
In a more complex system, we might have multitier routers. That is, you may have a geographic area containing several gateways with a router that knows the information about those gateways. However, you also have other geographic areas with other routers and multiple gateways in each. If the router does not know the gateway of a user (because it is not in its location), he can ask the routers of the other locations. In this microservices architecture example, we will keep things simple and do not consider this design.
Authentication
Also in this microservices architecture example, security is key. Before any request is relayed to the internal application, we need to ensure it is legitimate. This is often performed by an authentication mechanism, which is generally twofold.
The gateway component can verify if a request is legitimate or not without having to engage any external component – this saves ton of time. To do this, we need to implement asynchronous encryption. The client is provided with a public key, and by signing requests with that key we know the request is legitimate, because we are the only one who can open it. This is a gross oversimplification, but the key concept is that we need someone to give the user encryption keys. This is what the authentication server does.
A similar concept is implemented with the OAuth standard, the one you se for Single Sign On or when you “Log in with Google” on any third-party app.
Message Service
Sending messages to users is not the whole deal. What if the receiving user is not connected to the gateway when we want to send here a message? Our app needs a way to temporarily store messages until they can be delivered. We need to consider this in our microservices architecture example.
To do this, we need to insert a message service that acts as a queue. Incoming messages from a gateway are sent to this service which stores them and then tries to engage a gateway to send the message to a user. As it stores messages, it can produce a unique ID for each message and keep track of their status (not received, received, read). This can be implemented with a single table where each row represents a message, and each status change is a column containing a timestamp, null if the message is yet to reach that state.
Additionally, we should consider extra fields such as the type of message to understand if it is just plain text or if it contains images or other type of media. However, we should not use this service to store images and multimedia, because it will be clogged rapidly.
Microservices Architecture Example: Multimedia Service
Continuing with our microservices architecture example, we reach the multimedia service. This service has a simple goal: hold multimedia file (images, videos) that are sent alongside messages until we are able to deliver them to users.
In this microservices architecture example we kept this separated from the message service because the message service needs to be queried fast. On the other hand, we can retrieve multimedia directly by ID, and so we can use a cheaper storage option in a separate microservice. Most likely, this service will use a file-based storage system (ad not a database) because we don’t need the added features of a database.
When the user sends a message, the message service receiving that message will save any multimedia by engaging the multimedia service and providing the list of attachment to associated with a given message ID. When retrieving a message the process is the same, if the message service has mapped that a message contains attachment, it will fetch them from the multimedia service.
Group Chat: A Key Feature of our Microservices Architecture Example
Group chat is probably the most complex and yet critical feature of our microservices architecture example. The idea is simple: you are part of a group, and you send a message to the group so that all other group members will receive it. This is easy to say but it can be complex to implement. The challenge is that we have to de-multiplex the message, that is, send the message to all group participants. And, of course, we need to keep track of the delivery to each.
Fortunately, we can leverage our existing infrastructure created through all other services. In the end, a group message is like sending the same message to a given number of people in peer-to-peer fashion, all at the same time. The group service can do just that. If a message is received for a destination which is a group ID, rather than a peer ID, it is sent to the group service and not to the message service.
The group service knows all the participants of each group because it has a group-user mapping in a table. As a result, it can find which user belongs to the target group, and then issue a request to the message service for each user to send the message. In other words, the group service uses the message service to deliver the message to each user individually.
There are two challenges for this, which can we overcome. The first is that we need to avoid duplicating attachments, and have one for each message sent to a group, not for every copy of it. We can do this by adding an extra ID to the message table like a simple Boolean flag “copy” which indicates if a message is a copy and as such attachments should not be duplicated, referring to the original.
The second challenge is that a single group message can overwhelm the message service if there are too many users. Most apps solve this by limiting the number of participants you can have in a single group.
Read Notification Service
Our final service in our microservices architecture example is the read notification service (that also manages other states). This is the service that ensures our app knows when a message has been received by the user, and when it has been actually opened and read.
On the user’s mobile, when a message is delivered the app will send an automatic acknowledgement to the gateway. This type of message is routed directly to the read notification service, which updates the table on the messages service. Then, it engages the message service to send a message (of type notification) to the user that sent the message, so that he knows the message was received or read.
We need to do this because otherwise only the data in the message service would be updated, but the user won’t know about it unless it pulls the information out. Since our microservices architecture example is mainly based on a push system, rather than pulling information, this is not a viable option.
Microservices Architecture Example in Summary
All in all, the following drawing summarizes the microservices architecture example by listing all the components and showing how they interact with one another.
If you want to learn more about software architecture, continue by reading this Microservices checklist to see if you get it right.