Book Image

Hands-On Microservices with C# 8 and .NET Core 3 - Third Edition

By : Gaurav Aroraa, Ed Price
Book Image

Hands-On Microservices with C# 8 and .NET Core 3 - Third Edition

By: Gaurav Aroraa, Ed Price

Overview of this book

<p>The microservice architectural style promotes the development of complex applications as a suite of small services based on specific business capabilities. With this book, you'll take a hands-on approach to build microservices and deploy them using ASP .NET Core and Microsoft Azure. </p><p>You'll start by understanding the concept of microservices and their fundamental characteristics. This microservices book will then introduce a real-world app built as a monolith, currently struggling under increased demand and complexity, and guide you in its transition to microservices using the latest features of C# 8 and .NET Core 3. You'll identify service boundaries, split the application into multiple microservices, and define service contracts. You'll also explore how to configure, deploy, and monitor microservices using Docker and Kubernetes, and implement autoscaling in a microservices architecture for enhanced productivity. Once you've got to grips with reactive microservices, you'll discover how keeping your code base simple enables you to focus on what's important rather than on messy asynchronous calls. Finally, you'll delve into various design patterns and best practices for creating enterprise-ready microservice applications. </p><p>By the end of this book, you'll be able to deconstruct a monolith successfully to create well-defined microservices.</p>
Table of Contents (16 chapters)

Understanding the problems with the monolithic architectural style

In this section, we will discuss all the problems with the monolithic .NET stack-based application. In a monolithic application, the core problem is this: scaling monolithic applications is difficult. The resultant application ends up having a very large code base and poses challenges with regard to maintainability, deployment, and modifications. In the coming sections, we will learn about scaling, and then we will move on to deployment challenges by following scaling properties.

The challenges in standardizing a .NET stack

In monolithic application technology, stack dependency stops the introduction of the latest technologies from the outside world. The present stack poses challenges, as a web service itself will suffer from them:

  • Security: There is no way to identify the user via web services due to there being no clear consensus on a strong authentication scheme. Just imagine a banking application sending unencrypted data containing user credentials. All airports, cafes, and public places offering free Wi-Fi could easily become victims of increased identity theft and other cybercrimes.
  • Response time: Though the web services themselves provide some flexibility in the overall architecture, it quickly diminishes because of the long processing time taken by the service itself. So, there is nothing wrong with the web service in this scenario. It is a fact that a monolithic application involves a huge amount of code; complex logic makes the response time of a web service long, and therefore unacceptable.
  • Throughput rate: This is on the higher side, and as a result, hampers subsequent operations. It is not a bad idea for a checkout operation to rely on a call to the inventory web service that has to search for a few million records. However, when the same inventory service feeds the main product searching for the entire portal, it could result in a loss of business. One service call failure out of 10 calls would mean a 10% lower conversion rate for the business.
  • Frequent downtime: As the web services are part of the whole monolithic ecosystem, they are bound to be down and unavailable each time there is an upgrade or an application failure. This means that the presence of any B2B dependency from the outside world on the application's web services would further complicate decision-making, thereby causing downtime. This makes the smaller upgrades of the system look expensive; thus, it further increases the backlog of the pending system upgrades.
  • Technology adoption: In order to adopt or upgrade a technology stack, it would require the whole application to be upgraded, tested, and deployed, since modules are interdependent and the entire code base of the project would be affected. Consider the payment gateway module using a component that requires a compliance-related framework upgrade. The development team has no option but to upgrade the framework itself and carefully go through the entire code base to identify any code breaks preemptively. Of course, this would still not rule out a production crash, but this can easily make even the best of architects and managers lose sleep.
  • Availability: A percentage of time during which a service is operating.
  • Response time: The time a service takes to respond.
  • Throughput: The rate of processing requests.

Fault tolerance

Monolithic applications have high module interdependency, as they are tightly coupled. The different modules utilize functionality in such an intra-module manner that even a single module failure brings the system down, due to its cascading effect. We all know that a user not getting results for a product search would be far less severe than the entire system coming down to its knees.

Decoupling using web services has been traditionally attempted at the architecture level. For database-level strategies, ACID has been relied upon for a long time. Let's examine both of these points further:

  • Web services: In the current monolithic application, the customer experience is degraded due to using web services. Even as a customer tries to place an order, reasons, such as the long response time of web services or even a complete failure of the service itself, result in a failure to place the order successfully. Not even a single failure is acceptable, as users tend to remember their last experience and assume a possible repeat. Not only does this result in the loss of possible sales, but also the loss of future business prospects. Web service failures can cause a cascading failure in the system that relies on them.
  • ACID: ACID is the acronym for atomicity, consistency, isolation, and durability; it's an important concept in databases. It is in place, but whether it's a boon or a bane is to be judged by the sum total of the combined performance. It takes care of failures at the database level, and there is no doubt that it does provide some insurance against database errors that creep in. At the same time, every ACID operation hampers/delays operations by other components/modules. The point at which it causes more harm than benefit needs to be judged very carefully.

The monolithic application that will be transitioned to microservices has various challenges related to security, response time, scalability, and moreover, its modules are highly dependent on each other. These are all big challenges while trying to deal with a standard application, but especially a monolithic application, which is supposed to be used for a high volume of users. The main and important point here for our monolithic application is scalability, which will be discussed in the next section.

The scaling property of a system

Factors, such as the availability of different means of communication, easy access to information, and open-world markets, are resulting in businesses growing rapidly and diversifying at the same time. With this rapid growth, there is an ever-increasing need to accommodate an increasing client base. Scaling is one of the biggest challenges that any business faces while trying to cater to an increased user base.

Scalability describes the capability of a system/program to handle an increasing workload. In other words, scalability refers to the ability of a system/program to scale.

Before starting the next section, let's discuss scaling in detail, as this will be an integral part of our exercise, as we work on transitioning from monolithic architecture to microservices.

There are two main strategies or types of scalability:

  1. Vertical scaling or scale-up
  2. Horizontal scaling or scale-out

We can scale our application by adopting one of these types of strategies. Let's discuss more about these two types of scaling and see how we can scale our application.

Vertical scaling or scale-up

In vertical scaling, we analyze our existing application to find the parts of the modules that cause the application to slow down, due to a longer execution time. Making the code more efficient could be one strategy, so that less memory is consumed. This exercise of reducing memory consumption could be for a specific module or the whole application. On the other hand, due to obvious challenges involved with this strategy, instead of changing the application, we could add more resources to our existing IT infrastructure, such as upgrading the RAM or adding more disk drives. Both these paths in vertical scaling have a limit to the extent to which they can be beneficial. After a specific point in time, the resulting benefit will plateau. It is important to keep in mind that this kind of scaling requires downtime.

Horizontal scaling or scale-out

In horizontal scaling, we dig deep into modules that show a higher impact on the overall performance for factors such as high concurrency; this will enable our application to serve our increased user base, which is now reaching the million mark. We also implement load balancing to process a greater amount of work. The option of adding more servers to the cluster does not require downtime, which is a definite advantage. Each case is different, so whether the additional costs of power, licenses, and cooling are worthwhile, and up to what point, will be evaluated on a case-by-case basis. 

Scaling will be covered in detail in Chapter 8, Scaling Microservices with Azure.

Deployment challenges

The current application also has deployment challenges. It was designed as a monolithic application, and any change in the order module would require the entire application to be deployed again. This is time-consuming, and the whole cycle would have to be repeated with every change, meaning that this could be a frequent cycle. Scaling could only be a distant dream in such a scenario.

As discussed with regard to scaling current applications that have deployment challenges that require us to deploy the entire assembly, the modules are interdependent, and this is a single assembly application of .NET. The deployment of the entire application in one go also makes it mandatory to test the entire functionality of our application. The impact of such an exercise would be huge:

  • High-risk deployment: Deploying an entire solution or application in one go poses a high risk, as all modules would be deployed even for a single change in one of the modules.
  • Longer testing time: As we have to deploy the complete application, we will have to test the functionality of the entire application. We can't go live without testing. Due to higher interdependency, the change might cause a problem in some other module.
  • Unplanned downtime: Complete production deployment needs code to be fully tested, and hence we need to schedule our production deployment. This is a time-consuming task that results in long downtime. While it is planned downtime, during this time, both business and customers will be affected due to the unavailability of the system; this could cause revenue loss to the business.
  • Production bugs: A bug-free deployment would be the dream for any project manager. However, this is far from reality and every team dreads this possibility of a buggy deployment. Monolithic applications are no different from this scenario, and the resolution of production bugs is easier said than done. The situation can only become more complex with a previous bug remaining unresolved.

Organizational alignment

In a monolithic application, having a large code base is not the only challenge that you'll face. Having a large team to handle such a code base is one more problem that will affect the growth of the business and application.

To align an organization, the most concerning factor is the goal of the team. It is very important that a team goal should be the same for all team members:

  • The same goal: In a team, all the team members have the same goal, which is timely and bug-free delivery at the end of each day. However, having a large code base in the current application means that the monolithic architectural style will not be comfortable territory for the team members. With team members being interdependent due to the interdependent code and associated deliverables, the same effect that is experienced in the code is present in the development team as well. Here, everyone is just scrambling and struggling to get the job done. The question of helping each other out or trying something new does not arise. In short, the team is not a self-organizing one.
Roy Osherove defined three stages of a team as: survival phase—no time to learn, learning phase—learning to solve your own problems, and self-organizing phase—facilitate, and experiment.
  • A different perspective: The development team takes too much time for deliverables for reasons such as feature enhancement, bug fixes, or module interdependency, preventing easy development. The QA team is dependent upon the development team, and the development team has its own problems. The QA team is stuck once developers start working on bugs, fixes, or feature enhancements. There is no separate environment or build available for a QA team to proceed with their testing. This delay hampers the overall delivery, and customers or end users will not get the new features or fixes on time.

Modularity

In respect to our monolithic application where we may have an Order module, a change in the Order module affects the Stock module, and so on. It is the absence of modularity that results in such a condition.

This also means that we can't reuse the functionality of a module within another module. The code is not decomposed into structured pieces that could be reused to save time and effort. There is no segregation within the code modules, and hence no common code is available.

The business is growing and its customers are growing in leaps and bounds. New or existing customers from different regions have different preferences when it comes to the use of the application. Some like to visit the website, but others prefer to use mobile apps. The system is structured in a way that means that we can't share the components across a website and a mobile app. This makes introducing a mobile/device app for the business a challenging task. The business is therefore affected as companies lose out owing to customers preferring mobile apps.

The difficulty is in replacing the application components that are using third-party libraries, an external system such as payment gateways, and an external order-tracking system. It is a tedious job to replace the old components in the currently styled monolithic architectural application. For example, if we consider upgrading the library of our module that is consuming an external order-tracking system, then the whole change would prove to be very difficult. Furthermore, it would be an intricate task to replace our payment gateway with another one.

In any of the previous scenarios, whenever we upgraded the components, we upgraded everything within the application, which called for the complete testing of the system and required a lot of downtime. Apart from this, the upgrade would possibly result in production bugs, which would require repeating the whole cycle of development, testing, and deployment.

Big database

Our current application has a mammoth database, containing a single schema with plenty of indexes. This structure poses a challenging job when it comes to fine-tuning performance:

  • Single schema: All the entities in the database are clubbed under a single schema named dbo. This again hampers the business, owing to the confusion with the single schema regarding various tables that belong to different modules. For example, customer and supplier tables belong to the same schema, that is, dbo.
  • Numerous stored procedures: Currently, the database has a large number of stored procedures, which also contain a sizeable chunk of the business logic. Some of the calculations are being performed within the stored procedures. As a result, these stored procedures prove to be a baffling task to tend to when it comes to optimizing them or breaking them down into smaller units.

Whenever deployment is planned, the team will have to look closely at every database change. This, again, is a time-consuming exercise that will often turn out to be more complex than the build and deployment exercise itself.

A big database has its own limitations. In our monolithic application, we have a single schema database. This has a lot of stored procedure and functions; all this has an impact on the performance of the database.

In the coming section, we will discuss various solutions and other approaches to overcome these problems. But before that, we need to know the prerequisites of microservices before digging into this architectural style.