3

Microservices Architecture with Dapr

In this chapter, I intend to discuss the relevance of microservices architectures for building modern applications, as well as exploring how Dapr, as a runtime, can make this cultural shift way easier to adopt to achieve the many advantages that come with it. Learning why and how microservices are beneficial will help you make better use of Dapr, too.

In this chapter, we’re going to cover the following main topics:

  • Introducing our sample solution
  • Discovering microservices
  • Adopting microservices patterns
  • Building an e-commerce architecture
  • Building microservices with Dapr

We’ll start by introducing the sample solution we will build over the course of this book.

Introducing our sample solution

In order to navigate the concepts of Dapr throughout this book, we could use the help of a common theme to guide us. I thought that a fictional scenario, with an architecture that we will implement together, might be a good approach for the samples.

Therefore, let’s introduce the architecture of Biscotti Brutti Ma Buoni. In our quest to explore Dapr, it’s time to move away from the Hello-World kind of samples and shift to a hypothetical e-commerce scenario, which will be presented from different perspectives to illustrate each of Dapr’s capabilities over the course of the following chapters.

The architecture of the fictional e-commerce site Biscotti Brutti Ma Buoni (which means ugly but good cookies in Italian) is further discussed from a microservices perspective in Appendix, Microservices Architecture with Dapr.

In a nutshell, the Biscotti Brutti Ma Buoni website sells cookies to consumers, offering the ability to customize them. Cookies are manufactured all day; therefore, keeping track of their availability is important. Hopefully, this scenario will help us learn new topics from a practical standpoint and will make it easier to understand and implement them.

Let’s start by exploring the basic concepts of designing microservice architecture.

Discovering microservices

There is an endless collection of books, papers, and blog posts that excellently describe and analyze microservice-style architecture. The objective of this chapter is to present you with the advantages and challenges of using a microservices architecture and to find out how Dapr can help us create new applications based on it.

The nemesis of a microservices architecture is the monolith. No one would ever admit they built or are still working on one, but most of us in the development industry have spent many years working on monolith applications. In a monolith, as the name implies, all the business capabilities or features are condensed in a single application, probably layered between the UI, server, and database, but nevertheless not designed in a modular or distributed fashion.

In a microservice architecture, the services that are designed to support business capabilities are most likely to communicate with open protocols such as HTTP and gRPC. Microservices are built and released via automation, can be independently deployed, and each microservice’s team can adopt the language and technology stack that best fits their needs.

As microservices are an evolution of the Service-Oriented Architecture (SOA), let me briefly summarize the overall architectural concepts we are dealing with in the following sections:

  • Service
  • Autonomy
  • Automated deployment
  • Bounded context
  • Loose coupling
  • Event-driven architecture
  • Observability
  • Sustainability

We will look at these individually in the following sections. Let’s start by exploring the core concept of a service.

Service

A service is a logical representation of a process that’s capable of fulfilling a business activity: selling a product to a potential customer, supporting a customer in fixing an issue with the product, and so on.

A service exposes an application programming interface (API) that regulates how interaction with the service can occur.

It is very likely that the API will be defined as a REST API over HTTP, as most of the developer audience would expect in today’s world, but this concept is not restricted to a specific implementation detail. As an example, gRPC is also a popular choice for exposing a service.

From the client’s perspective, there is no interest in being, or needing to be, aware of the internal mechanisms of the service. What matters to the client is the API contract, its stability over time, and the service-level agreement (SLA), which indicates the objectives of availability and performance the service promises to uphold, as well as the reparations, in case it can’t keep those promises.

Autonomy

A microservice should be autonomous in reaching its objective, as declared via the API, regardless of its dependencies toward other services. Autonomy applies to operations and evolution as well.

To gain more autonomy, the service should limit the need to coordinate with dependent services: the more dependencies a service has, the more difficult it will be to respect the desired availability and to evolve, as changes to other services will impact your service over time.

Let’s look at an example: your application has two microservices, each relying on a common portion of code. As you identify this common ground, a case for a new microservice is made by separating the common portion of code into a new service. Does this make any sense? Not from my perspective: instead, consider refactoring the common code as a library and distributing it via NuGet, the package manager, so that the two microservices are free to control which version they use and when to update it.

Is this common portion of code the starting point of a new business capability that will grow over time? This would shed a different light on the scenario, which is more favorable for a third microservice that will introduce more coordination.

There should be a very good reason to add coordination to a dependent service, since having others depend on your microservice is a responsibility not to be taken lightly.

Automated deployment

By introducing microservices, you will tend to have more independently deployable units of code (which I see as a benefit) for several reasons. For example, different teams may be working on separate projects. Whatever the reason, you will need to adopt continuous deployment practices and tools to perform these operations in an automated fashion. Otherwise, if performed manually, the build and deployment process of microservices could be much more time-consuming than those of a monolith application.

Bounded context

A bounded context is a pattern that originated in the domain-driven design space. The unified model of an enterprise/large application tends to grow significantly large, so it becomes intrinsically difficult to manage over time. Splitting the complex model into smaller, more independent integrated models, each with a coherent purpose, can be a solution.

As an example, an e-commerce application might be difficult to manage as a single model. Can we agree that separating the context of sales from after-sales (support, products return, complaints handling, and so on) might be a more simplified approach?

The micro in microservices suggests the idea of small, but according to which measure? Is it the number of classes? The portable executable size as a file? The overall number of libraries?

In my opinion, the word micro does not help clarify the concept, while a bounded context is far more useful to indicate that a microservice should take care of a single part of the application. The reduced size of a microservice compared to a monolith is often a byproduct of this.

Loose coupling

Directly from the SOA space, the interactions between two services should be as loosely coupled as possible: if you reach this goal, you could deploy a service without impacting others.

This goal can be reached by implementing patterns that favor an indirect interaction (publish-subscribe) over a direct request-reply. While it’s not easy to keep services loosely coupled, this might prove an insurmountable task, so further analysis should verify whether the services (or microservices) in scope are meant to be kept separate or should be combined instead.

If this seems similar to the concept of autonomy, you are right. As a matter of fact, the looser you couple two microservices, the more autonomous they are.

Event-driven architecture

Event-driven is an architectural pattern in which events, such as changes in the state or facts, are produced for others, unknown to the producer, to be noticed and eventually consumed. For example, a new sale and the quantity of a product reaching 0 (which means it is out of stock) are two types of events.

With this pattern, a service is triggered not by a direct invocation, but by a message representing the event being detected. Inherently, both parties – the producer and the consumer – operate at a scale and speed that fits their own objectives, not interfering with each other.

To build an event-driven architecture, you will likely have to leverage a message bus to handle the complexity of exchanging messages.

Observability

A microservice architecture brings many more moving parts into play since they’re deployed more frequently, probably in a greater number of instances, over many hosts. Therefore, it is paramount to gain full visibility of the status and behavior of each microservice instance, each point of coordination, and each component of the underlying platform/runtime, making it easy for both the operators’ and developers’ audiences to gain easily readable and actionable intelligence from this vast group of information.

In contrast to our usual experiences, counting on accessing the logs from all the nodes will not help much; having a synthetic probe result letting you know the microservice is up is useful, but it won’t tell you that the microservice is demonstrating its ability to consistently perform its business capability over time. What you want to achieve is to have full traceability for each request as it crosses all the boundaries between microservices and the underlying stores and services.

Sustainability

Given the current historical context, where we are facing a climate crisis, there is much interest in sustainable operations, and lately, I have witnessed the growth of a community for sustainable software engineering.

Sustainable software engineering is a new discipline that combines climate science with software architecture – the way electricity is generated with how data centers and hardware are designed. You can learn more about this at https://principles.green.

The overall picture is that developers, software analysts, and even application owners should consider the carbon impact of their software, and all the possible ways to reduce it or at least adapt it to the conditions of the underlying energy offering. A perfect example of this discipline is the low carbon Kubernetes scheduler (white paper available at http://ceur-ws.org/Vol-2382/ICT4S2019_paper_28.pdf), which helps developers move their application’s containers according to the lowest carbon impact of the current location. I think Dapr can also influence this movement. This is because it makes microservice applications much easier to write and handle – adding the sustainability option should not be a huge effort – and besides, architecting a piece of software to not waste resources should be in our best interests anyway.

Since we share a common understanding of the core concepts of a microservice architecture, let’s look at the benefits its adoption provides.

Adopting microservices patterns

What benefits does a microservice architecture bring to an application? Why should we move from monoliths to microservices?

Considering the concepts described so far, I think the following might be a good list of improvements you can achieve by adopting microservice architecture:

  • Evolution: It is common to have several smaller teams, each working with the tools, languages, and platforms that best match their objectives. By defining a simpler and smaller (bounded) context of the application, there are far better chances that its evolution and growth will be faster, more reliable, and have greater business impact.
  • Flexibility: By becoming as autonomous as possible, and by interacting with others in a loosely coupled manner, the microservice will gain many opportunities that would otherwise be impossible for a monolith architecture: changing the persistence layer, adopting a new library, or even switching to a different technology stack now becomes a possibility. As long as there is no impact on other microservices, each team is free to choose their own innovation path.
  • Reliability: Code quality will not improve by itself just because it is in a microservice but having a smaller scope and being able to deploy it individually makes it easier to test it in an automated fashion, and it will have increased reliability as a byproduct.
  • Scale: Microservices enable you to deploy as many instances as you need, dynamically increasing and reducing them (scale out and in), and even independently choosing the right resource, such as a specific database or a virtual machine type (scale up and down), equipped with a specific CPU your code can leverage. These newly acquired abilities enable your application to achieve a much higher throughput rate, more efficiently than with other approaches.

Important note

With merit to scale, microservices are often cited as the default architecture for applications landing on Kubernetes because of their ability to scale and coordinate resources.

Kubernetes is a good choice for hosting a microservice architecture, as is a Function as a Service (FaaS) offering such as Azure Functions, to some degree.

In FaaS, the focus is only on the application code, with a greater abstraction from the infrastructure layer than there is for a deployment based on containers.

The main thing to note is that you should keep the subjects separate; Kubernetes can sometimes be the proper hosting platform for monolith applications that have been condensed into a single Pod.

All the benefits a microservice architecture brings to the application translate into advantages for your product against the competition.

This book only focuses on .NET, which we all deeply love, but it will probably not be the only language that’s used by your application. To support this common scenario, Dapr provides HTTP and gRPC APIs, and SDKs for most diffused languages. If your application needs to leverage machine learning, whether for existing models or to train new ones, these are disciplines in which the audience is more comfortable with Python, just to start with the obvious. Therefore, being able to fully leverage the job market, because your architecture paradigm allows you to choose the best technology for the task, can be a strategic advantage that you should not underestimate.

It is a good exercise to ask ourselves the following question: should we always adopt microservices?

Microservice architecture is the distilled outcome of endless improvements in the way software is designed and built, so it’s up to us, architects and developers, to adopt the latest and best in our daily jobs.

Nevertheless, I think every technology should only be adopted if it brings a net benefit.

If there is only a team (containing a few members) working on the application, splitting a monolith into microservices will not make it easier for that team to cope with the burden of more projects, more complex dependencies, and separate CD pipelines, just to name a few.

Regarding infinite resources, a monolith application may be able to scale to meet your goal by adding new instances of your host of choice, such as a virtual machine. As long as more powerful virtual machines become available, your monolith application will always have room for growth.

The scale for which microservices emerged as a winning architecture may be way beyond your needs. On the other hand, monoliths were not built to deal with the complexity of managing updates and coordination over too many hosts and instances.

If the team does not reach proper maturity on automated testing and CI/CD (namely, the overall DevOps culture), it might be a significant challenge to adopt microservices and handle all the aforementioned issues at the same time. Nevertheless, if time and resources are in your favor, you can try to kill two birds with one stone.

Finally, it may become difficult to identify clear context boundaries in your applications, maybe because they fulfill a very specific task and not much more. In these cases, there may not be a clear benefit of splitting it further, needlessly, into microservices. Nevertheless, adopting the same patterns and aiming for the same goals of microservice architecture may help you in the long run.

Let’s apply the concept discussed so far to our sample architecture.

Building an e-commerce architecture

The objective of this book is to illustrate how Dapr can support your job, as developers and architects, in creating an application that adopts a microservice architecture. I think it has helped to discuss a hypothetical scenario over the course of this book to see how each feature can be introduced. I considered a scenario we will all experience once or more in our daily lives, mostly as consumers (sometimes as creators), when building an e-commerce site.

An e-commerce site must support many capabilities: exposing a catalog and making it browsable, having a price model that can be influenced by a promotion engine, managing a shopping cart, collecting customer information, processing orders, fulfilling the orders, and coordinating the shipments, just to name a few.

Throughout this book, we will compose a sample solution for the fictional e-commerce site named Biscotti Brutti Ma Buoni. This site specializes in baking and customizing cookies. Most of its customers don’t buy the plain cookies packages but instead order customized versions for special occasions. Biscotti Brutti Ma Buoni is widely known for its ability to digitally print complex, multilayer scenes on a cookie.

Starting with this fictional example of e-commerce, we will explore the context of its components, all of which we want to include as microservices in our solution.

Bounded contexts

As we couldn’t interview the business and domain expert of the fictional e-commerce site, I briefly ask for your suspension of disbelief, as if you were watching a play. Let’s agree that we identified several bounded contexts:

Figure 3.1 – The bounded contexts

Figure 3.1 – The bounded contexts

Let’s focus our attention on a few of the contexts shown in the preceding diagram:

  • Sales: Most of the items sold are made on demand and may need to be customized. Therefore, sales orders only tend to be accepted if the manufacturing process is active. The stock is relevant during the manufacturing process. If for any reason a product line is temporarily unavailable because a particular ingredient is missing or a piece of equipment breaks, the order might be put on hold or canceled, and the item would become unavailable in the front store.
  • Manufacturing: Luckily for Biscotti Brutti Ma Buoni, most of the items on sale are made from the same dough, so planning for the main ingredients is simple. Customization is more complex as colorants and other ingredients for icing are much more diverse. The request to manufacture the final item is guided by the actual sale, but the provisioning of ingredients is driven by a machine learning model that takes seasonal and short-term sales into account. Manufacturing also encompasses customizing the cookies.
  • Shipping: While shipping is important to all e-commerce sites, as nowadays customers are used to immediate delivery, for perishable and delicate goods, order fulfillment demands maximum care.

To facilitate the exploration of Dapr, the connection between the bounded contexts will prefer coordination via service invocation and pub/sub instead of data duplication or any other approach.

An example – sales microservices

As an example, let’s suppose that analyzing the sales-bounded context further is going to require the following microservices:

Figure 3.2 – The sales-bounded context

Figure 3.2 – The sales-bounded context

As we can see, a sales order triggers the preparation workflow via a loosely coupled interaction. The Reservation service will also be informed by consuming events on the order status update. Once the order preparation workflow successfully completes, it will trigger Shipping as the next step.

As we have identified a few suitable bounded contexts that we explored in our sample solution, let’s find out how Dapr supports microservices architecture.

Building microservices with Dapr

How can Dapr help us build this e-commerce application by adopting a microservice architecture?

In this section, we will learn about the specific benefits Dapr brings to a microservice architecture. Let’s start by exploring them all and begin with loosely coupled microservices.

Loosely coupled microservices

With pub/sub in Dapr, we can achieve two objectives. Not only does Dapr make it transparent to use any of the supported messaging systems, such as Redis, RabbitMQ, Azure Service Bus, and Azure Event Hubs, but it also provides all the plumbing code that’s responsible for handling the message operations, ensuring at-least-once delivery.

Two microservices, signaling to one another an event via pub/sub, can coordinate via a loosely coupled connection. If the consumer is experiencing a temporary issue, the information that’s sent by the producer will stay safely in the messaging subsystem of choice, waiting for the consumer to come back and get it.

Autonomous microservices

A service with Dapr can be invoked by just specifying an application identifier. It is then Dapr’s responsibility to discover where the service is running in the hosted environment (most likely Kubernetes), how to reach it, and how to handle its request/response via a secure communication channel.

If the invoked service, over time, evolves to adopt a different set of libraries in order to change the data layer or the data storage itself, it will appear and operate just the same.

Observable microservices

The microservices that were identified in the manufacturing bounded context interact with each other and with other bounded contexts as well, and they communicate between many different hosting environments. This includes the Nodes and Pods of Kubernetes and their state stores, the messaging systems, and more.

It soon becomes clear that while a collection of logs from the Kubernetes infrastructure is helpful, what is much more needed is distributed tracing for each activity as its processing flows from one step to the next, from one microservice to the other, by sharing a common context of the customer journey.

Scalable microservices

Dapr promotes the usage of Kubernetes as the hosting platform for your microservices, enabling dynamic and rapid scaling of each independently on as many resources, Pods, and Nodes as needed.

Over the course of this book, we’ve learned how easy it is to create microservices with Dapr. From this perspective, Dapr enables architects and developers to only consider the bounded context and microservice analysis when it comes to defining the implementation details of the architecture. The unnecessary proliferation of microservices is to be avoided, and Dapr will not push your architecture toward more microservices or less, but it will significantly lift the initial effort needed.

Event-driven microservices

An event-driven architecture can be achieved in many ways: as an example, I can have a loop in my code that monitors the messaging or external subsystems, via a long polling approach, for new events.

In this scenario, I would be responsible for keeping the process active, regardless of whether I am relying on a PaaS or IaaS hosting environment. My code could leverage a library to spare me from the inner details of the message system, but nevertheless, I am still influenced by the process and host recycling as I must keep listening for events. Only at the end of this complex chain of elements will I have the code expressing my business logic.

There is a subtle but important difference: not having a library but counting on a runtime, designed to operate in complex conditions, in an environment such as Kubernetes, capable of fast recovery, reduces the code under your responsibility to just the message handling logic and is a tremendous advantage offered by Dapr.

Stateless microservices

A stateless microservice is easier to distribute over as many instances as needed, has a faster life cycle, and is more solid when it comes to handling faults and conditions of error.

Nevertheless, many – if not most – of the microservices we create need to manage a state, whether it is used to support how a request is processed or it represents the core data the code is handling.

By managing state as a service, with pluggable interchangeable components, Dapr makes any microservice practically a stateless microservice. With the reliable state API provided by Dapr, the complexity of managing state by taking concurrency and consistency into account is lifted from the service code.

This section helped us understand in which ways Dapr can help us in developing microservices architecture, knowledge that will guide us in our future endeavor in developing the architecture for the Biscotti Brutti Ma Buoni solution.

Summary

In conclusion, the reason why Dapr has an immediate appeal to native cloud developers is its ability to provide flexibility and simplicity in a very complex environment. As a fellow Microsoft colleague often said, “Only 10 years ago, an entire developer’s career could be built on what today is just a simple scaling slider."

This is the future of development, and in addition to the native cloud tools, Dapr is also offering a chance to bring together all the possible combinations of legacy applications and programming languages, so that a complete refactoring is no longer the only modernization option.

After this overview of microservice architecture, in the next chapter we will explore Dapr in more depth, starting with how services are invoked within Dapr.

Questions

  1. Why are bounded contexts so important?
  2. Should I implement a microservices architecture when designing a new solution?
  3. What is the most relevant contribution by Dapr to microservices?

Further reading

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.77.84