I’m excited to join the Code4Nord team as a .NET and microservices developer. I’ve spent the last five years working with Microsoft’s Azure Service Bus and Azure Service Fabric. For my first Code4Nord blogpost I wanted to share my positive experience with the microservices architecture and Azure Service Fabric, and the lessons I’ve learned along the way.
This is part of a new series where members of the Code4Nord team draft a blog about the software they’ve built and what they’ve learned.
What are microservices?
Microservices, also known as microservices architecture, is a movement away from the traditional legacy monolithic architecture toward an architectural style in which an application is split into more smaller services — called microservices or sometimes miniservices if they are larger around a certain workflow like checking out on a booking service. Each service has its own clear responsibility.
Advantages of microservices
Microservices are loosely coupled, which means that if one service is not responding, the whole application is still up and running. If these services are not loosely coupled, the application is still behaving like a monolith, even if it’s made up of multiple services.
Since the services are loosely coupled, the services can be developed independently. This means that the developers can work on multiple services at a time without being blocked that they need one service to be ready in order to continue. These services can also be maintained and deployed independently without affecting the other services. Let’s say that you find a bug in one of the microservices, the developers can quickly fix the bug and deploy the patched version without downtime, with the other services continuing to run as expected.
Another advantage is that each service can be scaled independently. This means that if the developers observe that a certain microservice has performance issues because of the load that is done on that specific service, it can easily scale the service, without affecting the other ones that are working as expected.
This saves time and money. It also allows our work as developers to be more creative and independent, building on our strengths and delivering features more quickly and securely.
Microservices vs monolithic architecture
There is no such thing as the perfect architectural style, since both microservices and monoliths have their own advantages and drawbacks. It depends on the application that needs to be developed:
- If the application is not that complex and huge, the monolith architecture fits best. Also, if the cost of the application is very important, this architecture fits best since in most of the cases a monolith architecture is cheaper. A monolith works for companies still using heavily documented Waterfall processes that have them releasing only a few times per year.
- If the application would be complex and huge, with expectations of high performance, then a microservices architecture fits best. Microservices requires a lot of work, which means that the overall cost of the application will grow, but it will be easier to maintain and scale when needed. Microservices are more aligned with agile methodologies DevOps culture, where the individual developers often build, test, and release on their own. With so many pieces fitting together with microservices architecture, you need rapid feedback and the ability to fail back and start again.
Azure Service Fabric
After the analysis phase and after all requirements are carefully processed, if your team decides that microservices architecture fits best, then it’s time to decide what you want to build it with — typically open-source Kubernetes or a combination of Docker and Azure Service Fabric.
Azure Service Fabric is a distributed systems platform for building scalable and reliable microservices, built by Microsoft. Since Service Fabric supports .NET Core framework, you can develop services that can run on Windows or Linux, so you are not limited to a specific operating system.
Azure Service Fabric supports three different types of services:
- stateless service
- stateful service
As the name implies, stateless service is a service that does not have any state. This service type can be used when you need to create a service responsible for a simple task and does not need to keep any state. For example, a service that builds and sends an email does not require to save any state, as it only receives an input and, based on that input, constructs and sends the email to the specified recipients.
A stateful service is a service that is able to save data in the state, which can be restored after a failure. Stateful services excel in terms of reliability and availability. Service Fabric provides two types of reliable data structures: queues and dictionaries, which are persisted and replicated to secondary replicas. This means, if the primary replica is down, the secondary replica is activated, and the service works as expected, since the state is not lost.
For example, a stateful service can be used when you need to develop a service that provides information about train schedules. Since these are not often changed, the data can be stored in the state for a specified time, and subsequent calls will return the same information during that time without the added cost of making another call to an external service.
The actor service is built on top of the stateful service. The difference is that the actor service can have multiple instances and each with its own state. An example where this service type fits best is a service that is processing the items that a user has in a shopping cart. Since there can be multiple users, you can create an actor for each user responsible for processing the cart and keep processing results in the state, in case that the user requests the status.
Instance vs Replica in Azure Service Fabric
An instance of a service is the copy of the service that runs on one of the nodes available in the cluster.
A replica of a service is a copy of the service that runs on one of the nodes available in the cluster. In addition to an instance, the replica maintains a copy of the state of that specific service. A replica can be primary (P) – responsible for performing the actual job – or secondary (S) – receives state updates from the primary one, and is ready to be promoted to primary in case the primary replica is faulty.
In a Service Fabric cluster, you can define multiple nodes, which basically are Virtual Machines that can run the services that are deployed. Each node can be scaled up independently when needed (CPU, memory, disk space, etc.). It is not mandatory that all services to run on all defined nodes, or on the same nodes. For example, you can have a service deployed on all nodes, only on few nodes, or even on one node. The following image is a five-node Service Fabric cluster with all three types of services.
Communication between two services that are targeting different .NET frameworks
Besides HTTP protocol, Service Fabric provides a remoting mechanism by using procedure calls for services. This is called service remoting. This works just fine between services targeting the same .NET frameworks (for example .NET Framework 4.6.1 or .NET Core 2.1) because the same remoting interface is used.
We ran into an issue when we created a new stateful service with .NET Core 2.1, which needs to communicate some data to an old service targeting .NET Framework 4.6.1. An exception with the following message was thrown when accessing the old service: “NamedEndpoint ‘V2Listener’ not found …” Later, we figured out that these two services are using different remoting listener versions, V1 and V2.
An easy solution would be to migrate the old service to .NET Core 2.1. This was already planned later in the future, so it seemed like a good time to make this change. But this old service was also communicating with other services from the same Service Fabric cluster, targeting the same framework, which made migrating to .NET Core not the best solution because it implied modifying also other services that are already in production and working as expected.
We started investigating if it is possible for a single service to use more than one remoting interface because, given the circumstances, this seemed like the only viable solution. After some research and experiments, we found that this is possible. The only thing is to specify on the service interface which remoting version is to be used besides the default one:
Having this attribute set on the interface, the service can be accessed remotely using V2 and V1 remoting listeners. The service targeting .NET Core 2.1 will use the default V2, and the old services will use V1. So, with this change, there was also no need to update other services.
After the project is built, the service manifest of the service is automatically updated and will include both endpoints, as in the following screenshot:
This solution will not always work, this was an exception case, but it could be that there are services targeting frameworks older than .NET Framework 4.6.1, which does not support remoting listener V2. So, if possible, the ideal solution would be to have in the solution services targeting the same .NET Framework, and since Azure Service Fabric supports .NET Core, I would advise to go with the latest version of .NET Core.
What other workarounds have you found to help make Azure and microservices work for your team? Please share this article and add your voice in the comments below!